FreeBSDのWeb ServerにLet's Encryptの証明書を突っ込む
かねてからの懸案だったLet's Encrypt証明書対応のメモ。
2017/02/01現在、まだFreeBSDでの動作確認をしていないので、この記事を鵜呑みにしても動かない可能性が高い。
Let's Encryptとは?
Let's Encryptは、公共の利益を図る目的で Internet Security Research Group (ISRG) が運営する、無料で利用できる自動化されていてオープンな認証局。
その基本方針は
- 無料
- ドメインを保持する者が無料で信頼された証明書を取得可能
- 運用の自動化
- 証明書の取得から更新まで自動化することが可能
- 安全
- 認証局サイド、管理者の両面から、最先端のプラットフォームを提供
- 透明性
- 証明書の発行と失効が公式に記録され、一般公開され、誰でも監査できる
- オープン
- 各手続き(プロトコル)を、オープン標準として公表
- 互助・協力
- コミュニティの利益のための共同の取り組みであり、1つの組織によって統制されているものではない
以上、About - Let's Encrypt - Free SSL/TLS Certificatesより抜粋。
実際に利用するレベルから見ると、
- 無料でSSLサーバ証明書を発行してくれるサービス。
- あくまでもサーバー証明書のみであり、個人証明書などは発行しない
- ドメイン認証型の証明書
- 一般に、サーバー証明書は「ドメイン認証(DV)」、「組織(企業)認証(OV)」、「実在性監査済(EV)」がありますが、DVしか発行しない
- 無料の証明書にそこまで要求してはいけません。そのレベルの要求をするならお金を払いましょう。
というサービスを提供してくれるサイト及びサービスである。
日本語訳のサイトがあるので、是非一度見て見ることを勧める。Let's Encrypt 総合ポータル
注意点
- 有効期間が90日
- もう少し長くても良いような気もするが、自動化されている前提ならこれでいいのかもしれない。
- 暗号アルゴリズムの危殆化やProtocolの更新などを考えると、短い方が良いかもしれない。
- 秘密鍵ファイルはパスフレーズで暗号化していない
- HTTP ServerやSMTP Serverなど、Server用途で利用するなら、これはこれで仕方がないことかもしれない
- 秘密鍵ファイルの取り扱いには十分な注意をすること。SSLでHTTP部分を(多少)守ったからといって、サーバーが安全になるわけではないことに十分に留意すること
- 国コードや組織名の入力がない
- DV(Domain Validation)ならば、Domainが存在しており、かつ、そのドメインが正当であると判断できればよいので、国コードや組織名はなくても問題ない
- もちろん、OVやEVならば、組織の存在認定などを受けることからも、国コードや組織名は必要
事前準備
まずは、何はともあれ、公開するサイトを作成すること。 今回の諸元は以下のとおり
項目 | 内容 | 備考 |
---|---|---|
Platform | XenServer | |
VMのCPU | vCPU x2 | |
VMのMemory | 2G | |
VMのDisk | 20G(boot)+100G(Data/DB) | |
NIC | vNIC2本 | 管理用と対外接続用 |
OS | FreeBSD 11.0 Release | Install時点では -p7 |
pkg | Head(latest) | |
HTTPd | NGINX | pkgからinstall |
CMS | DokuWiki / WordPress | 手動 install |
SMTPd | Postfix | 試験用 |
IMAPd | dovecot | 試験用 |
Let's Encryptから証明書を取得する方法は複数ある。 Let's Encrypt自身はLet's Encryptの配布するcertbotを利用することを強く推奨している。 今回は、certbot利用する。
その他の実装
- acme-client
- ports/securityに収録されている。
- OpenBSDで開発されており、LibreSSLを利用している。
- Cで記述されており、余計なパッケージ類が不要
- 連絡先のメールアドレスを指定できない模様。
- すこし古い(2017/02/01時点でportsは0.1.11だが、開発元では0.1.15)
certbotを利用する
certbotはPython 2.7系用のscriptなので、Python 2.7が導入されていなければならない。まぁ、自分の場合、XenToolsが入っているので、どうせPython2.7がInstallされているから諦めるのがいいかもしれない。
- まずなによりもpkgで。
pkg install py27-certbot
- 思ったよりも大量のpython関連モジュールが入るのでビビる。
- これ、わからないでもないけど、勘弁して欲しい気もするなぁ。
証明書関連処理
必要なサイトのURLを確認する。 ここでは、仮に、Named Virtual Hostとして、
を保持しており、管理者のMail Addressを
foo@example.net
とする。
引数関係は コマンド解説(コマンドリファレンス)を参照のこと。 ちょっとだけメモ。
–rsa-key-size N
: RSAの鍵長。defaultは2048。–must-staple
: OCSP Must Staple 拡張を追加。defaultは追加しない-cert-path CERT_PATH
: 証明書を保存するPathを指定する。defaultは None–key-path KEY_PATH
: 証明書のインストールや失効に用いる秘密鍵のパスを指定する。defaultは None–config-dir CONFIG_DIR
: 設定ファイルのあるディレクトリを指定する。defaultは /etc/letsencrypt–work-dir WORK_DIR
: 作業用ディレクトリを指定する。defaultは /var/lib/letsencrypt–logs-dir LOGS_DIR
: ログファイルの保存先ディレクトリを指定する。defaultは /var/log/letsencrypt
取得の前提条件
DV証明書である以上、Domainにアクセスできなければならない。したがって、上記2ドメインにhttpでアクセスするとサイトが返答する状況にしておくことが重要 また、管理用のメールアドレスもちゃんとメールが受け取れるようになっていることが重要。
取得
certbot certonly --webroot \ -w /tmp/le/com -d www.example.com \ -w /tmp/le/org -d www.example.org \ -m foo@example.net --agree-tos
更新
certbot certonly --webroot \ -w /tmp/le/com -d www.example.com \ -w /tmp/le/org -d www.example.org \ --renew-by-default
この更新スクリプトを作成し、cronでDailyに実行すれば、勝手に証明書が取得できる。 取得した証明書のTimestampを確認して、必要に応じてnginx restartすれば済むはず
問題点
上記のやり方の場合、現在の自分の構成ではうまく動作しない。 現状の構成は、
+--- WAF1 | +--- lb1 ---+--- IPS1 ---+--- Web Internet ---+ | | +--- lb2 ---+--- IPS2 ---+--- Web | +--- WAF2
となっているため、Let's Encryptをlb1/lb2に仕掛けなければならない。 この場合、以下の問題が発生する
- 証明書はlb1/2に設置されなければならない
- チャレンジ(存在検査)用のTokenはWeb Server側に仕掛けなければならない
これを解決するには、
–server SERVER
: ACME ディレクトリリソースの URI を指定
を利用し、URIにWebサーバー側で提供することのないURIを設定した上で、そこにチャレンジしてもらうという手が考えられる。 流れはおそらく
- 外部のHostからLet's EncryptのTokenを引っ張ってくる
- lbの設定で、nginxでいうlocation指定にあるコンテンツにTokenを記載
- Let's EncryptでChllangeしてもらう
となるはず。これをacme-clientで実行できるならば、そちらでやる手もあるし、certbotでやる手もありそうだ。 何れにしても、lbで処理を行うことは難しいと考えられるので、何らかのscriptを考える必要がありそうだ。
なお、Let's EncryptにおけるACMEでのドメインの検査は、http-01, tls-sni-01, dns-01の3種類があるので、dns-01を利用する手も考えられる。しかし、自分の管理しているDNSはDNS Server間での同期処理はさせず、全DNS Serverにコンテンツを個別に設置している。しかも、一部のNSは外部に設置しているので、更新処理を自動化するのは悩ましい。
なお、ACMEはInternet Draft(ID)になっており、2017/02/01現在、draft-ietf-acme-acme-04として公開されている。これを見ると、oob-01も定義されている模様。
この問題に関しては、少しゆっくり考える必要がありそうだ。