コミュニティ

お願いがあります.オリジンサーバを暗号化なしの HTTP で運用しないでください.

インフラもセキュリティも,まだまだ未熟な私ではありますが,これだけはお願いします.

オリジンサーバを暗号化なしの 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 対応がなされていないものもまだ多くあります.そういった場合,また対応している場合でも,以下のような設定を書いている,書いたことのある方は多いのではないでしょうか:

nginx.conf
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;
    }
  }
}
httpd.conf
<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 でもいいでしょう.はたまた自己署名証明書でもまあいいと思います.ただし,フィンガープリントを設定するなどして本物のオリジンサーバかを検証することは忘れないようにしてください.

追記 19/11/2020 22:38:
コメントで指摘いただいた通り,この方法は通信に要する計算リソースが大きくなります.TLS 通信のセッションの数だけ暗号化・復号処理が必要になるためです.

E2E 暗号化通信を行う

実は,すぐ上の方法は E2E 暗号化を実現できていません.リレーしているのですから.「大好きなあの子に,このチョコ渡してくれない?」と言って託した友人は信頼できますか?あなたが書いた秘密のメッセージを読んでいるかも,はたまた,勝手に食べてしまっているかもしれません.

すなわち,ペイロードを読まないままオリジンサーバまで届ければいいのです.HTTP では,複数ホストを共存する際に, ServerName で指定したものと Host ヘッダを照らし合わせてルーティングしていました.これでは,ペイロードを読む必要が出てしまいます.幸い, TLS には SNI (Server Name Indication) という,まるで Host ヘッダのような仕組みがあります.これは暗号化されていないため,ペイロードを読むことなく適切なオリジンサーバにルーティングできます.

SNI でどのサイトにアクセスしようとしたか分かるのではないかって?その通りです.まあ DNS で名前解決している時点でバレてますが. SNI も暗号化してしまう Encrypted SNI といったものも将来的に実現される可能性は高そうです.

TL;DR

  • オリジンサーバとエッジサーバ間を HTTP で通信するな
  • 自己署名証明書でもいいのでちゃんと設定してちゃんと検証しよう
  • そもそもペイロードを読まずに TLS のままリレーしよう
Siketyan
Department of Information and Computer Engineering, National Institute of Technology, Toyota College
https://siketyan.dev/
sweak
様々な Web アプリケーションを開発するサークルです。
https://sweak.net
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント

Edge→Originを流れるトラフィックの性質による気もしました。センシティブな情報はHTTPS化したほうがいいとは思います。

TLSは計算処理にマシンリソースを消費するのでインフラ面のコストは増加しそうだなと思いました。

@tomoyk
記事でも触れている通り,たとえパブリックな情報でも,改竄の可能性をへらすには HTTPS 化が有効だと考えています.また計算リソースを消費する問題については,記事でも触れている通りヘッダ上の SNI を読んでペイロードを読まずにルーティングすれば大差ないと思っています.

(編集済み)

@Siketyan
書かれている通り、ネットワーク経路上での改ざんはTLSで保護すべきだと思います👍

Edge→Originのネットワークを信用するか、ゼロトラスト的に信用しないかによるのかなとも思いました。

Client→EdgeとEdge→Originで別のTLS接続を確立するイメージでしたがトランスペアレントにやるということですね。たしかにSNIを見ればリソース消費は抑えられそうな気がしました🙋‍♂️

@tomoyk
あまり詳しく調べていないのでワードを使うのは控えましたが,私の思想としてはかなりゼロトラストに近いと思います.マシン内でも,コンテナ内でも,極限まで小さいスコープまでゼロトラストしたいなって思っています(もちろんデータの性質やリソースそのトレードオフですが).

@Siketyan
興味深い記事ありがとうございます!
浅学で完全には理解できていいないので質問なのですが、例えばAWSで以下のケースの場合にhttpsにするメリットは何がありますか?

  • ALB → EC2間をhttpで通信する場合
  • ALB → EC2間をオレオレ証明書で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

ALB と EC2 間を VPC で結んでいたとしても,脅威が入り込む隙間はまだまだあると考えています.となると,その間で暗号化及び署名の検証を行う必要があるのではないかと思います.

とありますが、具体的にどのような脅威が考えられますか?
VPC内にEC2インスタンスが作られる可能性でしょうか。

オリジンサーバを暗号化なしの HTTP で運用しないでください.

とありますが、優先順位の高い対策は他にもっとあると思います。
改ざんや流出のあったサーバでは他の理由によるインシデントが多いです。
エッジ、オリジン間でTLS通信を行わなかったことが直接の原因となっているインシデントがあったのでしょうか?

ゼロトラストの考え方に基づきそういった構築を行うことはよいことだとは思いますが、現時点でそれ以外の選択肢をとってはいけないとは言えないと思います。

記事中でNginxのリバースプロキシでlocalhostに接続している例を書かれていますが、localhostの場合も暗号化を考慮すべきなのでしょうか?
localhostの通信を改ざんなどできるところに不審なものがいたら、HTTPS通信にしたところで正規の証明書を奪って好きにできちゃう気がしましたがいかがでしょうか?
(内容を否定したいわけではなく、考え方を知りたいです)

E2Eの暗号化が理想的というのはわかるのですが、それができないからNginxでリバースプロキシするのかなぁと思いました。

(編集済み)

TLSで使われているRSA暗号ってすごい重たい処理なんですよ(アルゴリズム知ってればわかると思います)
脅威への対策として暗号化すべきというが
それをするとレスポンスタイムが増大しますし、サーバーへの負荷も増大します
ですのでプライベートネットワーク内でのTLS化はレスポンスタイムとのトレードオフとして考えるべきかと(個人的には可能性がある程度の脅威ならTLS化すべきではないと思います)

@ttsuru
AWS VPC は仮想ネットワークであるため,例えばその VPC を支えるシステムにインシデントが発生し MITM が行われる……などを想定していました.あまり現実的な話ではないですが.
もっと優先順位の高い策があることについては,仰る通りだと思います.その他の策を取っていけないという思想を持っているつもりではないですが,オリジン - エッジ間を TLS で結ぶこと,またオリジンサーバを TLS 終端にすることもできるよというのを知ってほしいという意味で記事を書きました.
ご理解いただけると幸いです.


@moritalous

localhostの場合も暗号化を考慮すべきなのでしょうか?

これは一例として localhost を記述しました.スコープが大きければ大きいほど第三者が入り込む可能性は大きくなると考えていますが, localhost 間での通信でもできれば暗号化されるべきだとは考えています.

localhostの通信を改ざんなどできるところに不審なものがいたら、HTTPS通信にしたところで正規の証明書を奪って好きにできちゃう

これについては,エッジサーバでペイロードを読むのであればそこでフィンガープリントを用いた検証などは可能ではないかと思っています.またオリジンサーバをクライアントとの TLS 終端にするのであれば直接ブラウザに信頼されるサーバ証明書をデプロイすることで MITM を防止できると考えています.


@taptappun
経由する TLS 通信が増えるほど計算リソースが必要になるのは理解しています.記事にも追記しておきます.ただ,オリジンサーバまで 1 つの TLS セッションでルーティングすれば負荷はそこまでかからないのではないかと考えています(最後のほうに記述した方法).


@kurema @lighthawk
HTTP ペイロードを読まないとわからない範囲でルーティングする場合はもちろんこの方法は使えません.そういったケースが多数あることも理解していますが,セキュリティを求めるのであれば FQDN 単位でルーティングされるのが妥当かなとも思います.
記事で紹介したような策を使える最小のスコープはプロセスかと思いますが,もちろんスコープを小さくするほどデプロイメントの複雑さは上がりますし,管理しなければならない TLS 証明書の数も増えます(エッジサーバでペイロードを読む場合).まずは大きなスコープ(例えば, LAN 内)から実施していき,どのスコープで止めるかはトレードオフかなと考えています.

@Siketyan
丁寧にご回答ありがとうございます。

もう一点、別の視点で質問させてください。

どうしてもHTTPSに対応していないWebサーバー製品を使用しないとした場合、なにかいい方法はありますでしょうか?(そんな製品があるかわかりませんが)
「そんな危ない製品を使うな」が正解でしょうか

Nginxのリバースプロキシの用途は、「理想的にはE2Eで暗号化したいけどできない場合」の手段と理解しております。

@moritalous
どうしてもオリジンサーバが HTTPS 対応できない場合,できるだけ小さなスコープ内に生の HTTP 通信を留めるのが最善かなと思います.例えば, LAN 内にエッジとオリジンがいたのを同じマシン内にするとか,同じコンテナにするとか,ですね.

(編集済み)

楽しく読ませていただきました、ありがとうございます。

ご主張はまさにそのとおりだと思います。いくつかの組織で同様の主張が行われている認識です。

たとえば、IPA(独立行政法人情報処理推進機構)が出している「 TLS暗号設定ガイドライン」の 7.3 委託先のサーバ(PaaS/SaaS)を利用する場合の注意点 では、クラウドのロードバランサやCDNを利用した時にTLSの終端後に通信が平文で行われる可能性があることを注意点として上げています。

もう一つの例として、Akamaiの方々がIETFに提出したドキュメント「Best practices for TLS Downgrade」も同様の主張をしています。こちらでは問題の背景や緩和策などが整理されているので読まれると良いかもしれません。(気にされている負荷についてもCPU負荷のオーバヘッドは2%と言ったことも書かれていますね)。文書内で参照されている、RFC7258 pervasive monitoring 含め大事な議論だと思っています。

@flano_yuki
こちらこそ,読んでいただきありがとうございます!
クラウドの CDN という意味では身近かつかなり危険なのが Cloudflare の Flexible モードだと認識しています.むしろこれを例示として用いたほうがよかったのかもしれません.
負荷も想像より少ないようで,少し安心しました.
二つのドキュメントについて,時間のあるときに改めて読みたいと思います.

第三者の介在がないのが分かっている経路なら平文でやり取りしても問題はないと思います。例えば「ゲートウェイになるサーバーとデータベースとアプリケーションしかいないファイアウォールで守られたセグメント」などです。
生徒が3人しかいないクラスルームでノートの切れ端にメモを書いて回している時に、他のクラスの生徒による覗き見を心配する必要がないのと同じ様に、このセグメント内での通信には第三者が立入る余地はありません。
(それが読まれてしまう環境では「ログファイル」とかの方がよっぽど危ない状態にあるでしょう)

そういう非武装地帯同士を繋ぐ連絡路は暗号化した方がよいのはその通りです。

@Siketyan

オリジンサーバまで 1 つの TLS セッションでルーティングすれば負荷はそこまでかからないのではないかと考えています(最後のほうに記述した方法).

この方法は現実的ではないように思います。この場合、クライアントはどの公開鍵を用いて暗号化するのでしょう?オリジンサーバーが秘密鍵を持たないと複号できないですよね?であれば
1. ロードバランサーが不要、オリジンサーバーと直接やりとり
2. 全てのオリジンサーバーに共通の秘密鍵を持つ → オリジンサーバーが最も数が多いので可用性が著しく低下
3. リクエスト/レスポンス2回で1回の処理
としなければ運用できなくなるように思いますし、いずれも大きなデメリットがあります。
またRSA暗号の処理の重さは相当エグいので、リクエスト/レスポンスまでの間に2回以上RSA暗号が行う場合となればサーバーコストが相当上がります。それによって得られるモノが不安の解消(脅威があるかもしれないがなくなった!!)ではメリットが小さすぎるように思うのですが...

@taptappun

バックエンドとの通信をTLS化したいという話なので、TLS session resumption を使うのは大前提なんじゃないですかね。(browser -> edge, edge -> backend の双方で)

proxy_ssl_session_reuse を正しく使えない状況だと、パフォーマンスの確認時でアウトでしょう。

正しく設定できていれば、TLS のハンドシェイクは browser -> edge の一回で済むケースが多くなるのでは。
(edge -> backend のハンドシェイクは先行する他の誰かのリクエスト時に済んでいる)

LANからのアクセスについては、まず大事なオリジンサーバをLANに置くなって気もします。
最近はDMZって言葉もあまり聞かなくなりましたよね。
この手の話はすぐプロトコル的な話になってしまいがちですが、
物理的だったり運用的な対策でできることもあるのではないのかとも考えてしまいます。
老害ですかね…。

興味深く読ませていただきました。

セキュリティ対策に関する提案をされているので、
一般的には
- 提案する対策の説明
- 対策の効果の評価(対策をしない場合の脅威の評価なども含む)
- 対策にかかるコストやデメリットの評価
を述べた上で、対策効果がコストやデメリットを上回る場合に対策の実施を提案する、という流れが良いかと思います。
私の読んだ時点では2点目と3点目の記載がかなり少なく、
特に対策にかかるコストやデメリットの評価については、
残念ながらほとんど記載がないように見受けられます。
この流れは実際のセキュリティ対策を行う際にも重要な観点になります。

また、他の方の指摘と重複しますが、是非ゼロトラストについては学ばれた方が良いと思います。
似たモチベーションから行われた先行事例の研究になるので有用だと思います。
たとえば、記事ではサーバの応答内容の改竄やなりすましについて脅威として取り扱っていますが、エッジサーバを認証する必要はないのでしょうか?
また、認可についてはどうですか?
エッジサーバが直接データベースサーバにアクセスした時、それを許可していいのでしょうか?(クライアント認証と認可制御がなければサーバは接続を拒否できません)

参考にしていただければ幸いです。

あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした