Qualys SSL Labs テストを A+ にする記事はよく見ますが、400 点満点にする記事はあんまり見かけないので、個人用メモも兼ねて書いていきます。
試した環境
- さくら VPS の最小プラン
- OS はデフォから CentOS Atomic Host v7.4.1708 へと差し替え
- Docker Compose をインストールしておく
- 独自ドメインが VPS と紐づいてる前提 (ここでは仮に example.com とします)
-
Rapid-SSL ワイルドカード証明書を使う
- Docker + jwilder/nginx-proxy を使って、foo.example.com とかサブドメイン生やして、一台の VPS で複数のスタートアップなWebサービスを運用する想定
準備
-
DH key exchange を 4096bit で作成します。
これが必要になるのは中盤になってからですが、CPU が弱いと 1KB も行かないファイルたった一つに アホみたいな処理時間を要する ので、先にバックグラウンドで動かして、あとは放置して進めましょう。$ openssl dhparam -out dhparam.pem 4096
-
CSR 作成のために、RSA 4096bit の秘密鍵を作ります。
途中パスワードを求められますが、すぐ捨てるので雑なやつで構いません。$ openssl genrsa -aes256 -out example.com.p.key 4096 Enter pass phrase for example.com.p.key: パスワードを作る Verifying - Enter pass phrase for example.com.p.key: パスワードを再入力 $ openssl rsa -in example.com.p.key -out example.com.key Enter pass phrase for example.com.p.key: パスワードを再入力 ここまででパスワードは忘れてよい $ rm example.com.p.key
秘密鍵は RSA 4096bit にします。2048bit でも問題なく作れますが、減点されます。
ECDSA 好きとか 8Kbit 好きは、堅けりゃいいってもんじゃないことを肝に銘じておいてください!
(Rapid-SSL で試したところ、いずれも怒られました) -
秘密鍵ができたら、次は提出用の CSR を作成します。
$ openssl req -new -key example.com.key -out example.com.csr Country Name (2 letter code) []:JP <- 国記号 State or Province Name (full name) []:Tokyo <- 都道府県名 Locality Name (eg, city) []:Chiyoda-ku <- 市区郡名 Organization Name (eg, company) []:Personal <- 法人名、個人なら Personal とか Organizational Unit Name (eg, section) []:SSL <- 部署名、なければ適当に入れる Common Name (eg, fully qualified host name) []:*.example.com <- 取りたい証明書のドメイン Email Address []:info@example.com <- メールアドレス A challenge password []: <- 何も入れずに Enter
-
Rapid-SSL へ CSR を提出し、メールアドレス認証と決済を済ませます。
その後審査待ちとなりますが、大晦日のコミケ中に申請したところ 3 時間以内に発行されたので、すぐできると思います。 -
発行された証明書と中間証明書を連結して、
example.com.crt
を作成します。
連結はテキストエディタ使うなり、リダイレクトで追記するなりで OK です。-----BEGIN CERTIFICATE----- MII...(SSLサーバ証明書 X.509)...= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MII...(中間証明書 X.509)...= -----END CERTIFICATE-----
-
ルート証明書を用意します。
Rapid-SSL の場合、この辺からダウンロードして、ファイル名をexample.com.chain.crt
に変更します。 -
この時点で 5 つのファイルが揃いました。
jwilder/nginx-proxy さんによしなにしてもらうために、今一度ファイル名と拡張子を確認しておきます。
dhparam.pem
がまだできていませんか?今から一睡でもすれば、きっと季節外れのサンタさんが用意してくれるでしょう。dhparam.pem
example.com.chain.crt
example.com.crt
example.com.csr
example.com.key
-
サーバーにアップロードして、適当なフォルダに配置します。
とりあえず今回は、例として下記のように置きました。-
/opt/example.com/
-
certs/
example.com.chain.crt
example.com.crt
example.com.csr
example.com.key
-
dhparam/
dhparam.pem
-
-
実装
-
docker-compose.yml
を作ります。version: '2' services: proxy: image: jwilder/nginx-proxy:alpine container_name: example-proxy restart: always ports: - '80:80' - '443:443' volumes: - '/opt/example.com/certs:/etc/nginx/certs' - '/opt/example.com/dhparam:/etc/nginx/dhparam' - '/var/run/docker.sock:/tmp/docker.sock:ro' root: image: 'nginx:alpine' container_name: example-root restart: always environment: VIRTUAL_HOST: example.com foo: image: 'httpd:alpine' container_name: example-foo restart: always environment: VIRTUAL_HOST: foo.example.com
各種証明書ファイルが jwilder/nginx-proxy コンテナと共有できていることがわかるかと思います。
-
作ったファイルをサーバーに上げて、コンテナを立ち上げてみましょう。
$ sudo docker-compose up
-
この時点でブラウザからアクセスして HTTPS で繋がることを確認できると思います。
上手くいかない場合、sudo docker-compose logs
すれば原因を追いかけやすいです。 -
では早速、Qualys SSL Labs テストを試してみましょう。
上手くいけば、375 点で A+ 判定 となるはずです。やったね!!
おしまい
おしまい といったな、あれは嘘だ
そう、私はそれが自己満だと解っていても、400 点満点じゃないと納得できないんです!
だから私はこの記事を書いたのです!!
400 点満点へ向けて改造する
-
後に
docker-compose.yml
を編集することになりますが、編集中コンテナが生きてるとややこしいことになります。
なので、まず立ち上げたコンテナをばくはしましょう。$ sudo docker-compose down
-
400 点に向かうには
jwilder/nginx-proxy
イメージに細工を加える必要があります。
下記のようにDockerfile
ファイルを作ってください。FROM jwilder/nginx-proxy:alpine RUN \ sed -i -e 's/max-age=31536000/max-age=31536000; includeSubDomains; preload/g' /app/nginx.tmpl && \ sed -i -e 's/ssl_session_tickets off/ssl_session_tickets on/g' /app/nginx.tmpl && \ sed -i -e "s/ssl_ciphers '\(.\+\)';/ssl_ciphers '\1:!aNULL!eNull:!EXPORT:!DES:!3DES:!MD5:!DSS:!AES128';/g" /app/nginx.tmpl && \ sed -i '/ssl_dhparam/a\\ssl_ecdh_curve secp384r1;' nginx.tmpl && \ sed -i '/add_header/a\ add_header Public-Key-Pins "pin-sha256=\\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\\"; pin-sha256=\\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\\"; max-age=5184000; includeSubDomains\\"";' nginx.tmpl
次の手順に向かう前に軽く解説。
- 1~2 行目については特に今更言うことはないですね。
- 3 行目は HSTS preloading の設定。
- 4 行目は Session resumption (tickets) の設定。
- 5 行目は、弱い暗号化による通信を塞いでいます。
- 6 行目は ECDH キー交換で使われる曲線名。デフォだと 256bit です。
- 7 行目は HPKP の設定です。これはもう要らん気もしますが、今回は設定してみます。
-
HPKP 用の公開鍵を作成します。
$ openssl rsa -in example.com.key -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64
…=
で終わる 44 文字の羅列がベロっと吐き出されるので、それをコピーして、Dockerfile
の 8 行目、一番目の AAAA… に上書きでペーストします。
二番目の AAAA… はそのまま放置してください。
(本当は予備の秘密鍵こさえて、そこからちゃんと生成しなきゃダメだけど、スコアに影響しないし気にしなーい) -
先ほど作った
docker-compose.yml
を編集します。version: '2' services: proxy: build: . # ↑ 差し替え (*1) container_name: example-proxy restart: always ports: - '80:80' - '443:443' volumes: - '/opt/example.com/certs:/etc/nginx/certs' - '/opt/example.com/dhparam:/etc/nginx/dhparam' - '/var/run/docker.sock:/tmp/docker.sock:ro' root: image: 'nginx:alpine' container_name: example-root restart: always environment: VIRTUAL_HOST: example.com SSL_POLICY: 'Mozilla-Modern' # ↑ 追加 (*2) foo: image: 'httpd:alpine' container_name: example-foo restart: always environment: VIRTUAL_HOST: foo.example.com SSL_POLICY: 'Mozilla-Modern' # ↑ 追加 (*2)
- (*1) これは出来合いのイメージの代わりに
Dockerfile
でカスタマイズしたやつを適用するよって意味です。 - (*2) これを入れると jwilder/nginx-proxy さんが空気読んで、古い通信規格や弱い暗号を弾いてくれます。
- (*1) これは出来合いのイメージの代わりに
-
2〜4 で作ったファイル群をサーバーに上げたら、コンテナを立ち上げます。
今度はDockerfile
をコネコネするためにビルドが必要になります。$ sudo docker-compose build $ sudo docker-compose up
-
改めてブラウザからアクセスして HTTPS で繋がることを確認できると思います。
上手くいかない場合、sudo docker-compose logs
すれば原因を追いかけやすいです。 -
今一度、Qualys SSL Labs テストを試してみましょう。
上手くいけば、400 点で A+ 判定 となるはずです。今度こそやったね!! -
HSTS Preload に登録しとくと、HTTP で アクセスしてきた人をブラウザ側で HTTPS にリダイレクトしてくれるから、もっとセキュアになるよ!
スコアを上げるデメリットとメリット
デメリット
古いブラウザ環境で死にます。
とは言っても、死ぬのは IE9 未満とか iOS6 未満とか Android 4.1 未満とか、 そういう本当にレガシーな環境ばかりなので、スコアを抑えてまで救済するメリットはほとんどないんじゃないかなと。
(2018.1.8 追記)
満点にすると、Twitter のカード表示で死にます。
暗号化スイートとして、ECDHE-RSA-AES128-GCM-SHA256
が有効である必要がありますが、この暗号化は AES128 という 256bit 未満の暗号化が含まれており、これがあると 390 点で頭打ちになります。ただ、A+ 判定であることに変わりはない上、Mozilla wiki でも執筆時点では Modern と見做されている暗号化スイートなので、これによるセキュリティの低下を気にするのは杞憂と言えるでしょう。
メリット
少なくとも、低いスコアよりはセキュアになります。
一般的には C〜B くらいは最低限上げておくべきと思います。
D ランク以下の場合は、何らかの重大な脆弱性を抱えている場合が多いです。
ただし A+、ましてや 400 点満点にするのは、ちょっと自己満足の世界に近いかもしれません。
実際、私は~~実際にこの方法で 400 点満点となった Web ページを持っています~~(※デメリットの項目参照)が、これのおかげでモテるようになったかというと、そういう経験は今の所ありません。
また当たり前の話ですが念のため。
このスコアを上げても、セキュアになるのは HTTPS(TLS) 通信だけです。 SSH とかそういう別のところに穴があるとかいう話は往々にしてあることなので、このスコアにこだわり過ぎず、また満点だからって慢心しないことが大事です。
(過去の経験だとやたら常時 SSL を推す割に、デプロイが生の FTP だったりとかあった)