こんにちは、GMOインターネット ネットワークチームの梅崎です。今回は弊社ネットワークチームの検証環境にあるWebサーバー群のために、プライベート認証局を立てた話です。※構築には easy-rsa を利用させていただきました。 作成されたファイルのパスなどはデフォルトのものを記載しております、適宜読み替えてください。※本文中の設定/コマンドはそのままコピペできなかったらごめんなさい。 (気をつけてはいますが、ワードプレスに慣れていないもので変な空白が……)
先に最終的になにをしたのかを
準備
easy-rsa をgit clone
openssl-easyrsa.cnfを編集して [ easyrsa_ca ]セクションを変更(中間認証局を作らない場合はpathlen は0でも大丈夫だと思います)
- basicConstraints = CA:true
+ basicConstraints = critical,CA:true,pathlen:1
[ easyrsa_ca ]セクションに下記追加
nameConstraints=critical,@name_constraints
下記セクションも追加※ 192.0.2.0/255.255.255.0 はご自身の環境に合わせてください
[name_constraints]
permitted;IP.1 = 192.0.2.0/255.255.255.0
permitted;DNS.1 = invalid.invalid
認証局を作る
初期化と認証局の秘密鍵や証明書作成は下記コマンドで鍵長などはお好みで、2022年現在だと4096bitもあれば大丈夫だと思います
./easyrsa init-pki
./easyrsa build-ca
できた認証局の証明書(./pki/ca.crt)をダウンロードして「信頼されたルート証明機関」にインポートすればOKです
サーバー用の証明書
サーバー用の証明書は下記コマンドで./easyrsa gen-reqは実際に秘密鍵を利用するサーバーで実行する方がおすすめです※192.0.2.1の箇所はご自身のサーバーのIPアドレス に合わせてください
./easyrsa gen-req hogehoge nopass
./easyrsa --subject-alt-name='DNS:invalid.invalid,IP:192.0.2.1' sign-req server hogehoge
後はコマンドの実行結果に出ているディレクトリに秘密鍵(./pki/private/hogehoge.key)と証明書(./pki/issued/hogehoge.crt)があるので実際に使用するだけです※Nginx等の利用の場合はサーバーの証明書へ認証局自身の証明書をcatかなにかでくっつける必要があります
以上で内部向けページも緑鍵に
目指せ「この接続ではプライバシーが保護されません」の撲滅!
本編
はじめに
昨今では常時TLS化もわりかし行われていますが社内向け環境だと自己署名証明書、いわゆオレオレ証明書を利用していることが多いのかなと思っております。
もしサーバーがグローバルIPアドレスを持っていればLet's Encrypt等の利用も考えられるのですが、プライベートIPのみの環境ですとそれもできないです。
これを解決するためにはサーバーの証明書を個別にPCに登録するか、個人的に認証局を立てそれをPCに登録するかの二択だと思います。
ただ、サーバーの証明書を個別に登録するのはサーバーが増えたり減ったりすることも考えるとかなり手間です。かといって認証局を立てるとその秘密鍵が漏洩した場合、最悪すべての通信に中間者攻撃の恐れがあるため、気軽に立てるにはかなり荷が重い仕事になってしまいます。
そんなこんなで仕方なくセキュリティエラーを無視する日々でした……
そんなある日
証明書に載せられる情報について色々調べていたある日のこと、なんの気なしにプロファイルを眺めているとhttps://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10制限するための方法が普通に書いているではありませんか!ということで、このオプションはどうやって適用できるのか、何ができてどんな挙動なのかを調べてみました。
認証局の証明書へ制限事項を追加
下記の設定を追加すれば認証局の証明書を作る際に、証明書へ制限事項の情報を追加できました。
nameConstraints=critical,@name_constraints
[name_constraints]
permitted;IP.1 = 192.0.2.0/255.255.255.0
permitted;DNS.1 = invalid.invalid
nameConstraintsというのが、この認証局が発行するサーバー証明書に含むことができるSubject Alternative Name(以降SAN)のブラック/ホワイトリストとなっております。※昨今ではブラウザはホストと証明書の組み合わせが正しいかの確認にSubject Alternative Nameを利用しています。ざっくりいうと「このIPのサーバーにしか証明書は発行できません」という感じです。
設定中のcriticalは「この項目を理解できない実装はとりあえず証明書エラー扱いにしてくれ」というフラグです。
設定中のIPやDNSは値の形式を表していて内容は見たままなのですが、存在しない 値の形式は全許可扱いになるため要注意です。(例えばDNSの制限/許可がないとデフォルトで全許可です、危ない!)
とあるブラウザの不思議な挙動
上記のような設定でいろいろな認証局とサーバー証明書を作って試したのですが、Chromeの挙動が意図したようにはなっていなかったのです。
具体的には、特定のIPアドレスのみ許可、DNS表記は不可としたく認証局の証明書に「すべてのDNS表記は不許可、IPは特定のプレフィックスのみ許可」や「この特定のDNS表記は許可、 IPは特定のプレフィックスのみ許可 」サーバーの証明書のSANはIPアドレス表記のみとした際
証明書自体は問題なしと出るのですが、Chromeの画面上だと証明書エラーとなる事象が起きてしまいました。※Firefoxだと問題なくページが表示される。
他にもいろいろ試していると、どうにもChromeはDNS表記の制限がある場合に、サーバー証明書へDNS表記のSANが無いとエラーと判断しているのではと思います。
ただ、検証環境ではIPでサーバーへアクセスできれば十分ですし、権威DNSサーバーを立てるのも面倒なので困ってしまいました。
その回避策として
DNS表記のSANが無いとエラーになるのなら ・サーバー証明書 へは適当なドメインのSANを追加 ・認証局の証明書へはそれへの許可のみをすれば実質IPだけの運用ができるのではと考えました。
とは言うのものの、勝手に変なドメイン名を利用するのも趣味ではなく、いい感じのTLDが無いか調べてみるとhttps://datatracker.ietf.org/doc/html/rfc6761#section-6.4名前解決を失敗するためのTLDがあり、自由に使っていいとも書いていたのでこいつを使っちゃおうということで
認証局の証明書はIPアドレスと「DNS invalid.invalid 」を併記でサーバー証明書にもとりあえずSANへ 「DNS invalid.invalid 」 を入れておこうということで落ち着きました。
まとめ
まとめると下記を書けば大丈夫です。
nameConstraints=critical,@name_constraints
[name_constraints]
permitted;IP.1 = 192.0.2.0/255.255.255.0
permitted;DNS.1 = invalid.invalid
(実はちょっぴり不安なので、やっていること間違えていればご教授ください。特にセキュリティ的にあかんことになっていると最悪なので!)
最後に
これにてプライベートな検証環境にも鍵マークが現れました?
今後はLet's EncryptのようにACMEに対応したいなーという野望を……!ACMEに対応できれば構築中のおうちk8sの証明書の管理がはかどりそうですしACMEの認証局側の実装はOSSとして公開してくれているようなので、今後は検証環境への導入を頑張るぞい
ということで、以上「社内検証環境用にプライベート認証局立ててみた」でした。ここまで読んでいただきありがとうございました。
付録
試した証明書の組み合わせと結果を下に貼っておきます。
・縦が認証局の証明書のnameConstraints ・横がサーバー証明書のSANです。
10.0.12.36でアクセスした場合
test.example.comでアクセスした場合