それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

WindowsのRDPを特定IPからに制限する

WindowsのRDPを特定IPからに制限する

とくになんてことのない、Windowのファイアウォールの設定。

ファイアウォールを構成してRDPを特定IPに限定する。

ローカルIP(接続される側・自分自身)のIPアドレスを指定する

なぜなら、ネットワークカードが複数刺さっていたり、複数IPを割り当ててることがあるからだ

ときどき、LANには1つのIPアドレスしか割り当てられないと思い込んでる人がいる。

Windowsでも1つのLANポートに複数のIPアドレスを割り当てることも出来る。もちろんLANポートが複数あることがある。

身近な例でいうと、WifiとLANポートを両方使うような場面である。

そのためファイアウォールでは「どのIPアドレスに着信したかを識別する」を識別する、これを「ローカルIPアドレス」と呼称しているのである。

この画面では、ローカルIPといっても192.168.0.0/24 のこととは限らない。たんにWindowsが持ってるIPアドレスを示している。

リモートIPアドレスとは、送信元のIPアドレスである。

デフォルトゲートウェイを許可しておくと、ルータからNATされた接続を受け付ける。

たとえば、VPNのIPのみ接続を受け付けるとか。管理用のルータからのみ設定を受け付けるとか。

リモートとローカルをあわせて設定する。

設定を行うのは次の箇所である。

ただしパスワードを簡単にする目的では使わないで。

IP制限を、ログインパスワードを省略する目的では使わないで。

もし、ユーザーの認証までやると管理が大変だなぁ

RD GateWayまでやるか https://blog.pfs.nifcloud.com/20191711_RD_Gateway_RemoteDesktopConnection

windows でアプリの一覧を表示する shell:appsfolderを使って「送る:sendto 」のショートカットを作成する

Windows でインストール済みのアプリケーションを一覧する

Macでいうところの /Application に相当するのは、C:/Program Files なので

MacでいうところのLaunchpad に相当する、アプリ一覧のWindows版です。

なぜ必要なのかというと、VsCodeChromeといった最近のWindowsアプリケーションは C:/Users/Takuya/AppData にもインストールされているし、Windowsにもインストールされている。そのため一覧することが不可欠なのです。いちいちレジストリを見に行ってられない。

shell:appsfolder でアプリケーションの一覧がだせる

shell:appsfolder

コマンドを指定して実行する

インストール済みアプリケーションの一覧が出てくる。

ショートカットを作成する

デスクトップにショートカットを作成する

送る(SendTo)にコピーする

shell:sendto

これで、SendToをカスタマイズしやすくなります。

たとえば、MsPaintなども探すのがめんどくさいです。

最近はPhotos(写真)に関連付けられて編集のためにpaint 開くのが大変になってますからね。

送る(SendTo)に入れるというハック

Windowsの右クリックメニューは崩壊している。全くわからない。なので「送る」を使ってアプリケーションを指定したほうが速いし確実な最終手段なのだ。レジストリをいちいち触るのも面倒なのです。

参考資料

https://answers.microsoft.com/en-us/windows/forum/all/how-to-make-windows-app-shortcuts-on-windows/b5e36d1a-ad89-421c-bfa6-454598c55de6

ruby bundle のコマンドをメモ

bundle コマンドの使い方

bundle コマンドは apt で入れている前提

apt install ruby-bundler

初期化

フォルダを作る

mkdir my-work
cd my-work

初期化する

bundle init

パッケージの追加

bundle add struct 
bundle add pry

パッケージの利用 / ソース作成

ファイルを作る

touch sample.rb
chmod +x sample.rb
cat <<EOS > sample.rb
#!/usr/bin/env ruby
require "pry"
require "struct"
EOS

パッケージ利用 / 実行する

bundle exec ./sample.rb

bundler 作成されるファイルとフォルダ

ここまででフォルダ構成は次のようになった。

takuya@uci$ ls -alt
合計 48
-rwxr-xr-x  1 takuya staff   51 2023-02-09 16:50 sample.rb
-rw-r--r--  1 takuya staff 1367 2023-02-09 16:48 Gemfile.lock
drwxr-xr-x 15 takuya staff  480 2023-02-09 16:48 .bin
-rw-r--r--  1 takuya staff  124 2023-02-09 16:48 Gemfile
drwxr-xr-x  3 takuya staff   96 2023-02-09 16:44 vendor

ruby bundle 特有のフォルダは次の通り。

  • .bin/
  • Gemfile
  • Gemfile.lock
  • vendor/

.bin

bundle install したパッケージが提供するコマンドが格納される。上記の例ではpry をインストールしたのでpry コマンドが作られている。

設定変更で変えられる。

vendor/bundle/ruby

ライブラリのインストール箇所。設定変更で変えられる

Gemfile

インストールするべきGemを指定している。

Gemfile.lock

実際にインストールしたGemについて保存している。バージョンの依存関係を解決したもの

sample.rb

今回作ったサンプルファイル

bundle 設定の確認

設定ファイルは .bundle/config に作られる。 path や bin を変えてみる。

今の設定の確認

bundle config

bundle 設定の変更

vendor/bundle を変更する

bundle config --local path .cache/bundle
bundle install # インストールし直し

変更された設定の確認

bundle config

設定を消す

rm .bundle/config

bundle コマンドの使い方の確認

bundle help 
bundle help config 
bundle help install 
bundle help add

まとめ

ざっとまとめるとこんな感じ。

mkdir work
cd work
bundle init 
bundle --local path .vendor/bundle
bundle add pry
bundle exec pry

certbot でドメインのDNSプラグインで、ドメイン毎DNS-01をし複数ドメイン証明書を1枚にまとめたい。

dnsプラグインを複数使いたい。

たとえば、次のような証明書を発行要求したいとする。

cert domain
commonName example.tld
subjectAltName DNS:example.tld, DNS:example.biz,DNS:second.tld

ただし、DNSの管理先はそれぞれ別のDNSサーバになっているとする。

Base Domain DNS
example.tld cloudflare
example.biz my_own
second.tld cloudflare

この場合でも、Let'sEncryptのDNS-01チャレンジは、証明書発行が行える。

DNS-01チャレンジを手作業でTXTレコードを書き込みに行く必要がある。

certbotプラグインで自動化を断念。

プラグインが3つ必要になる。

Base Domain DNS plugin User
example.tld cloudflare Cloudflare Plugin takuya
example.biz my_own MyOwn Plugin my own
second.tld cloudflare Cloudflare Plugin another user

複数個プラグインだとcertbotへの指定方法は不明だった。複数のプラグインを使ったCertbotでのDNS-01チャレンジは出来なかった。

つまり自動化出来なかったのである。

certbot は非対応である。(たぶん)

https://certbot-dns-cloudflare.readthedocs.io/en/stable/

マニュアルを読む限り、CertbotDNS毎にプラグインを指定するのは難しそうである。(2023-02-20現在 )

Cloudflareが複数アカウントになった場合、証明書の発行に対応できないっぽい

対応策。自分でプラグインを書く。

なら、certbotプラグインプラグインを自分で作れば良い。プラグインを呼び出すプラグインがあれば良い。異なるDNSサーバに管理された複数ドメインを更新するプラグインを掛けば良いのである。

書いた。

python で書いてもcertbot への依存が残る。

コマンドを呼び出すsystemdを管理するのがもう嫌。systemd がデフォルトでcertbot をインストールするのでそれを拡張して書き直すのは大変だし。

でも途中で捨てた。

ゼロからsystemdを書いて管理するとpythonのvenv が大変だし。疲れたのよ。

作った証明書を各Dockerインスタンスに配布したり、仮想マシンに埋め込んだりが大変だし。SSHで転送するのはcertbot がroot権限要求するし。PKCS12にまとめるコードも書いたり、シェルスクリプトが肥大化していく。

WEBアプリにしよう。

で、思い切って、pythonを捨てて、php完全にライブラリ動作するように書いた。

これなら、php 出来た管理画面を持つアプリケーションに埋め込むことができる。つまり、OPNSenseやOpenMediaValutやnextcloudのようなPHPアプリケーションへ証明書発行と自動更新機能を作り込む事が可能になるはずだ。

使い方。

php の composeer でインストールして

composer require takuya/php-letencrypt-acme-dns

DNS毎のプラグインを有効化する。

<?php
// set dns plugin per Domain.
$pkey='priv_key_pem';
$mail = 'your_email@gmail.com' ;
$cli = new LetsEncryptAcmeDNS($pkey , $mail);
$dns_plugin_1 = new CloudflareDNSPlugin( 'token1', 'example.tld' );
$dns_plugin_2 = new CloudflareDNSPlugin( 'token2', 'example.biz' );
$cli->setDnsPlugin( $dns_plugin_1, 'example.tld' );
$cli->setDnsPlugin( $dns_plugin_2, 'example.biz' );

証明書を発行する

$result= $cli->orderNewCert();

複数アカウントに分かれたCloudflare。

これで、「複数アカウントに分かれたCloudflareのドメイン」を使った「複数ドメインをSANに纏めた1枚の証明書」の発行に対応できた。

ああ、しんどかった。

レポジトリ

つくったレポジトリ

https://github.com/takuya/php-letencrypt-acme-dns

Certbot で異なる「別ドメイン」の証明書を1枚にまとめる。

Let'sEncryptの証明書は別々のドメインを集約できた。

Let'sEncryptのDNS更新クライアントを作っていた。仕様をアレコレと確認し、色々な組み合わせで証明書を発行したのですが。

「もしかして、異なるベース・ドメインでも認証通るんじゃね?」 と思いついた。1分ほど考えた、SAN値の問題なので証明書的には発行できるはず。Let'sEncryptが対応可能かに依存するだろう。Let'sEncryptのドキュメントを見た。ドメインのリストに言及はない。レート制限くらいである。実験したほうが早そうだ。

早速試しててみた。すると、ベースになるドメイン名が、異なっていても、Let'sEncryptなら1枚の証明書として発行してくれた。まじか。

作った証明書をWindowsで表示した例

xxx.com と xxx.biz が両方SubjectAlternativeNameにあるのがわかるでしょうか。

試してみる。

次のような流れで、試してみた。

サンプルドメイン

次の2つのドメインで発行を試みる。

multi-sample.example.com
multi.example.biz.tld

DNS-01 を使う

今回は、処理が簡単なDNS-01を使う。

Cloudflare 側で、すべてのZoneのDNSレコードを操作できるAPIトークンを作った。

このトークンをcloudflare-api.cfg に保存してスタート。

発行コマンドを実行

/usr/bin/certbot certonly \
 --cert-name sample-multiple \
 --dns-cloudflare \
 --dns-cloudflare-credentials cloudflare-api.cfg \
  -d "multi-sample.example.com" \
  -d "multi.example.biz.tld"

実行が始まる。

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator dns-cloudflare, Installer None
Requesting a certificate for multi.example.biz.tld and multi-sample.example.com
Performing the following challenges:
dns-01 challenge for multi.example.biz.tld
dns-01 challenge for multi-sample.example.com
Waiting 10 seconds for DNS changes to propagate
Waiting for verification...
Cleaning up challenges

発行された

  Certificate Name: sample-multiple
    Serial Number: 937d530297d53029xxx
    Key Type: RSA
    Domains: multi.example.biz.tld multi-sample.example.com
    Expiry Date: 2023-05-12 15:11:37+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/sample-multiple/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/sample-multiple/privkey.pem

証明書のSAN値を確認する

SAN値でも、ちゃんと表示されている。

sudo openssl x509 -noout -ext subjectAltName -in /etc/letsencrypt/live/sample-multiple/fullchain.pem
X509v3 Subject Alternative Name:
    DNS:multi-sample.example.com, DNS:multi.example.biz.tld

勝手な思い込み=「証明書はドメインごと」

SANを使えば、別々のドメインでも発行できるのは、当たり前なんだけど。

SSL証明書を購入するときってドメインごとにに買っていたので、1枚にまとめるなんて言う発想はまったくなかった。ドメインが違うと証明書と鍵を別々に発行する。というよくある手順。これって単なる思い込み。なんですよね。OpenSSLのX509を考えれば、出来ても当然ですわ。

Let'sEncryptは複数ドメイン証明書はサブドメインだけでなく、メイン・ドメインを複数発行にも対応していました。

メリット

管理がとても簡単になる。枚数が増えると負荷が増える。枚数が減れば、管理の負荷も下がる。

セキュリティ的なこと

別々のドメイン名の証明書を1枚にまとめるのは、セキュリティ面では、推奨されない。という意見もある。それを言い出すと、ワイルドカードな証明書すら使えない。なので、ドメインごとに1枚ずつ発行する必要がある。枚数が増えると、管理コストが嵩み、逆にヒューマンエラーの危険性を招くこともある。「〇〇をすれば完全OKというセキュリティなど存在しない。」なのがセキュリティの原則だ。セキュリティはいつも落とし所が違う。管理コストなど勘案して妥当なところを使うのがセキュリティだ。

個人的な意見ですが、管理を平易化しミスを減らし危険行為を未然に防ぐという視点から別々のドメインを1枚にまとめるのはアリだと思います。

たとえば、これはGoogleとか言う大手企業のSSL証明書です。

Google さんの証明書を見てみると、SAN値に大量のドメイン名が入っています。ドメイン所有の明示しているのがわかる。

むかし、ドメイン名とSSLは管理の関係で1枚にしろとセキュリティの偉い人に言われた記憶があるんだけど。いまは、もう証明書なんて1枚で済ませるんですよ。

そして、Let'sEncryptでも対応できるのが素晴らしいと思った次第です。

Let'sEncrypt証明書のACME発行機能を Laravelに組み込みたかった。

laravel などのWebアプリでPKI証明書発行を行いたい。

ドメイン証明書の発行は、ACME(v2)で自動化できるようになっている。

ACME は アクミ(日本ではアクメ)と呼びます。ACMEで自動化できる。

したがってACMEを組み込めば、自分のアプリが自分で証明書を発行して使えるようになる。

有名な ACME プログラム certbot

certbot がLet'sEncryptの証明書発行用に有名なプログラムで、これもACMEを活用している。

certbotPythonで記述されている。しかも規模がデカくソースをちまちま読んでいくと大変である。

certbot を使って証明書発行は便利になるが、ACMEで直接証明書を発行するのに、仕組みを知るために読んでいくと大変である。

ACME の定義

ACMERFC 8555で定義されている。

読みやすい日本語訳が見つかったのでリンクを掲載しておきます。

RFC 8555 - Automatic Certificate Management Environment (ACME) 日本語訳

ACME はWEB API

ACMEには、いくつかの機能があり、機能は次のとおりである。

  • registerAccount
  • requestOrder
  • reloadOrder
  • finalizeOrder
  • requestAuthorization
  • reloadAuthorization
  • challengeAuthorization
  • requestCertificate
  • revokeCertificate

中でも特に重要なのが、次の機能になる。

  • GET /acme/new-nonce
  • POST /acme/new-acct
  • POST /acme/new-order
  • POST /acme/revoke-cert

GET/POSTと書いているので分かる通り、ACMEはHTTP(s)で発行をリクエストする。つまりWEB APIである。

ディレクトリを取得する。

Web APIなので、エンドポイントがある。エンドポイントはディレクトリと呼ばれて、URLが公開されている。

## 本垢
curl https://acme-v02.api.letsencrypt.org/directory
## ステージング
curl https://acme-staging-v02.api.letsencrypt.org/directory

実際に取得してみると、次のような一覧が出てくる。

curl https://acme-v02.api.letsencrypt.org/directory
{
  "XuCiPPffcUk": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
  "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
  "meta": {
    "caaIdentities": [
      "letsencrypt.org"
    ],
    "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf",
    "website": "https://letsencrypt.org"
  },
  "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
  "newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
  "newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
  "renewalInfo": "https://acme-v02.api.letsencrypt.org/get/draft-ietf-acme-ari-00/renewalInfo/",
  "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert

証明書取得の実験をするにはステージングのURLを叩けばい。

また pebble という 完全な実験用のACMEサーバーが用意されている

JOSE+JSON

ACMEでのリクエストは、JOSE+JSON を送る。

APIJSONで呼び出すが、リプレイ攻撃に脆弱なので、JOSEを使う。

JOSEを簡単に説明すると、JSONJSONのHMACをつける。共通鍵(Nounce)でハッシュを計算して送る。

JOSE(JavaScript Object Signing and Encryption)である。

JSOE/JSONでのリクエストは色々と使われるリクエスト手段ですから、HTTPクライアントは自作不要と思われます。よく、探せば見つかると思います。

ACMEの流れ

ACME証明書発行の流れ。

  • ユーザを登録する( new user )
  • ACMEに証明書注文を出す(new order )
  • チャレンジに対応する。(ドメイン所有証明を配置)
  • 検証依頼を出す ( challe )
  • CSRを送信する
  • 注文を完了し、証明書を受け取る。

ACMEサーバにHTTPリクエストを送信するときは、次のような手順を踏みます。

初回リクエス

  • ACMEディレクトリを取得する
  • 最初の ACME API 呼び出しに対して nonce を要求
  • HTTPヘッダを組み立てる
  • HTTP JSONボディを組み立てJOSEを作る。
  • 最後に、ACME API を呼び出します。
  • ACME APIの後、HTTPレスポンスヘッダには2つの項目が返される

次回以降のリクエストでは、Nounceを使って送信し、ユーザを登録後は、ユーザキーで署名してリクエストを投げる。またnounceはヘッダについてくるので注意しておく。

このあたりは、pythonサンプルがあった

ドメイン所有の確認

ドメインの所有者であると証明するには、いくつかの方法がある。

この確認方法をチャレンジという。

通信プロトコルを使って所有者確認を取る。

  • HTTP チャレンジhttp-01
  • DNS チャレンジdns-01
  • APLN チャレンジ tls-alpn-01

HTTPとTLS-ALPNは、ドメイン名のAレコードで指定したIPアドレスACMEサーバーがデータを取得する。

  • ACMEで指定されたトークンを、AレコードのIPアドレスに設置する。
  • 任意のAレコード先にトークン設置できるので、所有者であるとわかる。

DNS は TXT レコードを使う場合。

  • ACME サーバが指定したデータを取得
  • 取得したデータを base64-url-safe な形式にする
  • _acme-challenge.example.tld TXT 指定データDNS TXTに追記する。
  • ACME サーバがDNSのTXTを取得する
  • TXT が一致すれば、DNSレコードを操作できるので、所有者だと証明される。

DNS-01 とワイルドカード証明書

Let'sEncryptは「ワイルドカード証明書」(DNS:*.example.tld)を発行できる。

Let'sEncryptでは、ワイルドカード証明書の発行に、DNS-01が必須である。

そのため、HTTPやALPNのAレコードを使った認証ではワイルドカード証明書が発行できない。

世間にあふれる、HTTPサーバにファイル設置の方法やCertbotの基本的な使い方ではワイルドカード証明書の発行はできません。

DNSとHTTPの比較。

ということだと思います。

HTTP チャレンジの注意点

AAAA レコードとAレコードがある場合、AAAAレコードが優先されるので注意が必要。

IPv4IPv6 アドレスの両方 (例: A と AAAA レコード) を持つドメインに対して、外向きのドメイン検証のリクエストを行う場合、Let’s Encrypt は最初のコネクションでは常に IPv6 を優先して使用します。

ワイルドカード発行時チャレンジの注意点

ワイルドカードの証明書をマルチドメイン発行する際は、DNS-01のチャレンジが少し特殊です。

たとえば、次のようなワイルドカードでマルチドメインの証明書が欲しい場合

['*.luna.example.tld','luna.example.tld']

DNSチャレンジは、同じドメインに2つのTXTレコードが必要です。

ACMEでnewOrder すると次のような2つのチャレンジが要求されます。

{
  "*.lua.example.tld":{
    "domain" : "lua.example.tld",
    "status" : "pending",
    "type" : "dns-01",
    "url" : "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/123/xxxxa",
    "token" : "ABCxxxxx.",
    "payload" : "ABCxxxxx.xxxxx"
  },
  "lua.example.tld" :{
    "domain" : "lua.example.tld",
    "status" : "pending",
    "type" : "dns-01",
    "url" : "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/123/xxxxb",
    "token" : "XYZxxxx",
    "payload" : "XYZxxxx.xxxxx",
  }    
}

上記の2challengeは、どちらも対象ドメイン名がlua.example.tldです。 サブドメイン'_acme-challenge.lua.example.tld' 'TXT' 'VALUE`を追加設定しACME検証要求を行います。ただし、それぞれのTXTレコードは異なります。この場合、2つのTXTレコードを同じドメイン上に作る必要があります。(または一つずつチャレンジと検証を2回行う。)。どちらも同じドメイン名なので、一つだけチャレンジすればいいと思いこんで、ハマりました。チャレンジが2つレスポンスされていることに気づかずにハマりました。

DNSと浸透待ち

DNS-01を使う場合、TXTレコードが更新されたか、どうやって確認するのか。

DNSレコードの格納先(たとえばroute53やcloudflare)のネームサーバに問い合わせれば良い。

つまり、DNSを管理しているNSに訊きに行くです。リゾルバではありません。大本に訊くのです。

ACMEサーバもリゾルバ(キャッシュサーバー)を使わず、ネームサーバ(コンテンツサーバー)に直接クエリ確認をしているようです。(仕様は確認してません。動作から推察しています。)

DNS-01で証明書発行をするとき、チャレンジ前にTXTレコード変更反映確認したいならNSに問い合わせが良いでしょう。DNSキャッシュサーバーではなくNSです。SOAレコードからNSを調べ、ドメイン名のNSにクエリを投げTXTレコードが変わったことを確認します。TXTレコード更新が確認できたらACMEに検証リクエストを送ればいいと思います。例えば以下のようになります。

DNSのNSを取得する。

次のようなSOAが応答が得られたら

$ dig t.co soa @1.1.1.1 +short
ns1.p26.dynect.net. ops.twitter.com. 2540 3600 600 604800 60

NSを調べて

$ dig t.co ns @ns1.p26.dynect.net +short
ns1.p26.dynect.net

TXTの更新をNSに問い合わせればよい。

dig _acme-challenge.t.co txt @ns1.p26.dynect.net

キャッシュサーバに訊くよりも、大元により近いところへクエリするべきです。浸透まちのような、「似非科学」みたいなことをやらずに。ちゃんと、NSに聞けばいいだけです。NSがTXTを返していれば、ACMEサーバは検証し証明書発行をしてくれます。

わたしが試したCloudflareの場合、albert.ns.cloudflare.comに問い合わせすれば検証に成功しました。

  • CloudflareのWEBサイト(もしくはCloudflareのAPI)でTXTレコードを入れる
  • 数秒待つ
  • Cloudflare 提供のNSがTXTレコードを返してくれる。

数秒待つ間は、浸透まちではなく、APIの結果がNSサーバに反映されるまでのタイムラグです。長くても20秒程度、最短だと5秒程度でした。確認したらTXTレコードが更新されていました。

この問い合わせには、外向きのDNS / UDP / 53番 が必要です。このポートが閉じている場合は、残念ながら、直接確認する術がありません。キャッシュリゾルバがキャッシュを破棄するまで待つしかありません。これは、ACMEサーバから見た問い合わせには関係のない話です。キャッシュリゾルバからみて未更新であっても、ACMEサーバーへ検証リクエストを送ればいいと思います。検証リクエストは何度も送信できるので、自分でTXTレコードの更新を確認できなくても、ACMEサーバ側から見えればいいのです。20秒程度待ってから検証を依頼すれば問題なく、DNS-01チャレンジ検証されると思います。

また、一部のキャッシュリゾルバ(systemd-resolved や dnsmasq )はSOAレコードやNS問い合わせにはに応答してくれないようです。(dig +traceを拒否する設定、unboundでのdisallow snoopのようなものがあるのかも?)このような理由から、DNSの更新をルータや8.8.8.8 に問い合せしても殆ど役に立ちません。というか、そもそもDNS更新を8.8.8.8に問合わせる行為自体に全く意味がありません。このような理由から更新の確認には、ドメイン名のNSサーバーに直接SOA/NS/TXTクエリを投げます。このため外向きののUDP/53に通信可能な状態が必要になるでしょう。

DNS更新待機時間を短くすると別の限界が訪れます。レート制限です。検証失敗後の再チャレンジには、レート制限があり、大量のドメインでの大量枚数の証明書発行は、DNS更新の待機時間よりもレート制限回避の待機時間のほうが長くなると思われます。

2つの鍵ペア

ACMEをするには、鍵ペアが2つ必要です。一つはユーザ登録に使う鍵ペア、もう一つはドメイン証明書に使う鍵ペアです。

ユーザ登録(鍵ペア、メールアドレス)

new-user でユーザ登録するには、メールアドレスと公開鍵ペアが必要になります。

Let'sEncryptで試したところメールアドレスは空テキストも問題なく証明書が発行されました。(ステージング限定かも)

Let'sEncryptでは同じ秘密鍵を使い続ける限り同じユーザーとして扱われるそうです。

署名リクエスト(CSR)と鍵ペア

証明書を発行するために、まず秘密鍵と公開鍵の鍵ペアをつくります。次に公開鍵に情報を入力し、署名リクエストを作成します。この署名リクエストをファイルとして保存し、ACMEサーバに署名を依頼します。

このときに使う、鍵ペアは、ユーザー登録に利用した鍵ペアとは別の鍵ペアを使います。ユーザ登録した鍵ペア流用して証明書発行を試みましたが、Let'sEncryptのACMEサーバに「ダメだよ」と拒否されました。

このことから、Let'sEncryptのACMEでは、ユーザー登録の鍵ペアと証明書の鍵ペア、2つの鍵ペアが必要でした。

署名リクエストと複数ドメインのSAN/CN

Let'sEncryptの証明書は、複数ドメインの証明書に対応していました。

Let'sEncryptの証明書リクエストでは、次のように複数ドメインを記入をします。

subject.commonName= 'example.tld',
extensions.subjectAltName = "DNS:*.example.tld, DNS:example.tld"

subjectAltName(通称SAN)にDNS: example.tldDNSをつけてカンマ区切りで記入します。

commonName にベースドメインを入れて作るようです。

ただし、openssl でCSR(署名リクエスト)を作成する場合、subjectAltNameの格納にはちょっと手間が必要です。openssl.conf を明示しないとだめなようです。気づかずにハマりました。めんどくさかったです。subjectAltNameを入れたCSRの作り方は、ググれば山ほど出てきます。

commonNameを複数設定すれば良さそうな気もしますが、chrome59辺りでエラー判定されるようになったらしいです。

オーダー

ユーザ登録ができたら、オーダーをだします。

新規のオーダーをつくると、チャレンジ用URLの一覧と、オーダ・エンドポイントが出てきます。

チャレンジとオーダーはそれぞれ別のエンドポイントです。

チャレンジをリクエストして全部終われば、オーダーのエンドポイントを使って全部終了を確認します。

オーダーのステータスはpending から、processing へ、そしてvalid へ変わります。(もしかしたらready も返されるかも)すべてが終わったら、オーダーエンドポイントに対して、CSRをPOSTします。証明書が発行されます。

オーダーはリロードすることもできるので、途中から再開もできるはずです。

このあたりは、チャレンジを何度も行うのことを前提に作られているみたいです。

発行される証明書

発行される証明書は、ANY_PURPOSEがTRUEになっています。IPSecなどでも使えます。S/MIMEはFALSEだったのでクライアント(ユーザー)証明書としては使えないとおもいます。

有効期限は3ヶ月です。

証明書とペア秘密鍵PKCS#12形式

証明書は、公開鍵とCAの署名です。公開鍵が正しいことを証明してくれます。公開鍵の元になる秘密鍵も合わせて保存します。certbot などでは、専用ディレクトリに証明書と鍵が別々のファイルとして保存されます。これら2つのファイル、証明書+秘密鍵を保存しておきます。この2つのファイルを別個に管理していると煩雑なので、一つのファイルに纏めて保存したいと思うでしょう。この目的に合致するのがPKCS#12形式です。PKCS#12 形式で保存しておけば、linux の openssl コマンドやライブラリで扱えるので便利です。保存時にパスワード設定が可能です。拡張子は'.p12'を使うようです。

DNS-01を使うメリットとデメリット

DNS-01で発行すると、ワイルドカードな証明書が作れるので、証明書の管理が格段に楽になる。

もし、ワイルドカード証明書がほしいと思ったら、DNSのTXTレコードを使うしかない。

デメリット。手順に関してデメリットは無いです。ただ情報が少ないです。極端に。

世の中の大半のLet'sEncryptとCertbotの記事は、HTTP-01を使った例が多い。ググった結果がHTTPばかりで役に立たずゴミです。仕方ないです。マニュアルを読みましょう。certbot をキーワードに入れた瞬間にdns チャレンジの細かい話は、記事数の問題で埋もれてしまいます。

renew と force-renewal の違い

certbot には renew と force-renewal の使い分けがある。ACMEを見る限り、サーバ側でrenew を検出しているわけではなさそう。ACMEサーバは単純にCSRに基づく証明書を発行する機能があるだけ。renew はコマンド実行時に有効期限をみて、有効期限が30日以上であれば処理をスキップしている。ということでしょう。force-renewal は、CSRを再生成し証明書を再発行している。force は30 日以上のスキップを省略するということだと思う。ただ、鍵ペアを再生成しているかどうかは、コードを見ているので分からない。

更新(renewl)と判定される基準

Let'sEncryptで、更新と判定されるのはどのようなときか。これは公式サイトに記述がある。

発行済の証明書が更新 (または複製) の対象とみなされるのは、全く同じホスト名の集合が指定されているときです (大文字・小文字、順序は区別しない)。たとえば、[www.example.com, example.com] というドメイン名に対する証明書をリクエストした場合、同じ週に [www.example.com, example.com] に対する証明書を重複して発行できるのは、追加で 4 つまでです。しかし、[blog.example.com] というドメインを追加してホスト名の集合が変化したときには、追加でリクエストを発行できます。

更新の処理では、公開鍵とリクエストの拡張は無視されます。新しい鍵を使用していたとしても、証明書の発行は更新とみなされます。

つまり、鍵ペアを新しくしても新規とはみなされない。ドメイン名の一覧が同じであれば更新として扱われる。

公式サイトには次のような記述がある。

同じ週に [www.example.com, example.com] に対する証明書を重複して発行できるのは、追加で 4 つまでです。

このことから、「同じドメイン」に対して、鍵を変えた証明書を複数枚重複して発行する事が可能であり、鍵を変えても別の証明書として看做されない。同じドメインの証明書は複数枚存在できる。

証明書を紛失したからと鍵を変えて発行したとき、追加・再発行扱いになり。Revokeしてないからといって再発行を拒否されることもない。

revoke の取り扱い。

revoke は再生成とは違う。鍵が危殆化したときにつかう。証明書とは公開鍵の証明書なので、秘密鍵が漏れたとき、証明書は無効にしないといけない。もし、同じドメインで2枚の証明書を贅沢に発行したとき、有効期限内であれば両方とも使えるはずである。同じ秘密鍵を使ってないとしても2枚の証明書がある。秘密鍵が漏洩したとき、新しく鍵ペアから証明書をつくったとしても、漏れてしまった秘密鍵の証明書は有効期限内であれは通用してしまうので、Revokeする。

上記の「更新」の判定で書いてあるが、鍵ではなく「ドメイン名の集合」で見ている。同じドメインの証明書は複数枚発行される。そのため秘密鍵をお漏らししたとき、新規で証明書を取り直す。鍵を変えたあとでも証明書は有効期限内に通用可能であるので、Revokeしないといけない。

DNS更新をしたい。

ここまで考えて、Let'sEncryptはやはりDNS更新でワイルドカードで使うのが一番便利だと思われる。

証明書発行するのに、もっと手軽にボタンひとつで出来ないか。スマホでWebサイトの管理画面を開いてポチッと押すだけで、ドメイン作成から証明書発行と更新を簡単にできないか。

ACMEを各種のwebアプリケーションに組み込めないかと考えた結果。AcmePHPを使えば既存PHPのWEBアプリケーションに証明書作成機能組み込めるじゃないかと。

流石にJSOE/JSONリクエストから作る気力はないので、DNS更新の部分だけでも省力化したい。certbotとsystemd や cron の呪縛逃れてdocker軽量アプリで証明書を管理したいなと思った次第です。

作った

CloudflareなどのDNS更新のAPIを叩いて更新できるようにサンプルで実装してみたが、超めんどくさい。

https://github.com/takuya/php-letencrypt-acme-dns/

journalctl が容量を食いつぶすので調べる

ubuntu仮想マシンをきれいに整理しようとしたらログがいっぱいあることに気づいた。

現在のログの容量を確認する。

journalctl --disk-usage

実行例

$ sudo journalctl --disk-usage
Archived and active journals take up 344.0M in the file system.

調べていたら、Storage=volatile でファイルが /var/log にない場合でも disk-usage がゼロにはならない。 なので、--disk-usage はディスクの使用量ではなく、ログの容量である。

Storage=auto のとき

実際に保存されているログの容量

takuya@pi-ubuntu:$ du -cksh /var/log/journal/
345M    journal/
345M    total

journalctl が報告するディスク使用量(ログ容量)

takuya@pi-ubuntu:/var/log$ sudo journalctl --disk-usage
Archived and active journals take up 344.0M in the file system.

Storage=volatile のとき

journald が報告するディスク使用量

takuya@pi-zero:~ $ sudo journalctl --disk-usage
Archived and active journals take up 9.2M in the file system.

実際のディスク使用量

sudo du -cksh  /var/log/journal/
8.0K    /var/log/journal/
8.0K    合計

/run/log/journal (tmpfs/RAMディスク)にファイルが格納されている。

takuya@pi-zero:~ $ sudo du -cksh /run/log/journal/
9.3M    /run/log/journal/
9.3M    合計

ログ削除

不要なログを削除する

sudo journalctl --vacuum-time=3d

今すぐ全部消す

sudo journalctl --vacuum-time=1sec

ログ設定の見直し

rsyslog (syslog)に転送する*1ので、journald 自体のログはそこまでいらないかもしれない。 /etc/systemd/journald.conf

Storage=volatile
SystemMaxUse=50M
ForwardToSyslog=yes

journald を少なくすると、systemctl status でのログが見づらいので少々困るが、安定運用してたらあまり困ることはない。 docker の起動ログとかゴミばっかり。

ログ設定の反映

systemctl status systemd-journald 

rsyslog のほうが tail し易いので私は好き

まとめ

journalctl はディスクを結構食いつぶす。

現在の使用量は次のコマンドでわかる。

sudo journalctl --disk-usage

ただし、Storage設定がauto persist のときにのみ信用して良い。

*1:2023以降のUbuntu/Debianはrsyslogに転送しない。syslogを使うようインストールし自分で転送を書くか、journalctlの容量や条件を詳しく記入し保存する

openwrt の dhcp 割当の一覧をIPアドレス順にソートする。

ipv4 アドレスをでソートしたい。

OpenWrtでDHCPの固定割当を設定している。MAC ADDRごとにIPアドレスを静的割当している。

これを一覧画面で閲覧すると、ソートされてない。追加順に末尾に足されるだけなのだ。

追加順だととても不便なのだ。IPアドレス順に並び替えたい。

仮想マシンの台数が増えてきたので、どのIPが未使用なのか、一瞥で判明させたい。

ということで、IPアドレス順にソートして見やすくしたい。

ucidhcp 取得、書き換え、取り消し。

uci コマンドを使って、dhcp の静的な固定割当のホスト一覧を取り出せる。

DHCP固定割当ホスト一覧の取り出し

uci show dhcp 

ここから、hostsに関係するものだけにする

uci show dhcp | grep 'dhcp.host'

dhcp の割当を全削除

インデックスで指定項目を消せる

uci del dhcp.@host[5]

インデックスを[-1] にすれば末尾を消せる

uci del dhcp.@host[-1]

全消しするなら、末尾から順に消せば、すべて消すことができる。

while uci del dhcp.@host[-1] ; do : ;  done;

dhcp の固定割当を追加する。

これは、uci 全般の使い方で配列を足すやり方に習うと楽。

最初、配列を増やす。項目の末尾に空欄のエントリを追加する。

[-1] が末尾を示すので、末尾の要素に設定項目を加えていく。

項目追加の例

### add a new dhcp.host
uci add dhcp host
uci set dhcp.@host[-1].dns='1'
uci set dhcp.@host[-1].mac='dc:a6:32:dd:23:c6'
uci set dhcp.@host[-1].ip='192.168.1.22'
uci set dhcp.@host[-1].leasetime='86400'
uci set dhcp.@host[-1].name='raspi-ubuntu'

既存のDHCPをソートする

既存のDHCPをソートするには、ちょっとめんどくさいが、eval を活用するのが早そう。

たとえば、次のようにコマンドを指定すれば。

ssh root@192.168.1.1 'uci show dhcp' \
  | \grep dhcp.@host | sed -E 's/@//' | sed -E 's/=host/={}/'

rubypythonjavascriptlua で使えそうなコードになる。

実行結果の例

dhcp.host[0]={}
dhcp.host[0].mac='xxxx'
dhcp.host[0].name='EPSONxxxx'
dhcp.host[0].dns='1'
dhcp.host[0].ip='192.168.1.12'
dhcp.host[0].leasetime='3600'
dhcp.host[1]={}
dhcp.host[1].name='DESKTOP-xxxx'
dhcp.host[1].dns='1'
dhcp.host[1].mac='xxxx'
dhcp.host[1].ip='192.168.1.111'
dhcp.host[1].leasetime='3600'
dhcp.host[2]={}
dhcp.host[2].ip='192.168.1.115'
dhcp.host[2].mac='xxx'
dhcp.host[2].name='ps4'
dhcp.host[2].dns='1'
dhcp.host[2].leasetime='3600'
dhcp.host[3]={}
dhcp.host[3].name='RE505x'
dhcp.host[3].dns='1'
dhcp.host[3].mac='xxx'
dhcp.host[3].ip='192.168.1.3'
dhcp.host[3].leasetime='144400'

ソートを登録

ここまで来たら、あとは、IPアドレスでソートし、OpenWrtにadd host して確認してからcommit すればいい。

ソート手順をスクリプトにした

とはいっても。ソートを手作業で起動するのは辛い。 openwrt で動かせるようなスクリプトにしてみた。wrt は lua が使えるようなので Lua のコードにした。

https://github.com/takuya/openwrt-uci-dhcp-sort

そして、Githubのレポジトリにあげておいた

IPアドレスを数字順にソートする(整数に変換)

ip アドレスを数字順にソートしたい。

IPアドレスソートするにはいろいろな答えが思い浮かぶ。

今回は、整数に変換してソートしてみたいと思う。

ip アドレスを10進数に変換する

256進数だから、桁数分だけ256を掛けてあげれば良い。

IPアドレスを10進数に変換する例. awk

echo 10.0.0.0 |  \
  awk -F '.' '{ printf "%.0f %d.%d.%d.%d\n",$1*16777216+$2*65536+$3*256+$4,$1, $2,$3, $4 }'

実行結果

167772160 10.0.0.0

IPアドレスをソート(文字列)

単純に文字列でソートとした場合

cat << EOS | sort
172.16.10.2
172.16.1.1
172.16.2.10
EOS

実行結果

172.16.1.1
172.16.10.2
172.16.2.10

文字列でソートすると辞書順になり、上記のように、10.22.10 より先に来てしまう。

IPアドレスをソート(整数値に変換)

いったん、整数値に変換して、それをソートすれば良いとわかったで、試してみる。

簡単のためにテキストにip一覧を入れる。

cat << EOS > test.txt
172.16.10.2
172.16.1.1
172.16.2.10
EOS

整数値を計算して書き出す。

 cat test.txt | awk -F '.' '{ printf "%.0f %d.%d.%d.%d\n",$1*16777216+$2*65536+$3*256+$4,$1, $2,$3, $4 }'

実行結果

2886729985 172.16.1.1
2886732290 172.16.10.2
2886730250 172.16.2.10

さらにソートする。

cat test.txt | awk -F '.' '{ printf "%.0f %d.%d.%d.%d\n",$1*16777216+$2*65536+$3*256+$4,$1, $2,$3, $4 }' | sort

実行結果

2886729985 172.16.1.1
2886730250 172.16.2.10
2886732290 172.16.10.2

ソート用の先頭カラムを除去する

cat test.txt | \
awk -F '.' '{ printf "%.0f %d.%d.%d.%d\n",$1*16777216+$2*65536+$3*256+$4,$1, $2,$3, $4 }' |\
 sort  | awk '{ print $2}'

実行結果

172.16.1.1
172.16.2.10
172.16.10.2

これで、IPアドレスをソートできた

まとめ

整数値に変換してソートすると確実である。

IPアドレスの整数値の計算をする awk

awk -F '.'  '{ printf "%.0f %d.%d.%d.%d\n",$1*16777216+$2*65536+$3*256+$4,$1, $2,$3, $4 }'

10進数の整数値に置き換える方法は、覚えておくと便利である。

EXCELなどでも汎用的に使えて便利だと思う。

また、IPソートのためだけにプログラム言語に外部のipaddr ライブラリを入れずに済むので依存モジュールを減らすことができる。

ただ、コマンドで扱うなら、どう考えてもソート・コマンドで実装したほうが楽です。

IPアドレス(v4)を整数値(int)に変換する。

ipアドレスをint に変換する

  • 10.0.0.0 -> 167772160
  • 10.0.0.1 -> 167772161

このように、IPアドレスを整数値に置き換えたい。

1 2 3 4
v4表記 10 0 0 1
2進数 0b00001010 0b00000000 0b00000000 0b00000001

3,4オクテットだけを見てみる。

1 2 3 4
v4表記 10 0 0 0
2進数 0b00001010 0b00000000 0b00000000 0b00000000
v4表記 10 0 0 1
2進数 0b00001010 0b00000000 0b00000000 0b00000001
v4表記 10 0 1 1
2進数 0b00001010 0b00000000 0b00000001 0b00000001

第4オクテットが1つ変わる場合 10.0.0.0 -> 10.0.0.1 には、整数値で1変化する

第3オクテットが1つ変わる場合 10.0.0.1 -> 10.0.1.1 には、整数値で256変化する

つまり、第3オクテットの変化量は、整数値で、256倍になる。

1オクテットは、10進数表記を2進数に直してるだけで、2進数の桁が上がるのです。(左へビットシフトを8回繰り返すので、28 だけ繰り上がる)

他の例も考えてみると

10.0 = (8+2)   . 0 = 0b00001010 *2^8 + 0b00000000
10.1 = (8+2)   . 1 = 0b00001010 *2^8 + 0b00000001
11.1 = (8+2+1) . 1 = 0b00001011 *2^8 + 0b00000001
12.1 = (8+4)   . 1 = 0b00001100 *2^8 + 0b00000001
13.1 = (8+4+1) . 1 = 0b00001101 *2^8 + 0b00000001

ここから考えると、

1 2 3 4
v4表記 10 0 0 1
整数値 {10}*{2}^{8}*{2}^{8}*{2}^{8} {0}*{2}^{8}*{2}^{8} {0}*{2}^{8} 1

もっと単純に考えると、実質的に256進数なので

1 2 3 4
v4表記 10 0 0 1
整数値 {10}*{256}^{4-1} {0}*{256}^{4-2} {0}*{256}^{4-3} {0}*{256}^{4-4}

上のようになり、結果として256進数を10進数に直すだけである。

IP アドレスを10進数(整数)に変換する

なので、IPアドレス(v4)を整数値(10進数)に変換するには 256を桁数n-1だけかければ良い

IP アドレス a.b.c.d があるとき、それを整数にするには。

 {a}*{256}^{(n-1)}+{b}*{256}^{(n-2)}+{c}*{256}^{(n-3)}+{d}*{256}^{(n-4)}

ただし、a,b,c,d は 0-255 で、256進数であり、n は桁数を表す。

で、256 は固定なので

a*256^3+ b * a*256^2+ c * 256^1 + d * 256^0

もっと単純に、算数に落とし込むと

a*16777216 + b * 65536 + c * 256 + d 

これだけである。

IPアドレスをソートする (sort コマンド編:区切り文字利用)ドメインのソートも可能

IPアドレスをソートする

sort コマンドでip addr をソートすると、文字列順になってしまう。

なのでIP アドレスを文字列でソートすると、めんどくさい

解決策

  1. sort コマンドのオプションつける(この記事)
  2. ip addr を int にする。別記事
  3. sort -V を使う。

まずは、sort コマンドのオプションで解決する方法を探る

IPアドレスをソートするなら、-V オプションでバージョン番号としてソートするのが速い。

## 最速解決
sort -V

上記のような方法が不可能な環境もある、この記事私が想定しているのはbusyboxのsort や BSDのSortコマンドも含め考えたい。

ip アドレスのソートの問題。

たとえば、次を実行すると。

cat << EOS | sort
172.16.10.1
172.16.1.10
172.16.10.2
EOS

実行結果は正しい(ように錯覚する)

172.16.1.10
172.16.10.1
172.16.10.2

しかし、次を実行すれば

cat << EOS | sort
172.16.10.2
172.16.1.1
172.16.2.10
EOS

実行結果は「文字列」としてソートされてるとわかる。

172.16.1.1
172.16.10.2
172.16.2.10

sort オプションでの解決。

sort コマンドのオプションで解決させてみる。

sort -n -k 3 -t '.'

オプションについては、次のように指定してる。

# -t は区切りを指定
# -k はカラムを指定する最大2個
# -n は数字順に並べる。

実行例

cat << EOS | sort -n -k 3 -t '.'
172.16.1.1
172.16.2.10
172.16.10.2
EOS

実行結果

172.16.1.1
172.16.2.10
172.16.10.2

ただ、これだと、一つのオクテットしかソートされない。

そこで、複数のカラムに対して「数字順にソートする」

sort  -t '.' -k 3,3n -k 3,4n

第1,2オクテットも対象にしたい。

そこで、-k を重ねがけする。

ip アドレスのソート

sort -t '.'  -n -k 1,2 -k 3,4

-k は2つまでだけど、複数書ける。

なので、3,4 オクテットと1,2 オクテットを別々にオプションとして指定している。

実行例

cat <<EOS | sort -t '.'  -n -k 1,2 -k 3,4
192.168.12.11
192.168.2.11
192.168.21.11
172.16.21.11
172.16.121.11
10.1.1.11
10.10.10.11
10.2.10.11
EOS

実行結果

10.1.1.11
10.10.10.11
10.2.10.11
172.16.21.11
172.16.121.11
192.168.2.11
192.168.12.11
192.168.21.11

ただ、これだと、1,2オクテットが文字列になってしまう。開始と終了で数字評価指定するのは困難だ。

そこで、重ねがけにオプション -k, --key=KEYDEF をちゃんと指定する。

KEYDEF は F[.C][OPTS][,F[.C][OPTS]] の書式で、開始位置と停止位置を指定します。

F はフィールド番号で、 C はフィールド内の文字位置です。両方とも開始番号は 1 で す。 停止位置はデフォルトでは行末です。-t と -b の両方とも指定がない場合、 フィールド内の文字は、前にある空白の開始から数えられます。 OPTS には、1 文字の 並び替えオプション [bdfgiMhnRrV] を一つ以上指定します。 OPTS で指定されたキー の並び替えオプションは、グローバルな並び替えオプション より優先されます。キー が指定されない場合、行全体がキーとして使用されます。 正しくないキーの使用を突 き止めるには --debug を使ってください

( man sort より抜粋)

いい感じにやるには、-k の重ねがけしかない。

cat <<EOS |  sort  -t '.' -k1,1n -k 2,2n -k 3,3n -k 4,4n
192.168.12.11
192.168.2.11
192.168.21.11
172.16.21.11

ドメイン名の場合

echo dns10.quad9.net dns.opendns.com doh.opendns.com dns11.quad9.net | xargs -n 1 | sort -t '.'  -k2

などとすれば、ある程度はソートできる。 sort コマンドは最終カラム(末尾フィールド列)を指定する方法がないので、これ以上の詳細なソート(ドメイン名・パス)などはawk などほかのツールと組み合わせる必要がある。

ip v4アドレスはオクテットと表記が固定されているので、この記事のようなソートコマンドによるソートが可能です。

ip v6 アドレスは省略記法があるので、ドメイン名とおなじく、sort単体で並べるのは難しい。省略記法を使ってない、または、省略記法展開するという場合においてはv6アドレスもソートが可能。

まとめ

これで、IPアドレスを数字順にソートすることができる。

sort  -t '.' -k1,1n -k 2,2n -k 3,3n -k 4,4n

オプションをつければgnu の基本的なコマンドだけで ip アドレスを扱えることがわかる。

v6の場合は、HEXなのでちょっとめんどくさい。また今度考えることにする。

関連資料

sort を区切り文字でカラム指定出そーつとする場合。 sortコマンドで列を指定、数順など指定する。 - それマグで!

参考資料

man sort

最新版のcurl をビルドしてみる。

curl の新機能を試したい。

Output HTTP headers from the most recent request by using %header{name} where name is the case insensitive name of the header (without the trailing colon). The header contents are exactly as sent over the network, with leading and trailing whitespace trimmed. Added in curl 7.84.0.

便利じゃん。使いたいじゃん。でもapt 提供パッケージじゃ使えないじゃん。

最新版のCurlを入れる。

最新版のcurl に面白い機能が追加されてた(ヘッダを-w でwrite out ) のでビルドして試してみることにした。

依存パッケージ

apt install -y build-essential libssl-dev

最新版のソースを取得

https://curl.se/download/から最新版のソースを取得

curl -LJO https://curl.se/download/curl-7.87.0.tar.gz
tar zxvf curl-7.87.0.tar.gz
cd curl-7.87.0

ビルド

ビルドオプションは最低限にした。

./configure  --with-ssl --with-zlib 
make -j `nproc`

make install はしない。

インストールするとlibcurl が変わっちゃうので、システム全体でlibcurlに依存する他システムにも影響が及んでしまう。

動かしてみる

bash src/curl -V
bash src/curl https://www.apple.com/

7.80 / 7.84 以降の機能を使う。

src/curl -I -w '%header{date}\n' https://www.apple.com/
src/curl -I -w '%{header_json}\n' https://www.apple.com/

動いた。これ(-w %header{XXX})、便利すぎる!

参考資料

https://www.uxlinux.com/install-latest-curl-version-on-ubuntu-vestacp/

Ubuntu でapt アップグレードした場合のダイアログ(警告・注意・再起動)を無効化する。

Ubuntu でアップグレードした場合の警告表示を無効化する

Ubuntu でapt upgrade したときに、再起動の画面が出てきて止まる。

アップグレード自体が止まってるわけではないのですが。SSH接続して目を話してたら警告画面で止まってたりする。

cat << 'EOF' | sudo tee -a  /etc/needrestart/conf.d/99_restart.conf
$nrconf{kernelhints} = '0';
$nrconf{restart} = 'a';
EOF

結果

リスタートは後回しになりました。

なぜ再起動が必要と警告するのか。

個人的な解釈ですが。セキュリティ・パッチのアップデートが来ているのに、再起動せずしてたらどうなる?みたいなことだと思います。

かといって、アップデート後にサービス強制再起動(以前みたいに)したら、意図せずにシステムが崩壊して困る。

暗黙的に再起動やりたいが出来ない、なら明示的に尋ねよう。みたいなことだと個人的には解釈している。

参考資料

https://sig9.org/archives/4580

linuxのコマンドで文字の結合で、折返しを避ける。

長い文字列をメモに残すと折返しが面倒

たとえば、次のような長い文字をデコードする事を考える。

echo 'Sa1VODv0YrgjqYMM44Nf1FAnKyAR8LLmI1je8FYkfC60MWzHr03gwIuIhGCTK/P3fJ2z7euY/PIXagqSbKwygV0ZpBWNnZVjsxcMLCFTsoQvFTzWkZl2awIlbaToDbc3Go1t++1kk4Va6/XsXYEEDcV6qRsRY704QAF01c1rCnZF0RJRBmoK4pSA035WcdVkBK8A+PpqQ/Cvu7l0MXpdSORCYbYXu/2VF5dngcb3zYXYFXKLttKLqeud6+1DiUQ9EiWmpz5Zh4aOGVEiWwjLgl7erVNGvrOZ3xIR9TXvAkhNjm4piH+zYaljmU2ILgKxQicfURurqXhHiFAkgJcznw==' \
| openssl enc -d  -pbkdf2 -base64 -aes256

長いので折返しがめんどくさい。

ISSUEコメントに書いたり、ブログに書いたり長い文字は扱いがめんどくさい。

長い文字は結合する。

手っ取り早い解決方法は、変数に入れて結合する。

STR=''
STR+='Sa1VODv0YrgjqYMMLLmI1'
STR+='je8FYkfC60MWzHr03fJ2z7'
STR+='euY/PIXagqSbKwyxcMLC'
STR+='FTsoQvFTzWkZl2awI++1kk'
STR+='4Va6/XsXYEEDcV1c1rC'
STR+='nZF0RJRBmoK4pA+Ppq'
STR+='Q/Cvu7l0MXpdSORgcb3z'
STR+='YXYFXKLttKLqeudz5Zh4'
STR+='aOGVEiWwjLgl7erVNAkhNj'
STR+='m4piH+zYaljmU2ILgKxQicfURunw==' 

echo $STR |  openssl enc -d  -pbkdf2 -base64 -aes256

折返しをバックスラッシュで記入してもいいけど、文字列にしたほうが確実かなと思う。

cloudflare のレコードをphpから処理

cloudflare のレコードをphpから処理

cloudflare のAPIを使う。

php が手軽かなと思って選んだ。

インストール

composer require cloudflare/sdk

初期化例。

API-TOKENを使う(最近はこっち)

<?php 
$api_token = 'P-xxxxxxxxxxxxxxxx';
$token   = new \Cloudflare\API\Auth\APIToken($api_token);
$adapter = new \Cloudflare\API\Adapter\Guzzle($token);
$zone = new \Cloudflare\API\Endpoints\Zones($adapter);
$zone_id = $zone->getZoneID('example.tld');
$dns = new \Cloudflare\API\Endpoints\DNS($adapter);

API-キーを使う(まえはこっちだった、今でももごく)

<?php 
$key     = new \Cloudflare\API\Auth\APIKey($mail, $api_key);
$adapter = new \Cloudflare\API\Adapter\Guzzle($token);
$user    = new \Cloudflare\API\Endpoints\User($adapter);
$ret =  $user->getUserID();

APIキーは何でも出来るので、機能を限定したAPIトークンを使うのがベター

APIトークンの発行

API TOKENでユーザーにアクセスするには、User/UserDetailsへのアクセスを許可する必要がある。

ユーザーを許可しないと、次のサンプルコマンドが動かない。

$ret =  $user->getUserID();

これに気づかないと、サンプルが動かないとハマることになる。

サンプル・ゾーンに含まれる。DNSレコードを取り出す

ゾーンに含まれるDNSレコードを取り出す。

<?php 
$api_token = 'P-81xxxxxxxxxxxxx';
$token   = new \Cloudflare\API\Auth\APIToken($api_token);
$adapter = new \Cloudflare\API\Adapter\Guzzle($token);
$zone = new \Cloudflare\API\Endpoints\Zones($adapter);
$zone_id = $zone->getZoneID('example.tld');
$dns = new \Cloudflare\API\Endpoints\DNS($adapter);
$ret = $dns->listRecords(...['zoneID'=>$zone_id,'perPage'=>100]);
foreach ( $ret->result as $item ) {
 echo $item->name.PHP_EOL;
}

サンプル DNSレコードの詳細を確認する。

ゾーンー>ドメインー>DNSレコードと辿っていて、DNSのレコードをを取り出す。

ここで、レコードIDを発見すれば、あとはすきに操作できる。

<?php 
$api_token = 'P-81xxxxxxxxxxxxx';
$token   = new \Cloudflare\API\Auth\APIToken($api_token);
$adapter = new \Cloudflare\API\Adapter\Guzzle($token);
$zone = new \Cloudflare\API\Endpoints\Zones($adapter);
$zone_id = $zone->getZoneID('example.tld');
$dns = new \Cloudflare\API\Endpoints\DNS($adapter);
$record_id = $dns->getRecordID(...['zoneID'=>$zone_id,'name'=>$name]);
$ret = $dns->getRecordDetails($zone_id,$record_id);

ドメインサブドメイン)の追加

ドメイン名を追加するときは、次のようにコマンドを掛けば良い。

<?php
$name = 'my.respose.example.tld'
$dns->addRecord(...['zoneID'=>$z_id,'type'=>'A',          'name'=>$name,'content'=>'10.0.0.1']);
$dns->addRecord(...['zoneID'=>$z_id,'type'=>'CNAME','name'=>$name,'content'=>'example.tld' ]);
$dns->addRecord(...['zoneID'=>$z_id,'type'=>'TXT',      'name'=>$name,'content'=>'sample',  'proxied'=>false]);

ただし、TXT レコード追加するときは、proxied=false をつけないとValidationで弾かれる。(省略時デフォルト true で、TXTはTrue に不可のため。これはCloudflare側のValidationがバグってる。)

<?php

$dns->addRecord(...[
  'zoneID'=>$z_id,
  'type'=>'TXT',
  'name'=>$name,
  'content'=>'sample',
  'proxied'=>false  # ← TXT のとき、ココを忘れないようにしてください。
]);

レコードを消す

プログラミングして実験をしているとき、ゴミレコードを大量に作ってしまった場合など、まとめて削除する。

$dns->deleteRecord($zone_id,$record_id); を使う。deleteRecord メソッドで消すことが出来るので、listRecordの結果からrecord_idを取り出して、まとめて消す。

<?php

$api_token = 'YOUR_TOKEN_ID';
$base_domain = 'example.tld';
$token = new \Cloudflare\API\Auth\APIToken( $api_token );
$adapter = new \Cloudflare\API\Adapter\Guzzle( $token );
$zone = new \Cloudflare\API\Endpoints\Zones( $adapter );
$zone_id = $zone->getZoneID( $base_domain  );
$dns = new \Cloudflare\API\Endpoints\DNS( $adapter );

$ret = $dns->listRecords(...['zoneID'=>$zone_id,'perPage'=>100]);
foreach ( $ret->result as $item ) {
  $name = $item->name;
  if (preg_match('/php-le-test|challenge/', $name)){
    echo "{$item->id} {$item->name}".PHP_EOL;
    $dns->deleteRecord($zone_id,$item->id);
  }
}

2023-02-14

追記。TXT レコードが追加できないので調べた。

2023-10-31

追記。DELETEをまとめて行う方法を追記。