Craft CMS で CSP 対応をする(導入編)

セキュリティ対策の一環として、CSP 対応を求められる機会が増えています。

コンテンツセキュリティポリシー (CSP) - HTTP | MDN
https://developer.mozilla.org/ja/docs/Web/HTTP/CSP

具体的な内容については MDN のドキュメントを参照していただくとして、Craft CMS で CSP 対応をする方法について、まとめてみたいと思います。

これは Craft CMS Advent Calendar 2024 9日目の記事です。

Craft CMS での導入方法

Craft CMS で CSP 対応する場合、フリーのプラグインを利用するとスムーズです。

プラグインのインストール

管理画面のメニューから プラグインストア に移動し、CSP で検索します。

プラグインストアの検索結果

(執筆時点では)3つのプラグインがヒットしますので、CSP をクリックします。

プラグイン詳細ページ

プラグイン詳細にある「インストール」ボタンをクリック。その後、インストールできたら 設定 > プラグイン 枠にある「CSP」アイコンをクリックしてプラグイン設定を調整しましょう。

プラグイン設定の調整

プラグイン設定をするにあたり、Settings タブの内容を調整しておきましょう。

プラグイン設定の調整:Settings タブ

今回のサンプルでは、デフォルトの状態から次の項目だけを調整しています。

ラベル説明
Enable Header ProtectionOFFCSP 以外の HTTP セキュリティヘッダーを定義できますが、
今回のサンプルでは割愛します。
CSP Optionsscript-src
style-src
img-src
connect-src
サイトの構成によって必要なポリシーが増減するため、
今回のサンプルでは4つとします。

続けて Policy タブを調整します。

プラグイン設定の調整:Policy タブ

項目ごとにテーブルの行を追加し、必要なポリシーを設定していきます。

項目
script-src'self'
https://*.googletagmanager.com
style-src'self'
https://*.googleapis.com
img-src'self'
https://.google-analytics.com https://.googletagmanager.com
connect-src'self'
https://.google-analytics.com https://.analytics.google.com https://*.googletagmanager.com

今回のサンプルでは、Google タグ マネージャー / Google アナリティクス / Google フォントを利用するイメージで、最低限必要そうなドメインを追加しています。

実際には CSP プラグインをインストール後、ブラウザのコンソールでエラーがないかを確認し、必要に応じてポリシーやドメインを追加していくとよいでしょう。

テンプレートの調整

最後に、テンプレートに記述された scirpt や style タグなどへ nonce 属性を追加します。

<script nonce="{{ csp('script-src') }}">
(中略)
</script>

<style nonce="{{ csp('style-src') }}">
(中略)
</style>

このとき、どのポリシーの nonce として追加するのか csp ファンクションの引数で指定する点だけ、注意しましょう。

レスポンスヘッダのサンプル

ブラウザでレスポンスヘッダを確認すると、Content-Security-Policy ヘッダが追加され、ここまでに調整した内容が反映されているのがわかりますね。

ひと通り調整を行い、ブラウザのコンソールに CSP 関連のエラーが出ないことを確認できたら、完了です。

まとめ

Craft CMS で CSP 対応をする場合、次のような流れになります。

  • フリーの CSP プラグイン(Craft 4 / Craft 5 に対応)をインストール
  • プラグイン設定で、サイトの構成上必要なポリシーを選択し、ポリシーごとにドメインを追加する
  • 必要に応じて、テンプレートに nonce 属性を追加する
  • ブラウザのコンソールでエラーが出ないことを確認する

一旦流れを覚えてしまえば、あとは難しくなさそうですね。

なお、単一のテンプレートで同じポリシーに複数の nonce を設定すると Content-Security-Policy ヘッダの値が冗長になってしまうのですが、その最適化方法については次の記事でご紹介したいと思います。