インフラもセキュリティも,まだまだ未熟な私ではありますが,これだけはお願いします.
オリジンサーバを暗号化なしの HTTP で運用しないでください.
TL;DR
ここで,オリジンサーバは,ファイアウォールやゲートウェイを通った先の最も奥にある,最終的にリクエストを処理するサーバをいいます.アプリケーションサーバが当てはまることが多いですが,静的ファイルサーバも例外ではありません.
また,この記事では暗号化なしの HTTP を HTTP , TLS レイヤ上の HTTP を HTTPS として記述します.HTTPS における TLS 上での通信も HTTP ではあるため,差別化のために明記しておきます.
何がだめなのか
近年, Web サイトのほとんどが TLS を用いた HTTPS で運用されています.パブリックな静的コンテンツに対しては HTTPS 不要論を唱える人々もいます.
TLS を用いると End to End (E2E) の暗号化を実現することができますが,それだけでなく,署名によってレスポンスの発信元を検証することもできます.確かに,パブリックなコンテンツで E2E 暗号化を行う必要はないかもしれませんが,データの改竄が行われてはいけないはずです.
従って,私は常時 HTTPS に賛成派です.
さて,そんな中でご自身のサイト・サービスを HTTPS に対応させている方はもちろん多いでしょう.今では Let's Encrypt などのサービスによって個人でも用意に(メジャーなブラウザで信頼された)サーバ証明書を手に入れることができます.数年前と比べると HTTPS を導入する敷居は確実に下がっているはずです.
しかし,オリジンサーバはどうでしょうか?アプリケーションサーバやそのフレームワークには HTTPS 対応がなされていないものもまだ多くあります.そういった場合,また対応している場合でも,以下のような設定を書いている,書いたことのある方は多いのではないでしょうか:
http {
server {
listen [::]:443 ssl http2;
server_name: app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
}
}
}
<VirtualHost *:443>
ServerName app.example.com
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/app.example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/app.example.com/privkey.pem
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
</VirtualHost>
みたいな.
こうした構成をした場合,たしかにクライアントからは HTTPS 対応ができているように見えます.
でも, nginx や Apache といったフロントエンドサーバ,ファイアウォールなどからオリジンサーバまでは暗号化されず,署名の検証もされないのです.
なぜダメなのか
同じことを何度も言う形になってしまうのですがもう少し詳しく書きます.
データが暗号化されない
データはインターネットの間だけ暗号化されればいいわけではありません.確かに,インターネットはとても広いスコープであり,危険に晒される可能性は非常に高いです.しかし,あなたの LAN 内, VLAN 内,マシン内,コンテナ内は本当に安全でしょうか?
署名による検証がされない
あなたが通信している先にあるのは,本当に目的のオリジンサーバでしょうか.ドメイン名が正しいから, IP アドレスが正しいから,サーバ証明書が正常だから,そのオリジンサーバは正しいのでしょうか.何らかの原因によって,偽のオリジンサーバがスコープ内に発生したとしましょう.フロントエンドサーバまたはファイアウォールを通って返っていくレスポンスは, HTTP なので,データの検証はされません.クライアントは信頼されたサーバ証明書のまま,通信を行ってしまいます.
クライアントからはわからない
上記 2 つに共通することですが,これが一番重篤だと思っています.オリジンサーバとフロントエンドサーバ間が何のプロトコルで通信していようと,クライアントからは知る由もありません.もしかしたら,インターネットの海を旅した後にオリジンサーバに到達しているかもしれません.クライアントは,フロントエンドサーバのサーブするデータを信頼するしかないのです.
じゃあどうすればいいの
HTTPS でリレーする
簡単な話です.オリジンサーバとフロントエンドサーバの間のすべての通信を HTTPS で行えばいいのです.ブラウザに信頼される証明書である必要はありません.独自 CA でもいいでしょう.はたまた自己署名証明書でもまあいいと思います.ただし,フィンガープリントを設定するなどして本物のオリジンサーバかを検証することは忘れないようにしてください.
E2E 暗号化通信を行う
実は,すぐ上の方法は E2E 暗号化を実現できていません.リレーしているのですから.「大好きなあの子に,このチョコ渡してくれない?」と言って託した友人は信頼できますか?あなたが書いた秘密のメッセージを読んでいるかも,はたまた,勝手に食べてしまっているかもしれません.
すなわち,ペイロードを読まないままオリジンサーバまで届ければいいのです.HTTP では,複数ホストを共存する際に, ServerName で指定したものと Host ヘッダを照らし合わせてルーティングしていました.これでは,ペイロードを読む必要が出てしまいます.幸い, TLS には SNI (Server Name Indication) という,まるで Host ヘッダのような仕組みがあります.これは暗号化されていないため,ペイロードを読むことなく適切なオリジンサーバにルーティングできます.
SNI でどのサイトにアクセスしようとしたか分かるのではないかって?その通りです.まあ DNS で名前解決している時点でバレてますが. SNI も暗号化してしまう Encrypted SNI といったものも将来的に実現される可能性は高そうです.
TL;DR
- オリジンサーバとフロントエンドサーバ間を HTTP で通信するな
- 自己署名証明書でもいいのでちゃんと設定してちゃんと検証しよう
- そもそもペイロードを読まずに TLS のままリレーしよう
Edge→Originを流れるトラフィックの性質による気もしました。センシティブな情報はHTTPS化したほうがいいとは思います。
TLSは計算処理にマシンリソースを消費するのでインフラ面のコストは増加しそうだなと思いました。
@tomoyk
記事でも触れている通り,たとえパブリックな情報でも,改竄の可能性をへらすには HTTPS 化が有効だと考えています.また計算リソースを消費する問題については,記事でも触れている通りヘッダ上の SNI を読んでペイロードを読まずにルーティングすれば大差ないと思っています.
@Siketyan
書かれている通り、ネットワーク経路上での改ざんはTLSで保護すべきだと思います👍
Edge→Originのネットワークを信用するか、ゼロトラスト的に信用しないかによるのかなとも思いました。
Client→EdgeとEdge→Originで別のTLS接続を確立するイメージでしたがトランスペアレントにやるということですね。たしかにSNIを見ればリソース消費は抑えられそうな気がしました🙋♂️
@tomoyk
あまり詳しく調べていないのでワードを使うのは控えましたが,私の思想としてはかなりゼロトラストに近いと思います.マシン内でも,コンテナ内でも,極限まで小さいスコープまでゼロトラストしたいなって思っています(もちろんデータの性質やリソースそのトレードオフですが).
@Siketyan
興味深い記事ありがとうございます!
浅学で完全には理解できていいないので質問なのですが、例えばAWSで以下のケースの場合にhttpsにするメリットは何がありますか?
@tosiooooooo
コメントありがとうございます.
ALB と EC2 間を VPC で結んでいたとしても,脅威が入り込む隙間はまだまだあると考えています.となると,その間で暗号化及び署名の検証を行う必要があるのではないかと思います.
データベースへの接続はどうなんだろ。
こちらも常時TLS接続必須派ですか?
@bluegoldme
しないに越したことはないと思っています.
うーん。
マシン/コンテナが信用できないならTLSにすれば安心にもならないでしょ。
できればTLSの方が良いとは思うけど、根拠が不足かな。
あと、途中で読むなってなら
/service1
と/service2
で別コンテナに投げるとかできたっけ?一方LANが信用できるかと言われれば微妙なライン。まぁアウトかな。
LAN内でもHTTPSにしとけば間違いなくリスクは減る。
個人的には宅内LANまでHTTPS強制はちょっと鬱陶しいと思う。
最近nextcloudのAndroidアプリを宅内サーバーに接続しようと思ったら、httpから始まるアドレスは入力できず残念だった。
オリジンサーバ -> データベースへの通信の TLS 化は、開発言語が PHP (で PDO を使っている)場合が逃げ道がなく断念する場合が多いです。
RDS は比較的オーバーヘッドが少ないですが、Azure Database だと無視できない時間かかるので。
E2Eの暗号化は難しそうだなと感じました。
よくある例ですが、パスベースでオリジンを振り分ける場合、どうしても中段のApacheやnginxで一度TLS終端させる必要があります。
すべてのサーバ、すべてのプロセスに証明書の秘密鍵を展開しなければいけないとなると、攻撃から守るポイントが多くなってしまうのも辛いところです。
@Siketyan
とありますが、具体的にどのような脅威が考えられますか?
VPC内にEC2インスタンスが作られる可能性でしょうか。
とありますが、優先順位の高い対策は他にもっとあると思います。
改ざんや流出のあったサーバでは他の理由によるインシデントが多いです。
エッジ、オリジン間でTLS通信を行わなかったことが直接の原因となっているインシデントがあったのでしょうか?
ゼロトラストの考え方に基づきそういった構築を行うことはよいことだとは思いますが、現時点でそれ以外の選択肢をとってはいけないとは言えないと思います。
記事中でNginxのリバースプロキシでlocalhostに接続している例を書かれていますが、localhostの場合も暗号化を考慮すべきなのでしょうか?
localhostの通信を改ざんなどできるところに不審なものがいたら、HTTPS通信にしたところで正規の証明書を奪って好きにできちゃう気がしましたがいかがでしょうか?
(内容を否定したいわけではなく、考え方を知りたいです)
E2Eの暗号化が理想的というのはわかるのですが、それができないからNginxでリバースプロキシするのかなぁと思いました。
TLSで使われているRSA暗号ってすごい重たい処理なんですよ(アルゴリズム知ってればわかると思います)
脅威への対策として暗号化すべきというが
それをするとレスポンスタイムが増大しますし、サーバーへの負荷も増大します
ですのでプライベートネットワーク内でのTLS化はレスポンスタイムとのトレードオフとして考えるべきかと(個人的には可能性がある程度の脅威ならTLS化すべきではないと思います)