26と27のリビジョン間の差分
2017-08-17 02:05:05時点のリビジョン26
サイズ: 20815
コメント:
2017-08-17 02:14:37時点のリビジョン27
サイズ: 20818
コメント:
削除された箇所はこのように表示されます。 追加された箇所はこのように表示されます。
行 7: 行 7:
 * ト利用者を証明するための「チャレンジ・レスポンス」のやりとりをHTTP経由(http-01)ではなく、DNS経由(dns-01)で行う方法である。  * ドメン所有者を証明するための「チャレンジ・レスポンス」のやりとりをHTTP経由(http-01)ではなく、DNS経由(dns-01)で行う方法である。

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コンテンツサーバーへの更新が標準化されていないため)。
    • RFC2136(Dynamic Updates in the Domain Name System)で更新手順が標準化されてるじゃないか!
    • というあなたは 現実 を見た方がいい。

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

目次

検証環境

  • 以下のソフトウェアの利用を前提に検証を実施した。いずれも最新のリリースということで確認しているが、ある程度古い環境でも問題無いと思われる。
    • OS: FreeBSD 11.1-R
    • ACMEクライアント: dehydrated 0.4.0
    • DNSダイナミックアップデートクライアント: BIND 9.11.2(nsupdate コマンド)
    • DNSコンテンツサーバー: BIND 9.11.2
    • 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 をインストールする。

  • なお既にBIND(ports/dns/bind911)がインストールされている環境では不要。

  • 古いFreeBSD標準(9.x等)の nsupdate コマンドはTSIGは取り扱えないため、やはりインストールする必要がある。
  • ACMEクライアントは ports/security/dehydrated をインストールしておく。

インストール

  • いずれも ports/security/dehydrated、ports/dns/bind911 または ports/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-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=";
};

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

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

  • 本気かどうかわからないが、「DNSコンテンツサーバー-ダイナミックアップデートするサーバー.」というニュアンスになる。
  • 本件の場合、ns.example.jp と www.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

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

www.example.jp

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

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

TTL="300"
DNSSERVER="ns.example.jp"
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

  • 本ケースでは 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 コマンドで説明しているサイトがあるなど、涙無しには調査できない。

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