ページ本文へ移動
炭火ブログ

MENU

ねこ ねこ

Cloudflare×KVでCSPレポートを効率的に受信・保存する手順

Cloudflareの変換ルールを利用してContent Security Policy(CSP)の設定を行いました。

設定したCSPのレポートを受信する仕組みをCloudflareのFree契約で構築します。CSPのためにnonce値をCloudflare Workersで付与しようとしたのですが、Workersの制限と力不足で上手くは行きませんでした。今回のレポートをWorkersで受信してCloudflare KVでデータとして保存する方法は、受信データーの容量が非常に小さく、WorkersのCPU動作時間10ms以下という制限は大丈夫だと思います。

作業の流れと必要コードの内容だけを掲載していますが、詳細な方法は検索するとたくさん見つかります。

目次OpenCloseCloudFlare WorkersとKVを利用したCSPレポートの受取機能の構築手順wrangler.jsoncwrangler.tomlindex.js

CloudFlare WorkersとKVを利用したCSPレポートの受取機能の構築手順

まずはじめにCloudflare Workersを使えるようにします。

  1. node.jsとnpmをインストールする。
  2. wranglerをインストールする。

次にWorkersとKVの設定をします。

  1. Cloudflare ダッシュボードで KV Namespace を作成する。 (例:REPORTS_KV)。
  2. Wrangler CLI を使用して Worker プロジェクトを作成・設定します。
  3. 上記のwrangler.tomlとsrc/index.jsをプロジェクトに配置する。
  4. wrangler.tomlのidを作成した KV Namespace の ID に置き換える。
  5. wrangler.jsonc;(またはwrangler.toml) ファイルにルート設定を記述する。
  6. wrangler deployコマンドで Worker をデプロイする。

このとき、Workersで自動で生成されるドメインは無効にすることをおすすめします。CSPレポートはwrangler.jsoncで設定で設定したアドレスに送られるのでWorkersドメインを公開する必要はありません。また、デプロイするたびにドメインはアクティブになるので注意が必要です。

最終的にはCloudflare KVの「KV ペア」タブにCSPレポートが表示されます。

cloudflare kv csp report

wrangler.jsonc

{
	"$schema": "node_modules/wrangler/config-schema.json",
	"name": "your-domein-csp",//worlersの名前
	"main": "src/index.js",
	"compatibility_date": "2025-06-01",// 最初の作成日
	"kv_namespaces": [
    {
      "binding": "REPORT_KV",
      "id": "XXXXXXXXXXXXXXXXXXXXXXX" // KV Namespace の ID
    }
  ],
	"compatibility_flags": [
		"nodejs_compat",
		"global_fetch_strictly_public"
	],
	"observability": {
		"enabled": true
	},
  "routes": [//ルートの設定ここから
    {
      "pattern": "your-domein.com/reports", 
      "zone_name": "your-domein.com"            
    }
  ]//ルートの設定ここまで
}

wrangler.toml

name = "your-domein-csp" // Workerの名前
main = "src/index.js"
compatibility_date = "2025-6-01" // 作成日に近い日付

[[kv_namespaces]]
binding = "REPORT_KV" //KVの名前
id = "XXXXXXXXXXXXXXXXXXXX"// KV NamespaceのID

index.js

export default {
  async fetch(request, env, ctx) {
    // POSTリクエストのみを処理
    if (request.method !== 'POST') {
      return new Response('Expected POST request', { status: 405 });
    }

    const contentType = request.headers.get('content-type');

    // CSPレポートのContent-Typeをチェック
    // report-uri は 'application/csp-report'
    // report-to は 'application/reports+json' (または単に 'application/json')
    if (
      !contentType ||
      (!contentType.includes('application/csp-report') &&
       !contentType.includes('application/reports+json') &&
       !contentType.includes('application/json'))
    ) {
      return new Response(
        'Invalid Content-Type. Expected application/csp-report or application/reports+json.',
        { status: 415 }
      );
    }

    try {
      const reportData = await request.json();
      const timestamp = new Date().toISOString();
      // ユニークなIDを生成してキーとして使用
      const reportId = `csp-report-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;

      // Workerのログに出力 (Cloudflareダッシュボードで確認可能)
      console.log(`CSP Report Received (${reportId}) at ${timestamp}:`);
      console.log(JSON.stringify(reportData, null, 2));

      // KVストアに保存 (REPORTS_KV が wrangler.toml でバインドされている必要あり)
      if (env.REPORTS_KV) {
        await env.REPORTS_KV.put(
          reportId,
          JSON.stringify({
            receivedAt: timestamp,
            clientIp: request.headers.get('CF-Connecting-IP'), // クライアントIP
            userAgent: request.headers.get('User-Agent'),     // ユーザーエージェント
            report: reportData,
          }),
          {
            // オプション: KVエントリの有効期限 (例: 60日間)
             expirationTtl: 60 * 24 * 60 * 60,
          }
        );
        console.log(`Report ${reportId} stored in KV.`);
      } else {
        console.warn('REPORTS_KV binding not found. Report not stored in KV.');
      }

      // レポート受信成功時は 204 No Content を返すのが一般的
      return new Response(null, { status: 204 });

    } catch (error) {
      console.error('Error processing CSP report:', error.message, error.stack);
      // エラー発生時は 500 Internal Server Error
      return new Response('Error processing report', { status: 500 });
    }
  },
};

まずは「Content-Security-Policy-Report-Only」ヘッダーを公開します。これは「Content-Security-Policy」と違いポリシー違反があってもブラウザの動作に制限がかかりません。文字通りレポートだけを表示するので、当面は様子を見るべきです。

ここまでで、ポリシーレポートを受け取りKVで保存できるようになりました。レポートを見やすくするために、一覧表を表示するページをCloudflare Workersで作ります。

最後までお読みいただき、誠にありがとうございます。

SEO関連

アクセシビリティ重視のdialog式ハンバーガーメニュー解説 Accessibility Dialog Menu ブログにCSP script-src導入でセキュリティを強固にする方法 content-security-policy-script-src CSPレポートの保存と可視化をCloudflareで実現する手順 create-a-csp-report-list-page WordPressの自作テーマでアクセス数が減少した時の見直しポイント blog accesses has decreased PageSpeed Insightsの長時間タスクをCSS・JS・DOMから検証 Pagespeed Insights Long Task

Cloudflare関連

iframeからHTMLへ・Turnstileで強化する問い合わせフォーム Cloudflare Turnstile Contact Form WordPress静的化とCloudflare Pagesでコストゼロ運営を実現 simply-static-cloudflare-pages-wordpress さくらレンタルサーバー移転手順とCloudflare活用法 sakura hosting service-cloudflare 「cdn-cgi/content?id=」と「サポートを終了した API が使用されています」の修正 サポートを終了したAPI WordPress静的化とCloudflare Pagesの実力検証 cloudflare-pages-pagespeed-insights

総合ランキング

U字溝と耐火レンガで作る格安の焼鳥台の作り方と使い方 U字溝と耐火レンガで炭火を使う キャビティプロ導入で水槽管理はどう変わる?3ヶ月使用レポート キャビティプロ TOPPING Mini 300を測定値で見る!最高級の音質を誇る低価格アンプ Topping Mini 300 Fosi Audio V3 Monoのオペアンプ交換OPA1612で音質は改善するのか V3 MonoとOPA1612 低価格で高音質!中華アンプとDACのASRの評価とその選び方 Fosi Audio V3
管理者
ほんだ

当サイトの管理者 : 炭火で美味しいものを作ることを中心に、日々の趣味についてを文章にすることで、WordPressを使ってのWebページ作成を忘れないようにするブログです。熱帯魚の世話や野菜の栽培、Linuxについて、音楽を聴くための機材やアイデアなど、興味のあることを書いています。兵庫県在住。