[AWS] CloudFrontからのリクエスト以外をオリジンで拒否する

このブログはCloudFrontからLightsailで作成したオリジンを参照する構成になっているのですが、最近オリジンに直接リクエストが来ることが増えました。間違えてオリジンのURLを返している可能性もあったのでログを解析してたのですが、大方の予想通りセキュリティホールを探っている物ばかりでした。

めちゃめちゃ気持ち悪い(´・ω・`) オリジンへのリクエストはCloudFrontからしか来ないと割り切って、80/443番へ直接来るリクエストはすべて拒否することにしました。

ここではApacheの設定を掲載しますが、同様の手法でnginxなど他のWebサーバーでも利用できると思います。

原理

動作原理は非常にシンプルです。

CloudFrontからオリジンへリクエストを投げる際に、秘密の独自HTTPヘッダーを付与します。オリジン側ではこの独自HTTPヘッダーが付いていればCloudFrontからのリクエストと判断。もし付いていなければ403を返して拒否します。

CloudFrontでは「Origin Custom Headers」という設定項目で自由にヘッダーを付けることができるのでこれを利用します。

CloudFront側の設定

Origin Custom Headersには今回X-Cdn-Secretという独自のHTTPヘッダーを追加し、値にはランダムな文字列を16桁指定しました。ヘッダー名も値も推測されにくければ何でもかまいません。これで設定を反映したらステータスがDeployedになるまで待ちます。

ちなみに独自ヘッダーの先頭にX-を付けるやり方は10年ほど前までは頻繁に利用されていましたが、最近では推奨されないようです。

独自のヘッダーは、以前は X- 接頭辞を使用していましたが、この慣習は 2012 年 6 月に非推奨になりました。これは、 RFC 6648 で非標準のフィールドが標準になったときに発生した不便さのためです。

※MDN - HTTPヘッダーより

……ですが、私が勝手に命名したこのフィールドが標準仕様になることは考えにくいのでこのまま行きます。

オリジン側の設定

運用中のサーバの場合、いきなりオリジン側で拒否するのではなくCloudFrontから値が渡ってきているか検証してから拒否する設定を入れます。

Webサーバのログに出力

httpd.confを開きLogFormatを新たに追加します。ここではcombined2という名前で定義しました。安直w

$ sudo vi /etc/httpd/conf/httpd.conf
<IfModule log_config_module>
  # 中略
  LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{X-Cdn-Secret}i\"" combined2

今回はVirtualHostで管理していたサーバでしたので、CustomLogに先ほど新規に追加したLogFormatを指定します。

$ sudo vi /etc/httpd/conf.d/vhost.conf
<VirtualHost *:80>
  ServerName   origin.example.com
  CustomLog    logs/origin.example.com-access_log combined2

あとはApacheを再起動などして設定を反映します。

$ sudo systemctl restart httpd

しばらくしてApacheのログを確認し、CloudFrontからトークンが渡ってきていれば次のステップへ進みます。

$ sudo grep 'BIIklPy5qQmgE7ol' /var/log/httpd/origin.example.com-access_log
64.252.110.127 - - [04/Nov/2020:18:17:21 +0900] "GET / HTTP/1.1" 200 113612 "-" "Amazon CloudFront" "BIIklPy5qQmgE7ol"

Webサーバで拒否設定

拒否設定は非常に簡単で、以下の例で言う10〜13行目がそれにあたります。

<VirtualHost *:80>
  ServerName   origin.example.com
  DocumentRoot /web/public
  CustomLog    logs/origin.example.com-access_log combined2
  ErrorLog     logs/origin.example.com-error_log
  <Directory /web/public>
    Require all granted
    AllowOverride All

    SetEnvIf X-Cdn-Secret "^BIIklPy5qQmgE7ol" CDNValid
    Order deny,allow
    Deny from all
    Allow from env=CDNValid
  </Directory>
</VirtualHost>

あとは再起動などして設定を反映します。

$ sudo systemctl restart httpd

効果検証

半日〜1日程度経過したら、アクセスログからトークンが含まれていないログを抽出し、403で正常に拒否できているか確認してみます。

$ sudo grep -v 'BIIklPy5qQmgE7ol' /var/log/httpd/origin.example.com-access_log
159.203.128.47 - - [04/Nov/2020:14:05:28 +0900] "GET / HTTP/1.0" 403 4890 "-" "masscan/1.0 (https://github.com/robertdavidgraham/masscan)" "-"
114.222.156.160 - - [04/Nov/2020:15:03:42 +0900] "GET /phpmyadmin/ HTTP/1.1" 403 199 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, li

バッチリですね👍