Content Security Policy(CSP) ではHTTPヘッダや<meta>要素に定義を記述し、さまざまな条件でコンテンツに制約をかけることができます。 そのCSPの中でも実際に制約をかけずにテストして、その結果を特定のURLにPOSTする Content-Security-Policy-Report-Only という仕組みがあります。

CSPにはHTTPSではない画像やJavaScriptを読み込めなくする制約も設定できるので、HTTPからHTTPSに移行する際などに役立ちます。 例えば、リソースはすべてHTTPSから読み込まないと行けないというCSPの設定は次のようにかけます。

Content-Security-Policy: default-src https:

実際にこのCSPをHTTPレスポンスヘッダに設定するとCSPに対応しているブラウザは、HTTPSではない画像やJavaScriptなどをブロックします。

実際にブロックされると使えなくなって困るので、サイトの管理者はそのようなリソースが埋め込まれていないかを Content-Security-Policy-Report-Only でチェックすることができます。(実際にアクセスしたブラウザがCSP違反があるならレポート先のURLにデータをPOSTする)

Content-Security-Policy-Report-Only: default-src https: report-to https://example.com/csp-report

のようなHTTPレスポンスヘッダを設定することで、ユーザーがページにアクセスした時に、https://example.com/csp-report へPOSTでその情報を投げてくれます。(ブラウザが必要な情報を勝手に送信します)

ここではreport-toと書いていますが、古いブラウザはreport-uriだったりするので注意します。

このCSPについては次の記事が詳しいです。

このCSPレポートの仕組みは便利ですがそのデータの集約先をどうするかという問題がついてきます。 CSPレポートは PV * CSP違反のリソース数 となるため適当にやると膨大なデータが飛んでくる可能性があります。 このCSPレポートを収集できるサービスとしてreport-uri.comsentry.ioなどがあります。

どちらもそれだけのために使うのも微妙だなーと思いCSPについて調べていたところ、CSPの制約違反はreport-toだけではなくJavaScriptのイベントとして取得する方法を見つけました。 securitypolicyviolationイベントではCSPの制約違反をした時にSecurityPolicyViolationEventオブジェクトと共に呼び出されます。

document.addEventListener("securitypolicyviolation", (e) => {
  console.log(e.blockedURI);    
  console.log(e.violatedDirective);    
  console.log(e.originalPolicy);
});

このイベントを使えば、report-to(report-uri)の指定以外の方法でも任意の場所にCSPレポートを送ることができそうです。

こういったPVに関連するデータの集積といったらGoogle Analyticsがよく使われていると思います。 このサイトでもGoogle Analyticsを使っていたので、CSPレポートをGoogle Analyticsに送るanalytics.jsのプラグインを作りました。

csp-report-to-google-analytics

このanalytics.jsのプラグインはCSPレポートをイベントとしてGoogle Analyticsに送信します。

導入方法は簡単で既にanalytics.jsを導入している人は、 <script async src='https://unpkg.com/csp-report-to-google-analytics/dist/csp-report-to-google-analytics.min.js'></script>でプラグインを別途読み込み、ga('require', 'csp-report');でプラグインanalytics.jsに適応するだけです。

<!-- Google Analytics -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
// require csp-report-to-google-analytics plugin
ga('require', 'csp-report');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<!-- End Google Analytics -->
<!-- Load csp-report-to-google-analytics plugin -->
<script async src='https://unpkg.com/csp-report-to-google-analytics/dist/csp-report-to-google-analytics.min.js'></script>

今のGoogle Analyticsはgtag.jsがデフォルトみたいですが、gtag.jsはプラグインの仕組みをサポートしていないためanalytics.jsでしか動きません。

あとは待っていればCSPレポートがGoogle Analyticsのイベント画面に表示されます。 (SecurityPolicyViolationEventに対応しているブラウザでアクセスする必要があります。)

efcl.infoでのテスト

このサイトでもHTTPSに移行するためにこのプラグインを導入してみました。 CSPは次のようなディレクティブが書かれています。 (http://www.google-analytics.com/*を許可しているのは、Google Analyticsがビーコンに画像を使っていて再帰的に掛かりそうな問題があるため。これBeacon APIにすれば解消するのかも)

Content-Security-Policy-Report-Only: default-src https: http://www.google-analytics.com/* 'unsafe-eval' 'unsafe-inline';

実際に取得できたCSPレポートはGoogle Analyticsの管理画面で見ることができます。 Google Analyticsのイベント トラッキングを使っているため自動的にページなどに紐付いたデータとなっています。

image

image

このデータはHTTPSへの移行する時のMixed Content探しの指標データなどとして利用できると思います。

もっとしっかりと分析したい人はAWSのAPI Gatewayで作成したAPIをreport-to(report-uri)に指定し、Amazon Kinesis Data Firehoseにデータを流し、S3やRedshiftなどでデータを分析するのがいいと思います。

Google Analyticsのイベントは送ることができるデータに制約があるので、csp-report-to-google-analytics では重要そうなデータしか送っていません。

まとめ

csp-report-to-google-analyticsを使ったCSPレポートをGoogle Analyticsに送る方法を紹介しました。

CSPのHTTPレスポンスヘッダを指定する必要がありますが、結構気軽にできるので面白いかもしれません。

残念ながら <meta>タグでは Content-Security-Policy-Report-Only が利用できません。 (通常のCSPは設定できますが実際にコンテンツが表示されなくなるので、目的に合わない) そのため、この手法にはHTTPヘッダを設定できるサーバが必要です。 このサイトはGitHub PagesからNetlifyに移すことで、この問題を回避しています。