Let s EncryptでローカルのWebサーバをSSL化
WebのSSL通信はもはや必須に近いものとなっていて、テスト環境もSSLで構築する必要があります。
その時悩ましいのが証明書の問題です。信頼できない証明書のままテストすることもできますが、本番環境では大丈夫なのかと不安になります。自己署名のCAを作ると、今度はクライアントマシンに仮のルート証明書や中間証明書を入れなくてはならず、後片付けが大変です。
そのような経緯があり、インターネットに公開していないテストサーバーにSSL接続する際にLet's Encryptをうまく使う方法がないか考えてみました。
Let's Encryptについて
Let's Encryptは、非営利団体の Internet Security Research Group (ISRG) が提供する自動化されたフリー認証局です。
この認証局ではDNSやWebサーバーに任意のファイルをおくことによる自動認証が用いられます。
このことをAutomatic Certificate Management Environment (ACME)と呼びます。
ACMEプロトコルで証明書を取得する手法として代表的なものにLinuxなどで使えるCertbotがあります。
これは、執筆時点でLet's Encryptには次のようなレート制限があります。
- 登録ドメインごとの証明書は1週間ごとに、30,000個まで
- 重複する証明書は1週間ごとに30,000個まで
- 検証の失敗は1時間とごに60個まで
- IPアドレスごとのアカウント数は、1つのIPにつき3時間ごと50アカウントまで
- ACMEv2ではNew Ordersは1アカウントにつき、3時間ごとに1500まで
この制限を超えないようにするために、最初はステージング(テスト)環境を用いることが推奨されています。
事前準備
Let's Encryptで証明書を発行するにはいくつかのパターンが用意されていますが、それらを応用して今回のケースに対応するとなると次のいずれかの環境が必要だと考えました。もしかしたら他にも方法はあるかもしれません。
- サブドメインに対してTXTレコードが設定できるDNS
公式のユーザーガイドにあるように、DNSにレコードを設定してCertbotで認証し、証明書を作成するのが一番早い方法だと思ったのですが、筆者の場合DNSをサービスとして利用しているのでこれができませんでした。
- DNSとシェルが実行可能なマシン(固定グローバルIPアドレス)
DNSに、シェルが可能なマシンの固定IPアドレスを登録して、そのIPが割り振られたマシンで操作します。マシンは80番と443番ポートをインターネットに公開できることが条件です。これは一時的な対応でかまいません。
- DDNS
無料で使えるDDNS用のCertbotのプラグインも開発されています。たとえばduckDNS用はGithub:「certbot_dns_duckdns」で開発されています。これを使ってLet's Encryptの証明書を受ける方法もあります。証明書を更新する時以外は、ファイアウォール等で外部からの接続を遮断します。
これらすべての方法共通で、ブラウザのアドレス欄にはLet's Encryptに設定したドメイン名を入力してローカルのサーバーに到達させる必要があります。それには、内部的なDNSを作って外部に出る前に解決させるか、利用するPCすべてのhostsファイルを編集します。
3の方法では、テスト期間中ローカルマシンをインターネットに公開してしまえば、DDNSに登録したドメインでそのまま開発サーバーにアクセスすることができます。ただ、セキュリティ的な問題があったり、インターネット経由の非効率なアクセスとなります。
今回は、この2つ目の方法で実施してみました。
OSはDebian10の場合で例示していますが、Certbotが使えれば同様のことができると思います。
証明書の取得
まず、DNSまたはAレコードに、テスト環境に利用したいドメインと、IPアドレスを登録します。反映に時間がかかるので早めにやっておきましょう。
ここでは証明書を発行する対象はサブドメイン「lets.somesite.jp」と想定しています。
A lets.somesite.jp 12.345.678.9
次に、グローバルな固定IPが割り振られていてシェルの使えるマシンにCertbotをインストールします。特に設定は不要です。
Certbotが一時的に80番と443番ポートを使うので、使われていないかチェックします。使われているようなら、systemctlやpkillコマンドで一時的に止めてください。
# どちらかで何か表示されたら使用中 lsof -i:80 lsof -i:443
使われていた場合の例 apache2 19399 ... TCP *:http (LISTEN) # systemctl stop apache2 または # pkill apache2
今度は、ファイアウォールでTCPの80番と443番ポートを解放します。
重要なので、戻し方も先に紹介しておきます。
設定方法
# TCP80番ポートの許可をエントリーの1行目に入れます iptables -I INPUT 1 -p tcp --dport 80 -j ACCEPT # TCP443番ポートの許可をエントリーの2行目に入れます iptables -I INPUT 2 -p tcp --dport 443 -j ACCEPT
戻す方法
念のためiptables -L --line-numbersでエントリー番号を表示します。 # iptables -L --line-numbers 1 ACCEPT ... tcp dpt:http 2 ACCEPT ... tcp dpt:https INPUTのエントリーの2番目を消した後に、1番目を消します。先に1を消した場合は番号が繰り上がりますので次も1を消すことになります。 # iptables -D INPUT 2 # iptables -D INPUT 1
そして、証明書をCertbotで取得します。
--standaloneオプションにより一時的に80番と443番のポートで仮のWebサーバーが待機され、その存在が確認されたら処理は終了します。
確認がとれた場合は、次のようなメッセージとともに各種証明書がマシン内に作成されます。
IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/lets.somesite.jp/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/lets.somesite.jp/privkey.pem Your cert will expire on 2022-01-07. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by:
証明書が作成されたら、前述の通りファイアウォールを元に戻して、80番や443番で待機するプログラムがあれば再起動してください。
証明書や秘密鍵ファイルは/etc/letsencrypt/live/lets.somesite.jp/ディレクトリに入っています。
- chain.pem
中間証明書です。
- cert.pem
自身のサーバーの証明書です。
- fullchain.pem
中間証明書と証明書がまとまったものです。今回は利用しません。
- privkey.pem
自身のサーバー用の秘密鍵です。厳重な管理が必要です。
出力されたcert.pem、chain.pem、privkey.pemをローカル環境にコピーします。権限についてはいろいろ考えがあると思いますが、筆者はcert.pem、chain.pemをrootをオーナーにした644、privkey.pemをrootをオーナーにした600にしました。
配置場所もこれにしないといけないというわけではありませんが、証明書が/etc/ssl/certs/、秘密鍵が/etc/ssl/private/にしました。
/etc/apache2/sites-enabled/default-ssl.confに中間CA証明書、証明書と秘密鍵の設定をします。
default-ssl.conf
... SSLCertificateFile /etc/ssl/certs/cert.pem SSLCertificateKeyFile /etc/ssl/private/privkey.pem ... SSLCertificateChainFile /etc/ssl/certs/chain.pem ...
バージョン2.4.8以降のApacheでは、SSLCertificateChainFile は非推奨となっています。中間証明書は、自身の証明書とあわせて、SSLCertificateFile へ設定します。
その方法ですが、pem 形式の証明書はテキスト形式になっていますので BEGIN ~ END までをそのままテキストエディタで連結することができます。その際の順序は、自身の証明書、中間証明書、になります。
公開用のApacheを稼働させている場合
Webサーバーの公開が目的の場合は80や443ポートは開いていると思いますが、この場合はポートを閉じるためにサービスを止める必要は無く、別のプラグインをインストールすることでそのまま証明書を発行・利用できます。
まずプラグインをインストールします。
# apt install python3-certbot-apche
次に、certbotを--apache オプションで起動します。必要な項目(ドメインとメールアドレス)を対話的に聞いてきますので入力します。
複数サイトを管理する場合は、-d オプションで ドメインを指定します。
# certbot --apache ( -d 2nd.marune205.net ) ... Enter email address: ... # 規約の確認 Please read the Terms of Service at... Y # メール送信の可否 Would you be willing... Y # ドメインの入力 Please enter the domain name(s)... marune205.net
ドメインの入力がおわり、証明書が発行されると、/etc/apache2/sites-available/000-default-le-ssl.conf にApache用の設定が出力されます。
このファイルには、先ほどの対話で入力したサーバー名、出力された公開鍵のフルチェーンへのパスと、秘密鍵へのパス、/etc/letsencrypt/optons-ssl-apache.conf のインクルードなどが設定されています。
この設定は、apache で有効になっていると思いますが、無効にしたい場合は次のようにします。
; 設定を無効に # a2dissite 000-default-le-ssl ; システムを再起動 # sytemctl restart apache2.service ; 再度有効にしたい場合は ensiteを # a2densite 000-default-le-ssl ; システムを再起動 # sytemctl restart apache2.service
通常のapacheのssl設定ファイルは、default-ssl.conf となると思いますが、Let's Encryptの設定は 000-default-le-ssl.conf となりますので注意してください。後者に設定を統合し、default-ssl は設定をOFFにしておきましょう。また、SSL モジュールが有効化されていない場合は、「 a2enmod ssl 」として有効にします。
ローカルテスト環境の構築
ローカルに作成したサイトには証明書を取得したドメインである「 lets.somesite.jp 」のアドレスを使って接続しないといけません。それをそのままブラウザへ入力すると外部のDNSにアドレス解決に行ってしまうので、プライベートなDNSを作ったり、hostsファイルに記述をして、ドメインからアドレスの変換をします。
hostsファイルはテキストファイルなので、テキストエディタで編集が可能です。WindowsではC:\Windows\System32\drivers\etc に存在します。Deibanでは /etc/hotsts にあります。
hosts
... # IPアドレス ドメイン の形式で記述します 192.168.1.200 lets.somesite.jp ...
証明書の失効
Let's Encryptは証明書の期間も短いうえに、今回はテストサーバなのであまりその必要がないかもしれませんが、有効期間中に失効させたい場合は次のようにします。
もともと設定したマシンでなければ失効させられないようでしたが、この場合は80番や443番のポートを解放しなくても問題ありませんでした。(ステートフルインスペクションが働いたのかもしれませんが)
Apacheサーバーでこの失効を確認するには、OCSPサーバに失効を問い合わせる設定をしなければいけません。その際用いるのがOCSP staplingです。
「How To Configure OCSP Stapling on Apache and Nginx」にその方法がかかれていました。
/etc/apache2/conf-availableディレクトリに「ssl-stapling-cache.config」という名前でファイルを作成して次のように記述します。
ssl-stapling-cache.config
SSLUseStapling On SSLStaplingCache shmcb:/tmp/stapling_cache(128000)
参考サイトでは「SSLUseStapling On」の部分はdefault-ssl.confに書いていますが、一緒にしても問題ありませんでした。ただし、SSLStaplingCacheのエントリーは<VirtualHost>の中に書けないそうです。
保存後a2enconfコマンドでこれを有効にします。
有効化 # a2enconf ssl-stapling-cache.config 無効化 # a2disconf ssl-stapling-cache.config
IPアドレスでアクセスしたい
IPアドレスを指定したアクセスではどうしてもLet's Encryptを利用できないので、発想を転換し、プライベートCAの運用の手間を減らすことを考えてみました。
開発サーバーをそのままCAとすればいくつも証明書を発行する手間は省けます。
まず、Google Chromeのセキュリティ要件を満たすために、署名バージョン3のオプションファイルをつくります。
option.v3
basicConstraints=CA:TRUE subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer keyUsage= nonRepudiation, digitalSignature, keyEncipherment, keyCertSign, cRLSign subjectAltName=IP:[サーバーのIPアドレス]
次に証明書を作ります。コマンドは2行、ファイルは先のオプションファイルを含め4種なので管理の手間はかなり省けると思います。
# openssl req -newkey rsa:2048 -nodes -keyout server-and-rootca.key -out server-and-rootca.csr # openssl x509 -req -days 365 -in server-and-rootca.csr -signkey server-and-rootca.key -out server-and-rootca.crt -extfile option.v3
このコマンドで出力された、server-and-rootca.crt がサーバー兼ルートCAの証明書となります。これを、クライアントのルート証明書に登録することで警告なしにブラウザからSSL接続できます。
apacheの SSLCertificateFile と SSLCertificateKeyFile もこのファイルに切り替えます。
証明書の配布時は、警告のダイアログがでますが、ユーザー権限のコマンドプロンプトまたは、Power Shellで設定可能です。
コマンドプロンプト
> certutil -user -addstore "ROOT" 証明書へのパス.crtPower Shell
> Import-Certificate -FilePath 証明書へのパス.crt -CertStoreLocation Cert:\CurrentUser\RootLet's Encryptの証明書でCAを運用したらどうなるか
もっと便利に使えないかと、Let's Encryptから得た証明書でプライベートCAを作ってみたらどうなるか試してみました。
まず、管理しやすいように取得した証明書や秘密鍵を/var/letscaにコピーしました。(cert.pem、privkey.pem、chain.pem、fullchain.pem)
そして同じディレクトリにVersion3用の拡張データを作成します。名前は何でもいいのですが、server.v3としました。
server.v3
basicConstraints=CA:FALSE subjectKeyIdentifier=hash authorityKeyIdentifier=keyid,issuer keyUsage= nonRepudiation, digitalSignature, keyEncipherment subjectAltName=IP:192.168.0.1
CAの準備はこれだけです。次にCAで、Webサーバー(test-web)の証明書を発行してみたいと思います。
Webサーバー用の秘密鍵の生成 # openssl genrsa -aes256 -out server.key 2048 Webサーバー用の秘密鍵のパスワードを除去します。生成時に設定したパスワードを入力してください。 # openssl rsa -in server.key -out server.key Webサーバー用の署名要求を作成 # openssl req -new -key server.key -out server.csr CAでWebサーバーの証明書を生成 # openssl x509 -req -in server.csr -CA /var/letsca/cert.pem -CAkey /var/letsca/privkey.pem -days 730 -out server.crt -CAcreateserial -extfile /var/letsca/server.v3
結果は次のように、R3からの証明書がCA用ではないので、エラーとなります(証明書のチェーンとして有効ではなくブラウザで警告が出ます)。Lets's EncryptでCA用の証明書を発行してくれるならこの方法で可能になると思いますが、stack overflowにあるように、CA用の証明書の発行はできないようです。