19と36のリビジョン間の差分 (その間の編集: 17回)
2017-08-10 23:09:09時点のリビジョン19
サイズ: 15359
コメント:
2019-12-14 23:31:51時点のリビジョン36
サイズ: 24283
コメント:
削除された箇所はこのように表示されます。 追加された箇所はこのように表示されます。
行 1: 行 1:
## page was renamed from SSL証明書/Let's EncryptでSSL証明書の新規取得と自動更新(dns-01編)
行 7: 行 8:
 * サイト利用者を証明するための「チャレンジ・レスポンス」のやりとりをHTTP(http-01)ではなく、DNS(dns-01)を使う方法である。
 * 環境(イントラネット内である、アプライアンス機器でHTTPのランディングが不可能、SMTP等で http-01 できないなど)によってはこの方法しか採れないケースもある。
 * DNSコンテンツサーバーとの連携(諸設定)が必要になってしまうが、この味を知ってしまうと http-01 手順には戻れなくなること請け合いである:-)。
 * どれくらい戻れないかというと、Webサーバーに対しても dns-01 手順を適用したくなってしまうくらい中毒性がある:-)。
 * ドメイン所有者を証明するための「チャレンジ・レスポンス」のやりとりをHTTP経由(`http-01`)ではなく、DNS経由(`dns-01`)で行う方法である。
 * 以下のような環境では、この方法しか採れないケースもある。
   * ワイルドカード証明書を取得する場合。
   * イントラネット内のサーバーでインターネットに公開してない場合。
   * アプライアンス機器等で、HTTPの直接ランディングが不可能である場合。
   * 特定プロトコル(メールサーバー等)ユースでHTTPでのランディングができない/したくない場合など。
 * もちろんデメリットもある。
   * `http-01` との比較で手順が複雑化する(DNSコンテンツサーバーへの更新が標準化されていないため)。
   * `RFC2136`(`Dynamic Updates in the Domain Name System`)で更新手順が標準化されてるじゃないか!
   * というあなたは [[https://github.com/lukas2511/dehydrated/wiki/Examples-for-DNS-01-hooks|現実]] を見た方がいい。
   * DNSコンテンツサーバーとの連携が必須でそちらをなんとかしないとどうしようもない。
 * とは言え、一度この味を知ってしまうと`http-01`手順には戻れなくなること請け合いである:-)。
 * どれくらい戻れないかというと、Webサーバーに対しても`dns-01`手順を適用したくなってしまうくらい中毒性がある:-)。
行 17: 行 27:
   * OS: FreeBSD 11.0-R
   * ACMEクライアント: dehydrated 0.4.0
   * DNSクライアント: BIND 9.11.1P3(nsupdate コマンド)
   * DNSコンテンツサーバー: BIND 9.11.1P3
   * SSL証明書利用サーバーについては言及しない。
   * OS: `FreeBSD 12.1-R`
   * ACMEクライアント: `dehydrated 0.6.5` ※少なくとも`0.6.2`以降が必須
   * DNSダイナミックアップデートクライアント: `BIND 9.14.8``nsupdate`コマンド)
   * DNSコンテンツサーバー: `BIND 9.14.8`
   * SSL証明書利用サーバーの利用詳細についてはここでは言及しない。
行 31: 行 41:
 * 本例では、www.example.jp というサイトに対して証明書を発行するものとする。
 * チャレンジ・レスポンスコードをDNSダイナミックアップデートするため、example.jp ゾーンに対するDNSコンテンツサーバーへの更新権限があるものとする。
 * ある程度制限できるとは言え、DNSダイナミックアップデートは「ゾーン」に対してフリーダムに更新できてしまうので、_acme-challenge.www.example.jp ゾーン(に分けて/委任してもらって)に、更新が閉じるよう制限するのが必要と思われる。
 * ここでは敢えて「example.jp」ゾーンから「_acme-challenge.www.example.jp」を自分自身(DNSコンテンツサーバー)へ委任するものとし、外部のDNSコンテンツサーバーへは向けないものとする。
 * ここまでお膳立てが整えられていれば、例えば、サービスゾーンのDNSコンテンツサーバーへの更新権限は得られなくても、自分のDNSコンテンツサーバーに委任してもらって、更新できるようにするのは難しくないと思う。
 * 本例では、`www.example.jp`というサイトに対して証明書を発行するものとする。
 * チャレンジ・レスポンスコードをDNSダイナミックアップデートするため、`example.jp`ゾーンに対するDNSコンテンツサーバーへの更新権限があるものとする。
 * BINDの場合「DNSダイナミックアップデート」を許可する(`allow-update`)だけでは、許可したアクセス元からの「ゾーン」に対するあらゆるレコードの変更ができてしまうので注意。
 * その辺りは`update-policy`機能(`allow-update`機能とは排他)により、ある程度制限することができる。
 * しかし`update-policy`では、アクセス元制限ができないという罠もあり多少の妥協は致し方ないところ。
行 38: 行 48:
 * DNSコンテンツサーバーは ns.example.jp とする。
   * 実際には複数のDNSコンテンツサーバーで運用されていると思う。
   * それらサーバーへの反映は ns.example.jp の notify yes; およびIXFR(Incremental Zone Transfer)により、全てのサーバーへ即時通達されるものとする。
   * それら詳細についてはここでは取り上げない。
 * SSLサーバーは www.example.jp とする。
 * 上記サーバーは同一でもかまわないし、別々でもかまわない。
 * DNSコンテンツサーバーは`ns.example.jp`とする。
   * 実際には複数のDNSコンテンツサーバー(いわゆるセカンダリサーバー)で運用されていると思う。
   * それらサーバーへの反映は`ns.example.jp`からの`notify yes;`およびIXFR(`Incremental Zone Transfer`)により、全てのセカンダリサーバーへ即時かつ遅滞なくデプロイされるものとする。
   * それら運用上の詳細については既に設定されているものとして、ここでは取り扱わない。
 * SSLサーバーは`www.example.jp`とする。
 * 上記サーバー(DNSコンテンツサーバー・SSLサーバー)は同一でもかまわないし、別々でもかまわない。
行 46: 行 56:
 * DNSサーバーは BIND 9.11.1([[https://www.freshports.org/dns/bind911|ports/dns/bind911]]  * DNSサーバーは [[https://www.freshports.org/dns/bind911|ports/dns/bind911]] をインストールする
行 49: 行 59:
 * DNSダイナミックアップデートクライアントは BIND 9.11.1([[https://www.freshports.org/dns/bind-tools|ports/dns/bind-tools]]
   * 既に BIND 9.11.1 ([[https://www.freshports.org/dns/bind911|ports/dns/bind911]])がインストールされている環境では不要。
   * FreeBSD 9.x 等といった
古い環境では nsupdate コマンドが存在したため、そのような環境でもインストールは不ある。
 * ACMEクライアントは dehydrated 0.4.0([[https://www.freshports.org/security/dehydrated|ports/security/dehydrated]]
 * DNSダイナミックアップデートクライアントは [[https://www.freshports.org/dns/bind-tools|ports/dns/bind-tools]] をインストールする
 * 古いFreeBSD標準(9.x等)の`nsupdate`コマンドはTSIGは取り扱えないため、`bind-tools`を別途インストールする必あるが、もう今更だよね
 * ACMEクライアントは [[https://www.freshports.org/security/dehydrated|ports/security/dehydrated]] をインストールしておく
行 55: 行 64:
 * いずれも ports/security/dehydrated、ports/dns/bind911 または ports/dns/bind-utils よりインストールする。  * いずれも`ports/security/dehydrated``ports/dns/bind-utils`よりインストールする。
行 61: 行 70:

== named.conf の設定(一部)例 ==
{{{
include "ダイナミックアップデートキーファイル名";
 * しかし設定ファイル(`named.conf`)やゾーンファイル(`example.jp.db`)の設置場所等に工夫が必要になる。

== /usr/local/etc/namedb/named.conf(一部) ==
{{{
include "/usr/local/etc/namedb/ns-www.key";
行 68: 行 78:
    file "example.jpゾーンファイル名";     file "/usr/local/etc/namedb/dynamic/example.jp.db";
    update-policy {
        grant ns-www. name _acme-challenge.www.example.jp. TXT;
    };
};
}}}

 * 「DNSコンテンツサーバー」と「SSLサーバー」とで、TSIG(`Transaction SIGnature`)キーを共有する。
 * TSIGキーファイルはキー名と秘密鍵で構成された、`named.conf`の書式に準拠したテキストファイルである。
 * このTSIGキーを「`/usr/local/etc/namedb/ns-www.key`」という名前で保存しておく(所有者は`bind:wheel`、パーミッションは`0400`で)。
 * また、TSIGキー名に対して、更新許可設定を与える(`update-policy`および`grant`)。
 * また変更できるレコード名およびリソースレコードを限定する(`name _acme-challenge.www.example.jp. TXT`)。
 * 残念なことに`update-policy`ではアクセス元制限ができないので、キーファイル(`secret`)の取り扱い(流出)については注意すること。
 * なお`allow-update`は`update-policy`とは排他であるため、両方設定することはできない。

== /usr/local/etc/namedb/dynamic/example.jp.db(example.jp ゾーンファイル) ==
{{{
$TTL 300

@ IN SOA ns.example.jp. domain.example.jp. (
                               2017032201 ; serial
                               7200 ; refresh (2 hours)
                               900 ; retry (15 minutes)
                               2419200 ; expire (4 weeks)
                               86400 ; minimum (1 day)
                               )
                        IN NS ns
ns IN A 192.0.2.1
}}}

 * 本ファイルの設置場所、命名規則はそれぞれのポリシーに従う(本例では ゾーン名.db とした)。
 * DNSダイナミックアップデート対象となるゾーンは`/usr/local/etc/namedb/dynamic/`ディレクトリ以下に設置されるものとしている。

== /usr/local/etc/namedb/ns-www.key(TSIGキーファイル) ==
{{{
key "ns-www." {
    algorithm hmac-sha256;
    secret "PfzeGvXiOqtPOwQJY/iNFrvlD3/eKAHRZ0TbyK5GYII=";
};
}}}

上記ファイルは以下の手順にて生成することができる。

{{{#!highlight bash
tsig-keygen -a hmac-sha256 ns-www. > /usr/local/etc/namedb/ns-www.key
chown bind:wheel /usr/local/etc/namedb/ns-www.key
chmod 0400 /usr/local/etc/namedb/ns-www.key

}}}

 * もちろん secret の部分は毎回【【ランダム】】に発行される(違うキーで同じシークレットを使ってはいけません)。
 * このファイルは`named.conf`でも、(後で説明する)`nsupdate`コマンド(`-k`オプションで)でもそのまま解釈してくれる。
 * 本ファイルの設置場所、命名規則については一概に言えることが無く、「ポリシーで」で逃げるには無責任すぎるので、以下に例を出してみる。

=== 本例における具体的設定例 ===
 * BIND側に設置する場合は、`/usr/local/etc/namedb/`ディレクトリに設置することとする。
   * `/usr/local/etc/namedb/ns-www.key`(`owner:group=bind:wheel`、`mode=0400`)
 * dehydrated側に設置する場合は、`/usr/local/etc/dehydrated/`ディレクトリに設置することとする。
   * `/usr/local/etc/dehydrated/ns-www.key`(`owner:group=root:wheel`、`mode=0400`)
 * ファイル名についてだが、「キー名.key」とするのが違和感なくていいと思う。
 * 肝心のキー名だが、[[https://ftp.isc.org/isc/bind9/cur/9.11/doc/arm/Bv9ARM.ch04.html#tsig|BIND9.11のマニュアル(TSIG)]]によれば「ホスト名1-ホスト名2.」という例がある。
 * 本気かどうかわからないが、「DNSコンテンツサーバー-ダイナミックアップデートするサーバー.」というニュアンスになる。
 * 本件の場合、`ns.example.jp`と`www.example.jp`であることから「`ns-www.`」とするのが妥当(ほんと?)。
 * まぁなんでもいいけど、わかりやすいようにね。

= SSLサーバー側の設定 =
 * ほとんど[[SSL証明書/Let's EncryptでSSL証明書の新規取得と自動更新(http-01編)|Let's EncryptでSSL証明書の新規取得と自動更新(http-01編)]]で実施した作業と同じになる。
 * 明確に違う点は、先のページでは解説してない「`HOOK`」設定となる。
 * ここでは一通り作業の意味がわかってる前提で、一通り設定を紹介する。

== /etc/periodic.conf ==
{{{
weekly_dehydrated_enable="YES"

}}}

自動更新設定(YES=自動更新する)。
[[https://www.freebsd.org/cgi/man.cgi?periodic(8)|periodic(8)]]にある通り、毎週土曜日3時に実行される。

なお今回、`weekly_dehydrated_deployscript`は指定しない(後述の HOOK 設定参照のこと)。

== /usr/local/etc/dehydrated/ns-www.key(TSIGキーファイル) ==
これは先に`tsig-keygen`コマンドで作成されたファイルである。
DNSコンテンツサーバーと同一になるようコピーするなどして設定すること。
その際のオーナー・グループ・パーミッションは以下の通りである。

{{{
chown root:wheel /usr/local/etc/dehydrated/ns-www.key
chmod 0400 /usr/local/etc/dehydrated/ns-www.key

}}}

== /usr/local/etc/dehydrated/config ==
{{{
CHALLENGETYPE="dns-01"
HOOK="${BASEDIR}/hook.sh"
RENEW_DAYS="30"
KEY_ALGO="rsa" KEYSIZE="2048"
#KEY_ALGO="prime256v1"
CONTACT_EMAIL="メールアドレス"
#テスト発行したい場合、以下の行を有効にすること。
#CA="https://acme-staging-v02.api.letsencrypt.org/directory"
}}}

 * `http-01` との時との大きな違いは`CHALLENGETYPE`と`HOOK`設定にある。
 * `HOOK`設定(によって指定されるファイル)については後述する。
 * `CHALLENGETYPE`には`dns-01`を指定する。
 * `CHALLENGETYPE`は現在`http-01`か`dns-01`、`tls-alpn-01`の3つしか選択肢は無い。

== /usr/local/etc/dehydrated/domains.txt ==
{{{
www.example.jp
}}}

本ファイルの設定については [[/SSL証明書/Let's EncryptでSSL証明書の新規取得と自動更新(http-01編)#A.2BMLMw4jDzMM0w.2FDDgMNUwoTCkMOs-|コモンネームの設定]]に準拠するものとする(例)。

=== ワイルドカード証明書 ===
[[https://github.com/lukas2511/dehydrated/blob/master/docs/domains_txt.md|v0.6.0 以降でワイルドカード証明書を取得したい]]場合の`domains.txt`の書き方は下記の通りです(`star_example_jp` は一例)。

{{{
*.example.jp > star_example_jp
}}}

 * 味噌は「 > 」で上記例では`star_example_jp`というエイリアス設定(サブディレクトリ名として使用)してやる必要があります。
 * エイリアス設定しない場合、エイリアス設定でも「`*.`」で始まる場合、エラーとなります(途中に`*`が入ってても問題無い!?)。
 * まぁシェルスクリプトで、パス名に`*`が含まれる場合の取り扱いは面倒なので、このような仕組みになってるのだと思います。
 * このエイリアスの指定は今までの指定方法でも使用できますが、わざわざ設定する必要はないでしょう。
 * 一行中の複数の「`>`」の指定はエラーです(エイリアスは一行に一個だけ)。
 * なお二行以上の設定(違うコモンネーム)に対して同一エイリアスを設定した場合の振る舞いについては未調査です:-)。

またベストプラクティクス的にははSANsによる設定がスマートかと思われます(`example.jp`をコモンネームとする)。

{{{
example.jp *.example.jp
}}}

=== RSA/ECDSAハイブリッド証明書 ===
同一コモンネームで複数キーアルゴリズムの鍵を取得したい場合は、下記の通りになります。
ハイブリッドといっても、一つの証明書の中に複数のキーアルゴリズムが入ってるわけでなく、複数の証明書で使い分ける話となります。

{{{
example.jp > example.jp.rsa2048
example.jp > example.jp.prime256v1
}}}

エイリアス機能により区別ができればいいので、エイリアス名はなんでもいいのですが、
`/usr/local/etc/dehydrated/config` で指定できるキーアルゴリズムは一つしか指定できません。

つまりこのままではどちらも同じキーアルゴリズムで取得することになるので、期待した結果は得られません。

エイリアス単位で「カスタマイズ」するために、`/usr/local/etc/dehydrated/certs/エイリアス名/config` ファイルにてカスタムしたい項目を上書き設定してやります。

{{{#!highlight console
# cat /usr/local/etc/dehydrated/certs/example.jp.rsa2048/config
KEY_ALGO="rsa"
KEYSIZE="2048"
# cat /usr/local/etc/dehydrated/certs/example.jp.prime256v1/config
KEY_ALGO="prime256v1"
}}}

セットアップした直後には `/usr/local/etc/dehydrated/certs/エイリアス名` ディレクトリが存在しないため、事前に作成しておきます。

{{{#!highlight bash
mkdir -p 0700 /usr/local/etc/dehydrated/certs/エイリアス名
}}}

== /usr/local/etc/dehydrated/hook.sh ==
{{{
#!/usr/local/bin/bash
TTL="300"
DNSSERVER="ns.example.jp"
alias nsupdate="/usr/local/bin/nsupdate -k ${BASEDIR}/ns-www.key"

deploy_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
    printf 'server %s\nupdate add _acme-challenge.%s. %d TXT "%s"\nsend\n' "${DNSSERVER}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | nsupdate
}

clean_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
    printf 'server %s\nupdate delete _acme-challenge.%s. TXT\nsend\n' "${DNSSERVER}" "${DOMAIN}" | nsupdate
}

deploy_cert {
    /usr/sbin/service apache24 restart && /usr/local/bin/dehydrated -gc
}

HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert)$ ]]; then
  "$HANDLER" "$@"
fi

}}}

 * フック(HOOK)ファイルのひな形としては`/usr/local/etc/dehydrated/hook.sh.example`を参照すること。
 * またほとんどのフックは「何もしない」ので、バージョンアップによる拡張の影響を考慮して、使用しているフック以外は無視するようプログラムしましょう。

== /usr/local/etc/dehydrated/deploy.sh ==
 * 本ケースでは`deploy.sh`を取り扱わない(無くてもいい)。
 * これは`HOOK`で指定するシェルスクリプトの deploy_cert シェル関数で代替するからである。
 * HOOK(`/usr/local/etc/dehydrated/config`)と`weekly_dehydrated_deployscript`(`/etc/periodic.conf`)の違いは以下の通りである。
   * 証明書取得する単位毎に実行されるのが HOOK、全て取得終って実行されるのが`weekly_dehydrated_deployscript`。
   * 証明書取得に成功した・失敗した(または更新なし)がわかるのが HOOK、わからないのが`weekly_dehydrated_deployscript`。
 * よって、1枚の証明書を相手にする時はそう違いは無いが、複数枚を取得して分散デプロイしたいなら HOOK しかない。
 * ファインチューニング(更新ない時は何もしないなど)したいなら HOOK 一択。
 * 逆に HOOK はデプロイだけしたいユースケースにおいて若干ながら大味(ダミーの関数を置く必要がある)である。
 * この HOOK はバージョンによって拡張されるので、未知の呼び出しに対してはエラーにならないよう、ケアする必要がある。

= 【付録】ゾーン分割によるダイナミックアップデートの制限 =
 * 以下のケースのどれか(`and/or`)が該当するなら、一工夫必要になる。
   * DNSコンテンツサーバーへの更新権限が無い/得られない場合。
   * 本例レベルの「ゾーン」に対して更新を許可するには広すぎる(セキュリティ的・ポリシー的)ので狭くしたい場合。
 * なお実現のためには「委任」が必須なので、そこは調整すること。
 * 今回`_acme-challenge.www.example.jp`ゾーン(に分けて/委任してもらって)に、更新が新しいゾーン内に閉じるよう制限してみた。
 * ここでは敢えて「`example.jp`」ゾーンから「`_acme-challenge.www.example.jp`」を自分自身(DNSコンテンツサーバー)へ委任するものとし、外部のDNSコンテンツサーバーへは向けないものとする。
 * ここまでお膳立てが整えられていれば例えば、サービスゾーンのDNSコンテンツサーバーへの更新権限は得られなくても、自前のDNSコンテンツサーバーに委任してもらって、更新できるようにするのは難しくないと思う。

== /usr/local/etc/namedb/named.conf(一部) ==
{{{
include "/usr/local/etc/namedb/ns-www.key";

zone "example.jp" {
    type master;
    file "/usr/local/etc/namedb/master/example.jp.db";
行 73: 行 306:
    file "_acme-challenge.www.example.jpゾーンファイル名";     file "/usr/local/etc/namedb/dynamic/_acme-challenge.www.example.jp.db";
行 75: 行 308:
        grant ダイナミックアップデートキー名 name _acme-challenge.www.example.jp. TXT;         grant ns-www. name _acme-challenge.www.example.jp. TXT;
行 80: 行 313:
 * 「DNSコンテンツサーバー」と「SSLサーバー」とで、TSIG(Transaction SIGnature)キーを共有する。
 * TSIGキーはキー名と秘密鍵で構成された、named.conf の書式に準拠したテキストファイルである。
 * このTSIGキーを「ダイナミックアップデートキーファイル名」で保存しておく(所有者は root:wheel、パーミッションは 0400 で)。
 * また、ダイナミックアップデートキーファイル名で定義されているキー名に対して、更新許可設定を与える(update-policy および grant)。
 * また変更できるレコード名およびリソースレコードを限定する(name _acme-challenge.www.example.jp. TXT)。

== example.jp ゾーンファイルの設定例 ==
== /usr/local/etc/namedb/master/example.jp.db(example.jp ゾーンファイル) ==
行 99: 行 326:
}}}

 * 本ファイルの設置場所、命名規則はそれぞれのポリシーに従う。
 * 少なくともFreeBSD的には、/usr/local/etc/namedb/master/ ディレクトリ以下に設置されるものとしている。
 * さらに「example.jp.db」とするか「jp.example.db」とするかなどは、特に明言しない。

== _acme-challenge.www.example.jp ゾーンファイルの設定例 ==
ns IN A 192.0.2.1
}}}

== /usr/local/etc/namedb/dynamic/_acme-challenge.www.example.jp.db(_acme-challenge.www.example.jp ゾーンファイル) ==
行 116: 行 340:
                        IN NS ns
}}}

 * 本ファイルの設置場所、命名規則はそれぞれのポリシーに従う。
 * 少なくともFreeBSD的には、/usr/local/etc/namedb/master/ ディレクトリ以下に設置されるものとしている。
 * さらに「_acme-challenge.www.example.jp.db」とするか「jp.example.www._acme-challenge.db」とするかなどは、特に明言しない。

== ダイナミックアップデートキーファイルの設定例 ==
{{{
key "キー名" {
    algorithm hmac-sha256;
    secret "シークレットキー(BASE64表現)";
};
}}}

上記ファイルは以下のコマンドにより生成することができる。

{{{
tsig-keygen -a hmac-sha256 キー名 > ダイナミックアップデートキーファイル名
chmod 0400 ダイナミックアップデートキーファイル名

}}}

 * もちろん secret の部分は毎回ランダムに発行される。
 * このファイルは named.conf でも、(後で説明する)nsupdate コマンド(-k オプションで)でもそのまま解釈してくれる。
 * 本ファイルの設置場所、命名規則については一概に言えることが無く、「ポリシーで」で逃げるには無責任すぎるので、例を出してみる。

=== 本例における具体的設定例 ===
 * BIND側に設置する場合は、/usr/local/etc/namedb/ ディレクトリに設置することとする。
 * dehydrated側に設置する場合は、/usr/local/etc/dehydrated/ ディレクトリに設置することとする。
 * ファイル名についてだが、「キー名.key」とするのが違和感なくていいと思う。
 * 肝心のキー名だが、[[https://ftp.isc.org/isc/bind9/cur/9.11/doc/arm/Bv9ARM.ch04.html#tsig|BIND9.11のマニュアル(TSIG)]]によれば「ホスト名1-ホスト名2.」という例がある。
 * 本気かどうかわからないが、「DNSコンテンツサーバー-ダイナミックアップデートするサーバー.」というニュアンスらしい。
 * 本件の場合、ns.example.jp と www.example.jp であることから「ns-www.」とするのが妥当か(ほんと?)。
 * まぁなんでもいいけど、わかりやすいようにね。

{{{
key "ns-www." {
    algorithm hmac-sha256;
    secret "PfzeGvXiOqtPOwQJY/iNFrvlD3/eKAHRZ0TbyK5GYII=";
};
}}}

 * /usr/local/etc/namedb/ns-www.key
 * /usr/local/etc/dehydrated/ns-www.key

= SSLサーバー側の設定 =
 * ほとんど[[SSL証明書/Let's EncryptでSSL証明書の新規取得と自動更新(http-01編)|Let's EncryptでSSL証明書の新規取得と自動更新(http-01編)]]で実施した作業と同じになる。
 * 明確に違う点は、先のページでは解説してない「HOOK」設定となる。
 * ここでは一通り作業の意味がわかってる前提で、一通り設定を紹介する。

== /etc/periodic.conf ==
{{{
weekly_dehydrated_enable="YES"

}}}

自動更新設定(YES=自動更新する)。
[[https://www.freebsd.org/cgi/man.cgi?periodic(8)|periodic(8)]]にある通り、毎週土曜日3時に実行される。

なお今回、weekly_dehydrated_deployscript は指定しない(後述の HOOK 設定参照のこと)。

== /usr/local/etc/dehydrated/ns-www.key ==
これは先に tsig-keygen コマンドで作成されたファイルである。
DNSコンテンツサーバーと同一になるように設定すること。

== /usr/local/etc/dehydrated/config ==
{{{
alias openssl="/usr/bin/openssl"

CHALLENGETYPE="dns-01"
HOOK="${BASEDIR}/hook.sh"
RENEW_DAYS="30"
KEY_ALGO="rsa" KEYSIZE="2048"
#KEY_ALGO="prime256v1"
CONTACT_EMAIL="メールアドレス"
#テスト発行したい場合、以下の2行を有効にすること。
#CA="https://acme-staging.api.letsencrypt.org/directory"
#CA_TERMS="https://acme-staging.api.letsencrypt.org/terms"
}}}

 * http-01 との時との大きな違いは CHALLENGETYPE と HOOK 設定にある。
 * HOOK 設定(によって指定されるファイル)については後述する。
 * CHALLENGETYPE には dns-01 を指定する。
 * CHALLENGETYPE は現在 http-01 か dns-01 の2つしか選択肢は無い。

== /usr/local/etc/dehydrated/domains.txt ==
{{{
example.org www.example.org
example.com www.example.com wiki.example.com
}}}

本ファイルの設定については [[/SSL証明書/Let's EncryptでSSL証明書の新規取得と自動更新(http-01編)#A.2BMLMw4jDzMM0w.2FDDgMNUwoTCkMOs-|コモンネームの設定]]に準拠するものとする(例)。
                        IN NS ns.example.jp.
}}}
行 212: 行 345:
#!/usr/local/bin/bash
TTL="300"
  :
行 215: 行 347:
alias nsupdate="/usr/local/bin/nsupdate -k ${BASEDIR}/ns-www.key"

function deploy_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
    printf 'server %s\nupdate add _acme-challenge.%s. %d TXT "%s"\nsend\n' "${DNSSERVER}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | nsupdate
}

function clean_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
    printf 'server %s\nupdate delete _acme-challenge.%s. TXT\nsend\n' "${DNSSERVER}" "${DOMAIN}" | nsupdate
}

function deploy_cert {
    /usr/sbin/service apache24 restart && /usr/local/bin/dehydrated -gc
}

function unchanged_cert {
    # NOTHING TO DO #
}

function invalid_challenge() {
    # NOTHING TO DO #
}

function request_failure() {
    # NOTHING TO DO #
}

function exit_hook() {
    # NOTHING TO DO #
}

HANDLER=$1; shift; $HANDLER $@
}}}

HOOK ファイルのひな形として、/usr/local/etc/dehydrated/hook.sh.example を参照すること。

== /usr/local/etc/dehydrated/deploy.sh ==
 * 先に述べたように、本ケースでは本ファイルを取り扱わない。
 * HOOK で指定するシェルスクリプトの deploy_cert 関数で代替できるからである。
 * HOOK(/usr/local/etc/dehydrated/config)と weekly_dehydrated_deployscript(/etc/periodic.conf)の違いは以下の通りである。
   * 証明書取得する単位毎に実行されるのが HOOK、全て取得終って実行されるのが weekly_dehydrated_deployscript。
   * 証明書取得に成功した・失敗した(または更新なし)がわかるのが HOOK、わからないのが weekly_dehydrated_deployscript。
 * よって、1枚の証明書を相手にする時はそう違いは無いが、複数枚を取得して分散デプロイしたいなら HOOK しかない。
 * ファインチューニング(更新ない時は何もしないなど)したいなら HOOK 一択。
 * 逆に HOOK はデプロイだけしたいユースケースにおいて若干ながら大味(ダミーの関数を置く必要がある)。
 * この HOOK はバージョンによって拡張されることがあるので、場合によっては(エラーで止まるなど)追随しなければならないこともありうる。
  :
}}}

場合によっては`DNSSERVER`設定を変更する(今回の前提では必要ない)。

== 相違点 ==
 * 以下の3ファイルに対する変更以外に相違点は無い。
 * 委任先のDNSサーバーが元と違う場合は`nsupdate`コマンドで指定するサーバーを変更する必要がある。

=== /usr/local/etc/namedb/named.conf ===
 * `example.jp`ゾーンでのDNSダイナミックアップデートの許可をしない。
 * よって設置先ディレクトリも変わる。
 * `_acme-challenge.www.example.jp`ゾーンを定義し、そちらでDNSダイナミックアップデートの設定を行う。

=== /usr/local/etc/namedb/master/example.jp.db ===
 * `_acme-challenge.www`レコードにて委任の設定を追加する。
 * もちろんDNSサーバーが複数ある場合は複数記入すること。

=== /usr/local/etc/namedb/dynamic/_acme-challenge.www.example.jp.db ===
 * 新規作成する。
 * 中身はほぼ空になるが、SOAとNSレコードの設定は必須である。
 * 設置先ディレクトリには注意すること(/usr/local/etc/namedb/dynamic ディレクトリ以下に設置)。
行 265: 行 372:
 * [[https://ftp.isc.org/isc/bind9/cur/9.11/doc/arm/Bv9ARM.ch04.html#dynamic_update|Chapter 4. Advanced DNS Features - Dynamic Update]]
 * [[https://ftp.isc.org/isc/bind9/cur/9.11/doc/arm/Bv9ARM.ch04.html#tsig|Chapter 4. Advanced DNS Features - TSIG]]
 * [[https://ftp.isc.org/isc/bind9/cur/9.11/doc/arm/Bv9ARM.ch06.html#dynamic_update_policies|Chapter 6. BIND 9 Configuration Reference - Dynamic Update Policies]]
 * [[https://ftp.isc.org/isc/bind9/cur/9.11/doc/arm/Bv9ARM.ch07.html|Chapter 7. BIND 9 Security Considerations]]

== 参考文献について一言 ==
 * BINDに関する情報を検索しても、オリジナルドキュメントが上位に来ないのは問題だと思う。
 * 検索して出てきたサイトの情報を精査してみると、微妙な問題が散見しており、オリジナル文書読むと間違いであることに気がつく。
 * 特にダイナミックアップデートについては`tsig-keygen`コマンドがあるのに`dnssec-keygen`コマンドで説明しているサイトが多いなど(歴史的理由については考慮するとしても)、涙無しには調査できない。

<<Include(SSL証明書/Memo_Of_dehydrated)>>

Let's EncryptでSSL証明書の新規作成と自動更新(dns-01編)

Let's Encrypt とは

  • Lets' Encrypt は認証局(Certificate Authority)のブランドの一つである。

  • これは「Symantec(旧Verisign)セキュア・サーバーID」「CyberTrust SureServer」「SecomTrust セコムパスポート for Web3.0」「GlobalSign クイック認証SSL」「GeoTrust RapidSSL」「Comodo PositiveSSL」などの一つと考えれば良い。

  • たぶんどれかは聞いたことあるはずと思う。アレが抜けてるというツッコミは却下で:-)。
  • 他の認証局と同じような点は、
    • 1証明書をどのように(複数IP、複数バックエンド、複数プロトコル)使用しても1取得で済む(安い認証局は大抵そうだよね)。
    • 親ドメインをまたぐ、マルチドメイン証明書(Subject Alternative Names)に対応している(全ての認証局で対応してるね)。
    • いよいよワイルドカード証明書に対応(対応してるブランドと対応してないブランドとあるね)。

    • DV(Domain Validation)証明書のみ提供(DVだけでなくOV・EVにも対応してるブランドもあるね)。

    • ただしOV(Organization Validation)証明書やEV(Extended Validation)証明書との純技術的な優劣は無い。

  • 他の認証局と明確に違う点は、
    • 無償。
    • RSA(2048bit3072bit4096bit), ECDSA(prime256v1secp384r1) の5種類の鍵が選べる(ここまで選べるブランドは限られるね)。DSAが無い?時代だ。諦めろ。

    • ACME(Automated Certificate Management Environment)プロトコルによる証明書の認証から発行までの一連のバッチ化(自動化)が可能。

    • 今どき誤差だけど、扱える端末が(他の認証局と比べて)少ない。
    • ごく一部のエンドユーザーが粘り強く使用しているような、全アクセスの0.1%未満の端末であってもサポートしないといけない用途であるならお勧めしない。
    • 逆に今どきのメジャーどころの端末・ブラウザは対応している。
    • よってPC相手にはほぼ問題無い(Windows XP? IE6? 知らんがな)。

    • 取得数制限(特に単位時間あたりの)があるので注意。詳しくは Rate Limits を参照のこと。

    • 検証(ステージング)用認証局も用意されているので、セットアップ時の検証や、ACMEクライアントの開発といった用途ではこちらを使う。

    • 「現在の」ルート証明書は「IdentTrust|DST(Digital Signature Trust) Root CA X3」である。

    • IdentTrust なの? DST なの? については旧会社のブランドも残ってるらしい、としか自分は認識してない。詳しくは会社概要でも読んでくれい。

    • 少なくとも中間証明書の発行者(Issuer)はそうである(Let's Encrypt運用元の ISRG - Internet Security Research Group ではない)。

    • このルート証明書がインストールされた端末が対応端末となる。
    • 中間証明書(Subject)は「Let's Encrypt Authority X3」である(場合によってはこっち「も」入ってることがあるかもしれない)。

  • なおツールとしては certbot が代表的だが、他にもたくさんのツールが存在する。

  • ここでは全て dehydrated を使用を前提に解説する。certbotdehydratedの違いについては特に解説しない。

  • dehydratedを選んだ理由は、

    • dehydratedはBash/ZSH依存スクリプトであるため、特別な言語環境(Python)を必要としない。

    • certbotの場合、Pythonに依存する分には問題無いが、依存するPythonモジュールが極めて大量にあって維持が大変。

    • dehydratedはまだ依存が少ない(curl のせいでずいぶん増えてるが)。

    • dehydratedの場合、わけわかんなくなっても、シェルスクリプトなのでソースコード読んで理解できる。また長いコードではない。

    • dehydratedはWebサーバー機能を内蔵していないため、Webサーバーとの競合に配慮しなくてよい。

    • dehydratedはエイリアス機能により、同じコモンネームでRSA/ECDSA両方の証明書取得が可能である。

dns-01 とは

  • ドメイン所有者を証明するための「チャレンジ・レスポンス」のやりとりをHTTP経由(http-01)ではなく、DNS経由(dns-01)で行う方法である。

  • 以下のような環境では、この方法しか採れないケースもある。
    • ワイルドカード証明書を取得する場合。
    • イントラネット内のサーバーでインターネットに公開してない場合。
    • アプライアンス機器等で、HTTPの直接ランディングが不可能である場合。
    • 特定プロトコル(メールサーバー等)ユースでHTTPでのランディングができない/したくない場合など。
  • もちろんデメリットもある。
    • http-01 との比較で手順が複雑化する(DNSコンテンツサーバーへの更新が標準化されていないため)。

    • RFC2136Dynamic Updates in the Domain Name System)で更新手順が標準化されてるじゃないか!

    • というあなたは 現実 を見た方がいい。

    • DNSコンテンツサーバーとの連携が必須でそちらをなんとかしないとどうしようもない。
  • とは言え、一度この味を知ってしまうとhttp-01手順には戻れなくなること請け合いである:-)。

  • どれくらい戻れないかというと、Webサーバーに対してもdns-01手順を適用したくなってしまうくらい中毒性がある:-)。

目次

目次

  1. Let's EncryptでSSL証明書の新規作成と自動更新(dns-01編)
    1. Let's Encrypt とは
    2. dns-01 とは
    3. 目次
    4. 検証環境
    5. 検証作業内容
      1. 想定サーバー・ドメイン
      2. DNSコンテンツサーバー側
      3. SSLサーバー側
    6. インストール
  2. DNSコンテンツサーバー側の設定
    1. /usr/local/etc/namedb/named.conf(一部)
    2. /usr/local/etc/namedb/dynamic/example.jp.db(example.jp ゾーンファイル)
    3. /usr/local/etc/namedb/ns-www.key(TSIGキーファイル)
      1. 本例における具体的設定例
  3. SSLサーバー側の設定
    1. /etc/periodic.conf
    2. /usr/local/etc/dehydrated/ns-www.key(TSIGキーファイル)
    3. /usr/local/etc/dehydrated/config
    4. /usr/local/etc/dehydrated/domains.txt
      1. ワイルドカード証明書
      2. RSA/ECDSAハイブリッド証明書
    5. /usr/local/etc/dehydrated/hook.sh
    6. /usr/local/etc/dehydrated/deploy.sh
  4. 【付録】ゾーン分割によるダイナミックアップデートの制限
    1. /usr/local/etc/namedb/named.conf(一部)
    2. /usr/local/etc/namedb/master/example.jp.db(example.jp ゾーンファイル)
    3. /usr/local/etc/namedb/dynamic/_acme-challenge.www.example.jp.db(_acme-challenge.www.example.jp ゾーンファイル)
    4. /usr/local/etc/dehydrated/hook.sh
    5. 相違点
      1. /usr/local/etc/namedb/named.conf
      2. /usr/local/etc/namedb/master/example.jp.db
      3. /usr/local/etc/namedb/dynamic/_acme-challenge.www.example.jp.db
  5. 参考文献
    1. 参考文献について一言
  6. 【付録】 dehydrated v0.40 での変更
  7. 【付録】 letsencrypt.sh から dehydrated への移行
    1. ディレクトリ名の変更
      1. /usr/local/etc/letsencrypt.sh/config.sh
      2. /usr/local/etc/letsencrypt.sh/deploy.sh
      3. /usr/local/etc/letsencrypt.sh/domains.txt
      4. /usr/local/etc/letsencrypt.sh/hook.sh
      5. /usr/local/etc/letsencrypt.sh/certs/
      6. /usr/local/etc/letsencrypt.sh/archive/
      7. 後始末
    2. 設定ファイルの変更
    3. SSL証明書利用アプリケーションの設定変更
      1. SSL証明書の旧指定例
      2. トークンディレクトリの旧指定例
    4. 起動設定(periodic)の変更

検証環境

  • 以下のソフトウェアの利用を前提に検証を実施した。いずれも最新のリリースということで確認しているが、ある程度古い環境でも問題無いと思われる。
    • OS: FreeBSD 12.1-R

    • ACMEクライアント: dehydrated 0.6.5 ※少なくとも0.6.2以降が必須

    • DNSダイナミックアップデートクライアント: BIND 9.14.8nsupdateコマンド)

    • DNSコンテンツサーバー: BIND 9.14.8

    • SSL証明書利用サーバーの利用詳細についてはここでは言及しない。
  • 上記以外の環境では、以下の点に相違が発生する。必要に応じて読み替えたし。
    • インストール方法
    • インストールされるディレクトリ
    • 自動更新のための手続きとその設定
  • 逆に以下の点は参考にできる。
    • 設定パラメータとその意味
    • 運用事例

検証作業内容

  • 本例では、www.example.jpというサイトに対して証明書を発行するものとする。

  • チャレンジ・レスポンスコードをDNSダイナミックアップデートするため、example.jpゾーンに対するDNSコンテンツサーバーへの更新権限があるものとする。

  • BINDの場合「DNSダイナミックアップデート」を許可する(allow-update)だけでは、許可したアクセス元からの「ゾーン」に対するあらゆるレコードの変更ができてしまうので注意。

  • その辺りはupdate-policy機能(allow-update機能とは排他)により、ある程度制限することができる。

  • しかしupdate-policyでは、アクセス元制限ができないという罠もあり多少の妥協は致し方ないところ。

想定サーバー・ドメイン

  • DNSコンテンツサーバーはns.example.jpとする。

    • 実際には複数のDNSコンテンツサーバー(いわゆるセカンダリサーバー)で運用されていると思う。
    • それらサーバーへの反映はns.example.jpからのnotify yes;およびIXFR(Incremental Zone Transfer)により、全てのセカンダリサーバーへ、即時かつ遅滞なくデプロイされるものとする。

    • それら運用上の詳細については既に設定されているものとして、ここでは取り扱わない。
  • SSLサーバーはwww.example.jpとする。

  • 上記サーバー(DNSコンテンツサーバー・SSLサーバー)は同一でもかまわないし、別々でもかまわない。

DNSコンテンツサーバー側

SSLサーバー側

  • DNSダイナミックアップデートクライアントは ports/dns/bind-tools をインストールする。

  • 古いFreeBSD標準(9.x等)のnsupdateコマンドはTSIGは取り扱えないため、bind-toolsを別途インストールする必要があるが、もう今更だよね。

  • ACMEクライアントは ports/security/dehydrated をインストールしておく。

インストール

  • いずれもports/security/dehydratedports/dns/bind-utilsよりインストールする。

  • オプションの選択によって手順が変わる点は無いため、ここでは明示しない。

DNSコンテンツサーバー側の設定

/usr/local/etc/namedb/named.conf(一部)

include "/usr/local/etc/namedb/ns-www.key";

zone "example.jp" {
    type master;
    file "/usr/local/etc/namedb/dynamic/example.jp.db";
    update-policy {
        grant ns-www. name _acme-challenge.www.example.jp. TXT;
    };
};
  • 「DNSコンテンツサーバー」と「SSLサーバー」とで、TSIG(Transaction SIGnature)キーを共有する。

  • TSIGキーファイルはキー名と秘密鍵で構成された、named.confの書式に準拠したテキストファイルである。

  • このTSIGキーを「/usr/local/etc/namedb/ns-www.key」という名前で保存しておく(所有者はbind:wheel、パーミッションは0400で)。

  • また、TSIGキー名に対して、更新許可設定を与える(update-policyおよびgrant)。

  • また変更できるレコード名およびリソースレコードを限定する(name _acme-challenge.www.example.jp. TXT)。

  • 残念なことにupdate-policyではアクセス元制限ができないので、キーファイル(secret)の取り扱い(流出)については注意すること。

  • なおallow-updateupdate-policyとは排他であるため、両方設定することはできない。

/usr/local/etc/namedb/dynamic/example.jp.db(example.jp ゾーンファイル)

$TTL               300

@                       IN SOA ns.example.jp. domain.example.jp. (
                               2017032201 ; serial
                               7200       ; refresh (2 hours)
                               900        ; retry (15 minutes)
                               2419200    ; expire (4 weeks)
                               86400      ; minimum (1 day)
                               )
                        IN NS ns
ns                      IN A  192.0.2.1
  • 本ファイルの設置場所、命名規則はそれぞれのポリシーに従う(本例では ゾーン名.db とした)。
  • DNSダイナミックアップデート対象となるゾーンは/usr/local/etc/namedb/dynamic/ディレクトリ以下に設置されるものとしている。

/usr/local/etc/namedb/ns-www.key(TSIGキーファイル)

key "ns-www." {
    algorithm hmac-sha256;
    secret "PfzeGvXiOqtPOwQJY/iNFrvlD3/eKAHRZ0TbyK5GYII=";
};

上記ファイルは以下の手順にて生成することができる。

   1 tsig-keygen -a hmac-sha256 ns-www. > /usr/local/etc/namedb/ns-www.key
   2 chown bind:wheel /usr/local/etc/namedb/ns-www.key
   3 chmod 0400       /usr/local/etc/namedb/ns-www.key
  • もちろん secret の部分は毎回【【ランダム】】に発行される(違うキーで同じシークレットを使ってはいけません)。
  • このファイルはnamed.confでも、(後で説明する)nsupdateコマンド(-kオプションで)でもそのまま解釈してくれる。

  • 本ファイルの設置場所、命名規則については一概に言えることが無く、「ポリシーで」で逃げるには無責任すぎるので、以下に例を出してみる。

本例における具体的設定例

  • BIND側に設置する場合は、/usr/local/etc/namedb/ディレクトリに設置することとする。

    • /usr/local/etc/namedb/ns-www.keyowner:group=bind:wheelmode=0400

  • dehydrated側に設置する場合は、/usr/local/etc/dehydrated/ディレクトリに設置することとする。

    • /usr/local/etc/dehydrated/ns-www.keyowner:group=root:wheelmode=0400

  • ファイル名についてだが、「キー名.key」とするのが違和感なくていいと思う。
  • 肝心のキー名だが、BIND9.11のマニュアル(TSIG)によれば「ホスト名1-ホスト名2.」という例がある。

  • 本気かどうかわからないが、「DNSコンテンツサーバー-ダイナミックアップデートするサーバー.」というニュアンスになる。
  • 本件の場合、ns.example.jpwww.example.jpであることから「ns-www.」とするのが妥当(ほんと?)。

  • まぁなんでもいいけど、わかりやすいようにね。

SSLサーバー側の設定

/etc/periodic.conf

weekly_dehydrated_enable="YES"

自動更新設定(YES=自動更新する)。 periodic(8)にある通り、毎週土曜日3時に実行される。

なお今回、weekly_dehydrated_deployscriptは指定しない(後述の HOOK 設定参照のこと)。

/usr/local/etc/dehydrated/ns-www.key(TSIGキーファイル)

これは先にtsig-keygenコマンドで作成されたファイルである。 DNSコンテンツサーバーと同一になるようコピーするなどして設定すること。 その際のオーナー・グループ・パーミッションは以下の通りである。

chown root:wheel /usr/local/etc/dehydrated/ns-www.key
chmod 0400       /usr/local/etc/dehydrated/ns-www.key

/usr/local/etc/dehydrated/config

CHALLENGETYPE="dns-01"
HOOK="${BASEDIR}/hook.sh"
RENEW_DAYS="30"
KEY_ALGO="rsa" KEYSIZE="2048"
#KEY_ALGO="prime256v1"
CONTACT_EMAIL="メールアドレス"
#テスト発行したい場合、以下の行を有効にすること。
#CA="https://acme-staging-v02.api.letsencrypt.org/directory"
  • http-01 との時との大きな違いはCHALLENGETYPEHOOK設定にある。

  • HOOK設定(によって指定されるファイル)については後述する。

  • CHALLENGETYPEにはdns-01を指定する。

  • CHALLENGETYPEは現在http-01dns-01tls-alpn-01の3つしか選択肢は無い。

/usr/local/etc/dehydrated/domains.txt

www.example.jp

本ファイルの設定については コモンネームの設定に準拠するものとする(例)。

ワイルドカード証明書

v0.6.0 以降でワイルドカード証明書を取得したい場合のdomains.txtの書き方は下記の通りです(star_example_jp は一例)。

*.example.jp > star_example_jp
  • 味噌は「 > 」で上記例ではstar_example_jpというエイリアス設定(サブディレクトリ名として使用)してやる必要があります。

  • エイリアス設定しない場合、エイリアス設定でも「*.」で始まる場合、エラーとなります(途中に*が入ってても問題無い!?)。

  • まぁシェルスクリプトで、パス名に*が含まれる場合の取り扱いは面倒なので、このような仕組みになってるのだと思います。

  • このエイリアスの指定は今までの指定方法でも使用できますが、わざわざ設定する必要はないでしょう。
  • 一行中の複数の「>」の指定はエラーです(エイリアスは一行に一個だけ)。

  • なお二行以上の設定(違うコモンネーム)に対して同一エイリアスを設定した場合の振る舞いについては未調査です:-)。

またベストプラクティクス的にははSANsによる設定がスマートかと思われます(example.jpをコモンネームとする)。

example.jp *.example.jp

RSA/ECDSAハイブリッド証明書

同一コモンネームで複数キーアルゴリズムの鍵を取得したい場合は、下記の通りになります。 ハイブリッドといっても、一つの証明書の中に複数のキーアルゴリズムが入ってるわけでなく、複数の証明書で使い分ける話となります。

example.jp > example.jp.rsa2048
example.jp > example.jp.prime256v1

エイリアス機能により区別ができればいいので、エイリアス名はなんでもいいのですが、 /usr/local/etc/dehydrated/config で指定できるキーアルゴリズムは一つしか指定できません。

つまりこのままではどちらも同じキーアルゴリズムで取得することになるので、期待した結果は得られません。

エイリアス単位で「カスタマイズ」するために、/usr/local/etc/dehydrated/certs/エイリアス名/config ファイルにてカスタムしたい項目を上書き設定してやります。

   1 # cat /usr/local/etc/dehydrated/certs/example.jp.rsa2048/config
   2 KEY_ALGO="rsa"
   3 KEYSIZE="2048"
   4 # cat /usr/local/etc/dehydrated/certs/example.jp.prime256v1/config
   5 KEY_ALGO="prime256v1"
   6 

セットアップした直後には /usr/local/etc/dehydrated/certs/エイリアス名 ディレクトリが存在しないため、事前に作成しておきます。

   1 mkdir -p 0700 /usr/local/etc/dehydrated/certs/エイリアス名

/usr/local/etc/dehydrated/hook.sh

TTL="300"
DNSSERVER="ns.example.jp"
alias nsupdate="/usr/local/bin/nsupdate -k ${BASEDIR}/ns-www.key"

deploy_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
    printf 'server %s\nupdate add _acme-challenge.%s. %d TXT "%s"\nsend\n' "${DNSSERVER}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | nsupdate
}

clean_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
    printf 'server %s\nupdate delete _acme-challenge.%s. TXT\nsend\n' "${DNSSERVER}" "${DOMAIN}" | nsupdate
}

deploy_cert {
    /usr/sbin/service apache24 restart && /usr/local/bin/dehydrated -gc
}

HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert)$ ]]; then
  "$HANDLER" "$@"
fi
  • フック(HOOK)ファイルのひな形としては/usr/local/etc/dehydrated/hook.sh.exampleを参照すること。

  • またほとんどのフックは「何もしない」ので、バージョンアップによる拡張の影響を考慮して、使用しているフック以外は無視するようプログラムしましょう。

/usr/local/etc/dehydrated/deploy.sh

  • 本ケースではdeploy.shを取り扱わない(無くてもいい)。

  • これはHOOKで指定するシェルスクリプトの deploy_cert シェル関数で代替するからである。

  • HOOK(/usr/local/etc/dehydrated/config)とweekly_dehydrated_deployscript/etc/periodic.conf)の違いは以下の通りである。

    • 証明書取得する単位毎に実行されるのが HOOK、全て取得終って実行されるのがweekly_dehydrated_deployscript

    • 証明書取得に成功した・失敗した(または更新なし)がわかるのが HOOK、わからないのがweekly_dehydrated_deployscript

  • よって、1枚の証明書を相手にする時はそう違いは無いが、複数枚を取得して分散デプロイしたいなら HOOK しかない。
  • ファインチューニング(更新ない時は何もしないなど)したいなら HOOK 一択。
  • 逆に HOOK はデプロイだけしたいユースケースにおいて若干ながら大味(ダミーの関数を置く必要がある)である。
  • この HOOK はバージョンによって拡張されるので、未知の呼び出しに対してはエラーにならないよう、ケアする必要がある。

【付録】ゾーン分割によるダイナミックアップデートの制限

  • 以下のケースのどれか(and/or)が該当するなら、一工夫必要になる。

    • DNSコンテンツサーバーへの更新権限が無い/得られない場合。
    • 本例レベルの「ゾーン」に対して更新を許可するには広すぎる(セキュリティ的・ポリシー的)ので狭くしたい場合。
  • なお実現のためには「委任」が必須なので、そこは調整すること。
  • 今回_acme-challenge.www.example.jpゾーン(に分けて/委任してもらって)に、更新が新しいゾーン内に閉じるよう制限してみた。

  • ここでは敢えて「example.jp」ゾーンから「_acme-challenge.www.example.jp」を自分自身(DNSコンテンツサーバー)へ委任するものとし、外部のDNSコンテンツサーバーへは向けないものとする。

  • ここまでお膳立てが整えられていれば例えば、サービスゾーンのDNSコンテンツサーバーへの更新権限は得られなくても、自前のDNSコンテンツサーバーに委任してもらって、更新できるようにするのは難しくないと思う。

/usr/local/etc/namedb/named.conf(一部)

include "/usr/local/etc/namedb/ns-www.key";

zone "example.jp" {
    type master;
    file "/usr/local/etc/namedb/master/example.jp.db";
};

zone "_acme-challenge.www.example.jp" {
    type master;
    file "/usr/local/etc/namedb/dynamic/_acme-challenge.www.example.jp.db";
    update-policy {
        grant ns-www. name _acme-challenge.www.example.jp. TXT;
    };
};

/usr/local/etc/namedb/master/example.jp.db(example.jp ゾーンファイル)

$TTL               300

@                       IN SOA ns.example.jp. domain.example.jp. (
                               2017032201 ; serial
                               7200       ; refresh (2 hours)
                               900        ; retry (15 minutes)
                               2419200    ; expire (4 weeks)
                               86400      ; minimum (1 day)
                               )
                        IN NS ns
_acme-challenge.www     IN NS ns
ns                      IN A  192.0.2.1

/usr/local/etc/namedb/dynamic/_acme-challenge.www.example.jp.db(_acme-challenge.www.example.jp ゾーンファイル)

$TTL               300

@                       IN SOA ns.example.jp. domain.example.jp. (
                               2017032201 ; serial
                               7200       ; refresh (2 hours)
                               900        ; retry (15 minutes)
                               2419200    ; expire (4 weeks)
                               86400      ; minimum (1 day)
                               )
                        IN NS ns.example.jp.

/usr/local/etc/dehydrated/hook.sh

  :
DNSSERVER="ns.example.jp"
  :

場合によってはDNSSERVER設定を変更する(今回の前提では必要ない)。

相違点

  • 以下の3ファイルに対する変更以外に相違点は無い。
  • 委任先のDNSサーバーが元と違う場合はnsupdateコマンドで指定するサーバーを変更する必要がある。

/usr/local/etc/namedb/named.conf

  • example.jpゾーンでのDNSダイナミックアップデートの許可をしない。

  • よって設置先ディレクトリも変わる。
  • _acme-challenge.www.example.jpゾーンを定義し、そちらでDNSダイナミックアップデートの設定を行う。

/usr/local/etc/namedb/master/example.jp.db

  • _acme-challenge.wwwレコードにて委任の設定を追加する。

  • もちろんDNSサーバーが複数ある場合は複数記入すること。

/usr/local/etc/namedb/dynamic/_acme-challenge.www.example.jp.db

  • 新規作成する。
  • 中身はほぼ空になるが、SOAとNSレコードの設定は必須である。
  • 設置先ディレクトリには注意すること(/usr/local/etc/namedb/dynamic ディレクトリ以下に設置)。

参考文献

参考文献について一言

  • BINDに関する情報を検索しても、オリジナルドキュメントが上位に来ないのは問題だと思う。
  • 検索して出てきたサイトの情報を精査してみると、微妙な問題が散見しており、オリジナル文書読むと間違いであることに気がつく。
  • 特にダイナミックアップデートについてはtsig-keygenコマンドがあるのにdnssec-keygenコマンドで説明しているサイトが多いなど(歴史的理由については考慮するとしても)、涙無しには調査できない。

【付録】 dehydrated v0.40 での変更

v0.40 で、ライセンス受諾の確認が必須になりました。 それまでは暗黙の受諾として、dehydrated が勝手に受諾していましたが、 別途手動で、dehydrated --register --accept-terms を実行する必要があります。

ステージング用のライセンス受諾ともまた別の話となりますので、 CA だけでなく、CA_TERMS も合わせて設定する必要があります。

この振る舞いは /usr/local/etc/dehydrated/accounts/ハッシュキー/account_key.pem の有り無しがトリガーとなりますので、 既にこのファイルがある環境では影響有りません。

初回実行に手動で実行していれば気がつくレベルとなります(エラーメッセージ等「読まない」人についてはノーコメント)。

【付録】 letsencrypt.sh から dehydrated への移行

商標上の理由で、letsencrypt.sh から dehydrated へ名前変更が実施された。 合わせてディレクトリから設定まで色々変更になったので対応についてメモっておく。

ディレクトリ名の変更

/usr/local/etc/letsencrypt.sh から /usr/local/etc/dehydrated へ変更になったわけであるが、更新後は新しいディレクトリが作成されていることもあり、こまめに手を入れる必要がある。

/usr/local/etc/letsencrypt.sh/config.sh

mv /usr/local/etc/letsencrypt.sh/config.sh /usr/local/etc/dehydrated/config

※config.sh から config に変更になったので合わせて変更を実施する。

/usr/local/etc/letsencrypt.sh/deploy.sh

mv /usr/local/etc/letsencrypt.sh/deploy.sh /usr/local/etc/dehydrated/

/usr/local/etc/letsencrypt.sh/domains.txt

mv /usr/local/etc/letsencrypt.sh/domains.txt /usr/local/etc/dehydrated/

/usr/local/etc/letsencrypt.sh/hook.sh

mv /usr/local/etc/letsencrypt.sh/hook.sh /usr/local/etc/dehydrated/

/usr/local/etc/letsencrypt.sh/certs/

mv /usr/local/etc/letsencrypt.sh/certs /usr/local/etc/dehydrated/
  • certs ディレクトリはインストール時には作成されない。
  • SSL証明書取得時にコモンネームと合わせて作成される。
  • 既に /usr/local/etc/dehydrated/certs ディレクトリが作成されている場合は、子ディレクトリ(コモンネーム)を個別にリネーム(移動)すること。

/usr/local/etc/letsencrypt.sh/archive/

mv /usr/local/etc/letsencrypt.sh/archive /usr/local/etc/dehydrated/
  • archive ディレクトリはインストール時には作成されない。
  • SSL証明書クリーンナップ(-gc オプション指定)時にコモンネームと合わせて作成される。
  • 既に /usr/local/etc/dehydrated/archive ディレクトリが作成されている場合は、子ディレクトリ(コモンネーム)を個別にリネーム(移動)すること。

後始末

rmdir /usr/local/etc/letsencrypt.sh

※全ての移動が完了すれば、ディレクトリもファイルも残ってないはずなので削除する。

設定ファイルの変更

リネームによる影響で変更になるファイルは以下の3ファイルである。 ディレクトリ名およびコマンド名に letsencrypt.sh というキーワードが無いか確認し、dehydrated に置き換えること。

  • /usr/local/etc/dehydrated/config
  • /usr/local/etc/dehydrated/deploy.sh
  • /usr/local/etc/dehydrated/hook.sh

SSL証明書利用アプリケーションの設定変更

本例では Apache(2.4)を例に説明した。 当該箇所のディレクトリ名変更を新しいディレクトリ名に変更する。

SSL証明書の旧指定例

  SSLCertificateFile    /usr/local/etc/letsencrypt.sh/certs/コモンネーム/fullchain.pem
  SSLCertificateKeyFile /usr/local/etc/letsencrypt.sh/certs/コモンネーム/privkey.pem

トークンディレクトリの旧指定例

Alias /.well-known/acme-challenge/ /usr/local/etc/letsencrypt.sh/.acme-challenges/

<Directory /usr/local/etc/letsencrypt.sh/.acme-challenges>
  Options       None
  AllowOverride None
  Require       all granted
  Header        add Content-Type text/plain
</Directory>

起動設定(periodic)の変更

/etc/periodic.conf あるいは /etc/periodic.conf.local のどちらかの設定で定期更新を実施しているわけだが、変数名が変更になっている。

本例では以下のような設定を実施しているが、

weekly_letsencrypt_enable="YES"
weekly_letsencrypt_deployscript="/usr/local/etc/letsencrypt.sh/deploy.sh"

下記のように変更する。

weekly_dehydrated_enable="YES"
weekly_dehydrated_deployscript="/usr/local/etc/dehydrated/deploy.sh"

単純に letsencrypt や letsencrypt.sh を dehydrated に置き換えるだけである。

certificate/レッツエンクリプトでSSL証明書の新規取得と自動更新(dns-01編) (最終更新日時 2019-12-14 23:31:51 更新者 NorikatsuShigemura)