おことわり
以下を読んで「は?」って思う方々が読み進むことは推奨できません。
俺は下位互換性なんて知らねぇ! 最新に対応していないユーザーが悪いんだ!
ゆっくりしていってね!!!
モダンに行こうぜ
僕の趣味は最新に頑張ってついていくことです。
ですので自分しか見ないWebサーバーですが最新のTLSv1.3
だけに対応させて悦に浸ろうと思います。
しかし、致命的なことに猿と同じ哺乳類なのでビルドとか難しいことはできません。
apt
で手に入るバージョンだけを使って簡単に無駄にハイセキュアな自己満環境を作っていきたいと思います。
幸い現行バージョンではそれが可能です。
雛形
MozillaさんがSSL Configuration Generatorという素晴らしいものを公開してくださっています。
こちらでサーバー
とセキュリティレベル
、環境
、オプション
を選択すると自動で最適な設定を教えてくれます。
セキュリティレベルには以下の三段階があります。
Modern
スパルタ系セキュリティ
下位互換性なんて知らねぇよ!最新の俺についてこい!
Intermediate (言うまでもないが常識的にはここがオススメ)
やさしさ系セキュリティ
貴方のような古い機種を使っている人も守ってあげますわ。
Old
優しすぎてむしろスパルタ系セキュリティ
うちはー…自治を尊重してるんで…、自分の身は自分で守ってくださーい。
さて、あなたの落としたセキュリティ設定はどれですか。
当然Android 10
をお使いの皆様は、当然Modern
を選択しますね?
注意
Modern
を選ぶのは当然趣味なので構わないのですが、きちんと設定をしないとユーザーがhttp
で通信することになったりしてむしろ危険です。
TLSv1.3
かDeny
かの二択を突き付けられるような環境でのみ実装してください。
例えold
を選択したとしても、基本的にhttp <<<<< old
です。
http
での通信は猿がウッキャァァァって叫んでいるのと同様に丸見えで丸聞こえで原始的です。
それと上のセキュリティレベルの説明は誇張してあります。
例えold
でもきちんと(きちんと!)設定すれば互換性を考えた素晴らしいサイトになりますので、あくまでもこれは雛形ということです。
ていうかold
レベルでもいいから全企業サイトはHTTPSに対応してくれ…。
環境
$uname -a
Linux raspberrypi 5.4.51-v8+ #1333 SMP PREEMPT Mon Aug 10 16:58:35 BST 2020 aarch64 GNU/Linux
$nginx -v
nginx version: nginx/1.14.2
前提条件
-
certbot
を使いLet's Encrypt
で証明書取得済み - ネットワークがipv6対応済み
- 読者の皆様は、猿と同じ哺乳類の僕よりは頭が良い
設定
前置きが長くなってしまいました。
要するに、今回のお題は「modern
を打ち出して設定すればめっちゃセキュアじゃん」ってことなんですが、当然コピペじゃ動かないのでハマったことやTipsなどを記していきます。
まずは参考に僕の最終的な設定を貼っておきます。
# generated 2020-10-02, Mozilla Guideline v5.6, nginx 1.14.2, OpenSSL 1.1.1d, modern configuration
# https://ssl-config.mozilla.org/#server=nginx&version=1.14.2&config=modern&openssl=1.1.1d&guideline=5.6
# Deny IP-direct access
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
server_name _;
return 444;
}
# Standby for example.com
server {
root /var/www/example.com;
index index.php index.html;
server_name example.com;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.3-fpm.sock;
}
location ~ /\.ht {
deny all;
}
# SSL configuration
listen [::]:443 ssl http2 ipv6only=on; # managed by Certbot
listen 443 ssl http2; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_ecdh_curve secp384r1;
ssl_session_timeout 10m;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options "DENY";
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}
# Redirect http to https
server {
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name example.com;
return 404; # managed by Certbot
}
ssl_session_cache shared:SSL:10m;
ご覧の通り基本的にcertbot
による設定をそのまま使ってます。
最近はだいぶ優秀ですが、上手くいくコツは設定をいじっていない状態で走らせることです。
設定のポイントは以下で。
Strong SSL Security on nginx
前述の雛形を参考にしながら、こちらの内容をそのまんま実行していきます。
とても分かり易くまとまっていて、かつ色んなサーバーに統一的に対応しているのが素晴らしい解説です。
全て漏らさず正確に記す自信はないので、各項目の細かい解説は上記リンクを参考にするかご自身で調べてください。(丸投げ)
一応見出しをリンクにしてあります。
SSLの種類 & 暗号の種類
いきなり核心、そして革新です。
ssl_protocols TLSv1.3; # Only TLSv1.3!!
#ssl_ciphers TLSv1.3では明示しません
ssl_prefer_server_ciphers off; # TLSv1.3ではoffにします
TLSv1.3
は従来のプロトコルと違いゴリゴリっと刷新されたものですから、設定もだいぶ変わってきます。
暗号スイートは明示せずに、ssl_prefer_server_ciphers
もoff
に設定します。
違和感しかありませんがこれで良いそうです。
ちなみに、Certbotで普通に設定すればTLSv1.2
とTLSv1.3
の両方が有効になるはずです。
それでもSSL LabsでTLSv1.3
が有効になっていない人は、メインユース以外のServerブロック(ip直打ち拒否用など)にSSLの設定がないかを確認してください。
なぜかすべての設定のなかで一番低いものに引っ張られるようですので、すべての設定にTLSv1.3
を書き加えましょう。
Session resumption
思いっきり躓いたところです。
どのサイトを見ても「**ssl_session_cache
**書けばええで!」と書いてありますが、SSL labsではNo (IDs assigned but not accepted)
と言われます。
こちらのブログにあるとおり、正しい位置はserver {}
の外で、http{}
に直書きすればよいそうです。
http{}
の中というのは一瞬戸惑いますが、/etc/nginx/sites-enabled/default
は/etc/nginx/nginx.conf
内のhttp{}
内にinclude
されますので、直に書けばよいということになります。
server {
ssl_session_timeout 10m;
ssl_session_tickets off;
}
ssl_session_cache shared:SSL:10m;
こちらを設定しておくと、サーバーの負荷が軽減されます。
雛形ではキャッシュの期間が1d
(一日)になっていますが、この長さはセキュリティの高さに反比例しますので5m-10m
ぐらいが適当だろうということです。
HSTS & X-Frame-Options
どちらもセキュリティを高める設定です。
こちらの設定は一度アクセスされるとmax-age
秒の間クライアント側で保持され続けます。
要するに、いきなり以下のように設定して証明書なんかが間違っていたら2年間アクセスできなくなりますので、最初はmax-age=300
なんかにして正しく設定されていることを確認してから延ばすとよいと思います。
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options "DENY";
以前はincludeSubdomains
なんてごちゃごちゃ書いてましたがすっきりしました。
Forward Secrecy & DHE Parameters
こちらもセキュリティを高める設定です。
設定の前に以下のコマンドで暗号化キーを生成します。
なぜかシングルコアしか使えず時間がかかるのでご注意ください。(Raspi 4で5時間)
openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_ecdh_curve secp384r1;
前述のサイトでは暗号化キーを出力するパスが間違っていて(/etc/ssl/certsdhparam.pem
)エラーになるので注意ください。
OCSP Stapling
ここも何気につまずいたところです。
証明書と同ディレクトリにあるREADME
を読むと分かるのですが、certbot
で認証を行うと自動でOCSP Staplingで使うssl_trusted_certificate
用のroot_CA_cert_plus_intermediates
(ルート証明書+中間証明書)を作成してくれます。
二つのファイルを繋げて云々と試行錯誤していたのですが一発で解決しました。
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
また、設定できたかどうかを確認するコマンドとしてopenssl s_client -connect example.com:443 -tls1 -tlsextdebug -status
をよく見かけますが、本設定ではTLSv1.3
のみの対応なので、このままではOCSP response
の項目すら出てきませんのから-tls1
のオプションを削除します。
$ openssl s_client -connect example.com:443 -tlsextdebug -status
(...)
OCSP response:
======================================
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
Produced At: Oct 2 03:09:00 2020 GMT
Responses:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #別に隠匿する必要もないんですが…。
Issuer Key Hash: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Serial Number: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Cert Status: good # こうやって出てくればOK この項目名でgrepするのもアリ
(...)
一応、テストの前にサイトにアクセスするようにしてください。
割と数時間とかで登録が消えます。
徒然なるままに
以下は僕の設定について余談です。
セキュリティに関するところだけ拾い上げているつもりです。
IP直打ち拒否
ログをチェックするとめちゃくちゃ不正アクセスがあります。
大抵IP直打ちでアクセスしてきますので拒否しましょう。
nginx
はアクセスしてきたドメイン名毎に仮想のサーバーを提供できます。
それを利用して、運用サーバー(example.com)
に引っかからなかったそれ以外(IP直打ち)
のアクセスを受けるサーバーを立ててdefault_server
とし、アクセスに対し444
を返すようにします。
status 444
はnginx
の独自コードで、低負荷でアクセス拒否できます。
server {
listen 80 default_server; # このdefault_serverを複数のドメインにつけるとエラーになるので
listen [::]:80 default_server; # IP拒否のこのserverブロックにだけ付与する
listen 443 ssl http2 default_server; # SSLでも待ち受けてあげる
listen [::]:443 ssl http2 default_server; # IPv6でも
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.3; # ここにも指定しないとTLSv1.3が反映されないという落とし穴
ssl_prefer_server_ciphers off;
server_name _; # どこにもひっかからなかった、という意味
return 444; # さようならー
}
リダイレクト
リダイレクトは色々な設定がありますがcertbot
のデフォルトもそれなりにスマートなのでそのまま採用です。
ポート80へのexample.com
のアクセスをif
で場合分けし、301
とともにリダイレクトさせ、それ以外の場合は404
を返します。
server {
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name example.com;
return 404; # managed by Certbot
}
HTTP Public Key Pinning Extension
こちらの機能は廃止された機能です。
過去の安全を支えてくれ、今日のセキュリティの礎となった技術に敬意を払いながら、実装しないように無視しましょう。
HTTP/2
本設定ではHTTP/1.1の後継であるHTTP/2にも対応しています。
もう大方普及してきたかと思いますが、体感できる程の違いがあるそうなので実装しておきたいですね。
設定は簡単、listen
にhttp2
を追記するだけです。
listen [::]:443 ssl http2 ipv6only=on;
listen 443 ssl http2;
Google chromeのデベロッパツールのNetwork
タブでテーブルのヘッダーを右クリックすると表示項目を選べますので、Protocol
にチェックをつければ通信を確認できます。
h2
と表示されればHTTP/2
で通信ができています。
ちなみにビルドやらなんやらをすればHTTP/3もいけるのだとか。
手が届くところに来たら実装してみたいと思います。
SSL labs
ここまで設定すればA+
だろうと思うんですが、TLSv1.3
だけにするとTLSv1.2
がNo
となってしまいますので評価はA
です。ええぇぇぇ(激寒)
緑色のThis server supports TLS 1.3.
が気持ちいいですね。
ピンクとかでThis server supports ONLY TLS 1.3.
とか書いてくれてもいいんですが…。
追記
このサーバーではHLSでライブストリーミングを実装しているのですが、(ブラウザでの視聴は問題ないものの)MX Player
がTLSv1.3に対応していませんでした。
MX PlayerでTLSv1.3のみのストリーミングURL(.m3u8
)にアクセスするとこのURLは再生できません
と言われてしまいエラーになります。
このサーバーの目的は主にストリーミング視聴なので、ロマンか利便性か…、迷いますね。
まとめ
まず、記述漏れがあったらすみません。
なにか脆弱性や問題点、僕の頭の脆弱性などを見つけられた方はお教えください。
再度言いますが、リダイレクトと拒否の設定をきちんと行わないとかえって安全ではなくなります。
SSL Labsの評価を見ても分かる通り、正直言って実用性はあまりない設定です。
それでも自分しか使わないのだからロマン重視で行きたいという記事でした。
僅かでもお役に立てれば幸いです。