昨日の続き。
HTTP ObservatoryでRedmineサイトをB-→B+に改善しましたが、
筆者環境の
- Apache 2.4によるリバースプロキシ
- Growi v7.4.7
環境のHTTP診断もそのぐらいであろうと思ったらまさかのF判定を食らいました。
Fランクの内訳
最初の診断結果では、以下の項目で派手に減点を食らっていました。
- CSP (Content Security Policy):
- 未設定 (-25)
- Cookies:
- Secure属性なし (-20)
- HSTS: 認識されず
- (-20)
- X-Frame-Options / X-Content-Type-Options:
- 認識されず (-25)
特に謎だったのが、「Apache側でHeader設定を入れているはずなのに、認識されない(Failed)」という点でした。
アプリとApacheの二重出力。
原因を調査するため、curl -I コマンドで生のレスポンスヘッダーを確認しました。
curl -I https://growi-site
Strict-Transport-Security: max-age=63072000
Strict-Transport-Security: max-age=15552000; includeSubDomains # <-- 2重に出ている
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff # <-- 2重に出ている
Set-Cookie: connect.sid=...; Path=/; HttpOnly # <-- Secure属性がない
原因の分析:
リバースプロキシ構成では、「バックエンド(アプリ側)が吐き出すヘッダー」と「Apacheが追加しようとするヘッダー」が両方ブラウザに届いてしまい、重複が発生。
診断ツール側が「どの設定を信頼すべきか判断できない」としてエラーになっていたのです。
これを回避するための手段が以下の通りです。
さっくりとした手順
- Apacheの設定ファイルを書き換えます。
- 設定を反映します。
- 設定反映を確認します。
Apacheの設定ファイルを書き換え
- 設定ファイルのバックアップ
sudo cp -pi /etc/apache2/sites-available/growi-site.conf /path/to/backup/growi-site.conf.$(date +%Y%m%d)
.confの名前やバックアップディレクトリは自分の環境に合わせます。
- 設定ファイルのバックアップ確認
diff -u /path/to/backup/growi-site.conf.$(date +%Y%m%d) /etc/apache2/sites-available/growi-site.conf
エラーがないことを確認します。
/etc/apache2/sites-available/growi-site.confを以下のように修正していきます。(要管理者権限)
- 修正前
Header always set Strict-Transport-Security "max-age=63072000"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-XSS-Protection "1; mode=block"
- 修正後
# 重複を防ぐため、通常のレスポンスとProxyレスポンスの両方から一旦削除
Header unset Strict-Transport-Security
Header always unset Strict-Transport-Security
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header unset X-Content-Type-Options
Header always unset X-Content-Type-Options
Header always set X-Content-Type-Options "nosniff"
Header unset X-Frame-Options
Header always unset X-Frame-Options
Header always set X-Frame-Options "SAMEORIGIN"
# 足りなかった重要ヘッダーを新規追加
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# CSP: アプリの動作を維持しつつ、安全性を高める(object-src 'none' を追加)
Header always unset Content-Security-Policy
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';"
# CookieのSecure属性をApache側で強制付与
Header edit Set-Cookie ^(.-)$ "$1; Secure; SameSite=Lax"
この「2重出力」を解消し、かつアプリ側で足りない属性を付与するために、Apacheの設定を「既存ヘッダーの完全掃除 + 強制セット」という戦略に変更しました。
- 修正後の差分確認
diff -u /path/to/backup/growi-site.conf.$(date +%Y%m%d) /etc/apache2/sites-available/growi-site.conf
以下のような差分を確認します。
- Header always set X-Content-Type-Options "nosniff"
- Header always set X-Frame-Options "SAMEORIGIN"
- Header always set X-XSS-Protection "1; mode=block"
+ # 重複を防ぐため、通常のレスポンスとProxyレスポンスの両方から一旦削除
+ Header unset Strict-Transport-Security
+ Header always unset Strict-Transport-Security
+ Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
+
+ Header unset X-Content-Type-Options
+ Header always unset X-Content-Type-Options
+ Header always set X-Content-Type-Options "nosniff"
+
+ Header unset X-Frame-Options
+ Header always unset X-Frame-Options
+ Header always set X-Frame-Options "SAMEORIGIN"
+
+ # 足りなかった重要ヘッダーを新規追加
+ Header always set Referrer-Policy "strict-origin-when-cross-origin"
+
+ # CSP: アプリの動作を維持しつつ、安全性を高める(object-src 'none' を追加)
+ Header always unset Content-Security-Policy
+ Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';"
+
+ # CookieのSecure属性をApache側で強制付与
+ Header edit Set-Cookie ^(.-)$ "$1; Secure; SameSite=Lax"
設定反映を確認します。
- 構文確認
sudo apache2ctl configtest
Syntax OKを確認します。
- サービス再起動
sudo systemctl restart apache2.service
- サービス再起動確認
systemctl status apache2.service
active (running)を確認します。
設定反映の確認
curl -I https://growi-site
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';
など、ヘッダーのダブりがないことを確認します。
HTTP Observe に再度アクセスし、セキュリティ診断を行いました。
再スキャンの結果、主要な項目はすべて Passed となり、無事に B+ を獲得。
- HSTS / XFO / NoSniff: 重複が解消され、正しく認識された。
- Cookies: Secure/SameSite属性が付き、満点。
- Referrer Policy: 合格。
なぜ「A+」ではないのか?
CSPにおける 'unsafe-inline' や 'unsafe-eval' が「安全ではない」として減点対象になっています。しかし、これらを外すとモダンなWebアプリ(Wikiのエディタなど)は動かなくなることが多いため、利便性とのトレードオフとして 「B+」は現実的な落とし所と言えます。
まとめ
- 診断ツールで「Recognizedできない」と言われたら、まずは
curl -Iで重複を疑う。 - Apache側で
Header always unsetしてからsetすることで、バックエンドとの競合を確実に排除できる。 - CookieのSecure化もApacheの
Header editで一発解決。























