第6回 Cloudflareのセキュリティヘッダーを設定する―AstroサイトのHTTPレスポンスを見直す
CloudflareのResponse Header Transform RulesとHSTS、Always Use HTTPSを使ってbringain.comのHTTPレスポンスを見直した。Security Headersの評価がFからAに変わるまでと、HTTPが200 OKのままだったつまずきも含めて記録する。

第5回では、記事公開後にXへ流す運用をWeb Intentで半自動化した。
サイト側の設定へ戻り、CloudflareでHTTPレスポンスヘッダーを追加する。以前、Astroサイトのセキュリティヘッダーを確認した記事で、bringain.comにどのヘッダーが付いているかを確認した。今回は確認だけで終わらせず、実際にCloudflare側へ設定する。
作業内容は大きく3つある。
- Response Header Transform Rulesで4つのヘッダーを追加する
- HSTSを有効にする
- HTTPアクセスをHTTPSへリダイレクトする
途中で、HSTSまで設定したあともHTTPアクセスが200 OKのままになっていることに気付いた。Cloudflare側に別の設定が残っていたため、そこも含めて実際の作業順に残す。
設定前のレスポンスヘッダーを確認する#
最初にPowerShellからHTTPS側のレスポンスヘッダーを確認した。
curl.exe -I https://bringain.com/

この時点では、レスポンスにCloudflare由来のヘッダーは返っているものの、今回確認したかった次のヘッダーは見当たらなかった。
Strict-Transport-SecurityContent-Security-PolicyX-Frame-OptionsX-Content-Type-OptionsReferrer-PolicyPermissions-Policy
続いてSecurity Headersでbringain.comをスキャンした。

評価はF。

画面上でも6項目すべてがMissing Headersとして表示された。
ここでCSPまで一度に入れることはしなかった。ゆまラボではGoogle AnalyticsやAdSenseなど外部リソースを使っている。CSPは許可する読み込み元を細かく決めるため、値を適当に追加すると計測や広告、外部スクリプトを止める可能性がある。
CSPは保留し、それ以外を先に設定する。
Response Header Transform Rulesで4つのヘッダーを追加する#
Cloudflareでは、Response Header Transform Rulesを使って訪問者へ返すHTTPレスポンスヘッダーを追加・上書きできる。
公式ドキュメントでも、Set staticは同名ヘッダーがあれば値を置き換え、存在しなければ新しく追加する動作になっている。
ルール名はSecurity Headers。すべての受信リクエストへ適用した。
設定した値は次の4つ。
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

今回のルールでは、カメラ、マイク、位置情報の利用も許可しない設定にした。
デプロイ後、同じコマンドでもう一度確認する。
curl.exe -I https://bringain.com/

レスポンスには次の4行が追加された。
Permissions-Policy: camera=(), microphone=(), geolocation=()
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Cloudflareのルール作成画面だけでなく、実際の公開URLに対するレスポンスで確認できたので、Transform Ruleは反映されている。
HSTSはエッジ証明書から有効にする#
Strict-Transport-SecurityはTransform Ruleへ追加せず、CloudflareのHSTS設定を使った。
Cloudflareのエッジ証明書にはHSTS専用の設定があり、公式の案内でもHTTPSが正常に利用できることを確認してから有効にする手順になっている。
HSTSはこの設定で有効化した。
HSTS: 有効
Max Age: 6か月
includeSubDomains: OFF
Preload: OFF
No-Sniff Header: OFF

includeSubDomainsは有効にしていない。サブドメインまで一括でHSTS対象にすると、HTTPSに対応していないサブドメインがあった場合にアクセスできなくなるためだ。
Preloadも使わない。Cloudflareの案内では、Preloadを使う場合はより長いMax Ageなどの前提がある。
No-Sniff HeaderはOFF。X-Content-Type-Options: nosniffはTransform Ruleで追加済みなので、別設定との重複を避けた。
保存後にHTTPS側を確認すると、今度はHSTSも返るようになった。

Strict-Transport-Security: max-age=15552000
15552000秒は、Cloudflareで選択した6か月の設定として返っている。
Security Headersの評価はFからAへ変わった#
ここでSecurity Headersをもう一度実行した。

評価はFからAへ変わった。
設定前に不足していた6項目のうち、次の5つは緑の判定になっている。
X-Frame-OptionsStrict-Transport-SecurityPermissions-PolicyReferrer-PolicyX-Content-Type-Options
残ったのはCSPだけ。

ここは無理に埋めない。
CSPは外部スクリプトや画像、フレームなどの読み込み元を制御する。現在のゆまラボで使っているGoogle AnalyticsやAdSense、今後追加する外部サービスまで含めて許可元を確認してから設定する必要がある。
Security Headersのスコアだけを上げるために、動作確認なしでCSPを追加する方が危険なので、今回の作業範囲から外した。
HSTSを設定してもHTTPは200 OKのまま#
ここで一度つまずいた。
HSTSまで有効にしたのにHTTPが200 OKのままなのは、最初に見たとき少し違和感があった。
HTTP側のレスポンスを確認するため、次を実行していた。
curl.exe -I http://bringain.com/

結果はHTTP/1.1 200 OK。
HSTSまで有効にしたので、HTTPアクセスもHTTPSへ送られる状態になったように見えたが、Cloudflare側のHTTPレスポンスそのものはリダイレクトされていない。
HSTSとHTTP→HTTPSリダイレクトは同じ設定ではない。
HSTSはHTTPSレスポンスにStrict-Transport-Securityを付け、対応ブラウザへHTTPS利用を記憶させる。一方、HTTPリクエストへ3xxを返してHTTPSへ移動させるのは別の処理になる。
Cloudflareの設定を見直すと、常に HTTPS を使用(Always Use HTTPS)がまだ有効になっていなかった。
Always Use HTTPSを有効にする#
CloudflareのAlways Use HTTPSは、HTTPで届いたリクエストをHTTPSへリダイレクトする機能になっている。
Cloudflareの画面から常に HTTPS を使用を有効にした。

ここは、最初から設定済みだったことにはしない。
実際にはセキュリティヘッダーとHSTSを設定した後、HTTP側が200 OKのままなのを見て追加確認した設定だ。
有効化後、同じHTTP URLを再度確認した。
curl.exe -I http://bringain.com/

今度は結果が変わった。
HTTP/1.1 301 Moved Permanently
Location: https://bringain.com/
HTTPでアクセスした場合に、HTTPS側へリダイレクトされることを確認できた。
この確認を入れなければ、Security HeadersでA評価になった時点で作業完了としていた可能性がある。外部の評価結果だけでなく、実際のHTTPレスポンスをcurlで確認しておく意味があった。
最終的なヘッダー設定#
最後に、設定結果を表で確認する。
| 項目 | 設定・確認結果 |
|---|---|
| X-Content-Type-Options | nosniff |
| X-Frame-Options | SAMEORIGIN |
| Referrer-Policy | strict-origin-when-cross-origin |
| Permissions-Policy | camera=(), microphone=(), geolocation=() |
| Strict-Transport-Security | max-age=15552000 |
| Always Use HTTPS | 有効 |
| HTTPアクセス | 301 Moved PermanentlyでHTTPSへ移動 |
| Security Headers | F → A |
| Content-Security-Policy | 今回は未設定 |
今回の作業で変更したのはCloudflare側の設定で、Astroのコードには手を入れていない。
設定前は6つのヘッダーが不足し、Security HeadersではF評価だった。Response Header Transform Rulesで4つを追加し、HSTSを有効にしたところA評価まで変わった。さらにHTTPアクセスを確認すると200 OKのままだったため、残っていたAlways Use HTTPSも有効化し、最後に301 Moved PermanentlyとHTTPSへのLocationを確認した。
CSPは未設定のまま残している。ここは現在利用している外部リソースを確認したうえで、別の作業として扱う。


