django-csp-reports report-uri へのリクエストを、Google Analytics で記録する。


CSP の reports を report-uri に POST してから、MongoDB に登録していました。
しかしながら、結果の確認、データ可視化に一手間必要なので、Mesurement Protocol で、Google Analytics に記録するように修正しました。
結果を以下に記載します。


前提

以下、Django の plugin を使用して、CSP の report は収集しています。

django-csp                 3.4        
django-csp-reports         1.1        


修正前の実装

以下、記事に pulgin の設定方法、report-uri に post したデータを MongoDB に登録する実装を記載しています。


修正後の実装

以下、CSP レポートのデータを、Google Analytics に転送するスクリプトになります。
* csp.py

CSP_REPORTS_ADDITIONAL_HANDLERS = ["blog.csp.collect_csp_reports"]

# collect csp reports to Google Analytics
import json
from django.http import HttpResponse
from django.http import HttpResponseBadRequest
import random
import requests
import uuid

def collect_csp_reports(request):
    if request.method == 'POST':
        json_data = request.body
        if isinstance(json_data, bytes):
            json_data = json_data.decode('utf-8')
        cid = request.COOKIES.get('_ga')
        if not cid or cid == "":
            cid = uuid.uuid4()
        json_dict = json.loads(json_data)
        json_dict = json_dict.get("csp-report", {})
        payload = {"v": "1",
                   "t": "event",
                   "tid": "UA-XXXXXXXX-X",
                   "cid": cid,
                   "ni": "1",
                   "dl": json_dict.get("document-uri", ""),
                   "ec": "CSPReport",
                   "ea": json_dict.get("violated-directive", ""),
                   "el": json_dict.get("blocked-uri", ""),
                   "ua": request.META.get('HTTP_USER_AGENT', ''),
                   "z": random.randint(0, 99999),
                   "ds": "WebApp",
                   "ul": request.LANGUAGE_CODE
                  }
        r = requests.post("https://www.google-analytics.com/collect", payload)
        return HttpResponse(status=r.status_code)
    else:
        return HttpResponseBadRequest(content='Bad Request...')

説明

以下、実装の説明を記載します。

  • CSP_REPORTS_ADDITIONAL_HANDLERS について
    django-csp-reports の 拡張ポイントになります。
    デフォルトでは CSP レポートをファイルに書き出す POST 処理が定義されていますが、その POST 処理とともに、この設定値に定義した関数を呼び出すことができます。

  • CSRF 対策 について
    django-csp-reports で基本的な、対策は実施されているので、POST 処理では意識する必要がありません。
    最新版だと、DOS 対策っぽい処理も入っていますが、まだ、pypi にはパッケージとして配布されてなさそうです。

  • POST される CSP レポートのフォーマット
    以下のような JSON 形式の文字列が POST されます。

    "csp-report": {
        "blocked-uri": "https://cdn.viglink.com/images/pixel.gif?ch=1&rn=7.989542820989307",
        "disposition": "report",
        "document-uri": "https://www.monotalk.xyz/blog/amphtml-ads-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/",
        "effective-directive": "img-src",
        "original-policy": "script-src 'self' 'unsafe-inline' 'unsafe-eval' pagead2.googlesyndication.com www.google-analytics.com *.disqus.com c.disquscdn.com disqus.com *.googletagmanager.com *.google-analytics.com *.ampproject.org adservice.google.com tagmanager.google.com adservice.google.co.jp uh.nakanohito.jp platform.twitter.com cdn.syndication.twimg.com tagmanager.google.com googleads.g.doubleclick.net adservice.google.ca adservice.google.co.in adservice.google.co.kr adservice.google.co.th adservice.google.co.uk adservice.google.com.au adservice.google.com.br adservice.google.com.hk adservice.google.com.mm adservice.google.com.my adservice.google.com.ph adservice.google.com.sg adservice.google.com.tw adservice.google.com.vn adservice.google.de adservice.google.es adservice.google.fr cdn.mxpnl.com; connect-src 'self' *.disqus.com uh0.nakanohito.jp www.google-analytics.com pagead2.googlesyndication.com syndication.twitter.com 3p.ampproject.net www.googletagmanager.com api.mixpanel.com; style-src 'self' 'unsafe-inline' *.disquscdn.com *.google.com *.googleapis.com *.monotalk.xyz platform.twitter.com ton.twimg.com; font-src 'self' fonts.gstatic.com *.monotalk.xyz data:; object-src pagead2.googlesyndication.com; img-src 'self' *.disqus.com *.disquscdn.com *.googleusercontent.com *.google.com *.googledrive.comgoogledrive.com data: googledrive.com drive.google.com www.gstatic.com www.monotalk.xyz ssl.gstatic.com ir-jp.amazon-adsystem.com ssl.google-analytics.com www.google-analytics.com drive.google.com pagead2.googlesyndication.com stats.g.doubleclick.net; child-src 'self' googleads.g.doubleclick.net staticxx.facebook.com *.disqus.com kemsakurai.github.io *.ampproject.net syndication.twitter.com platform.twitter.com www.googletagmanager.com rcm-fe.amazon-adsystem.com disqus.com disqusads.com rcm-fe.amazon-adsystem.com; default-src 'self'; report-uri /report/",
        "referrer": "https://www.monotalk.xyz/blog/django-template-%E5%86%85%E3%81%A7%E9%95%B7%E3%81%84%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E7%9C%81%E7%95%A5%E3%81%99%E3%82%8B/",
        "script-sample": "",
        "status-code": 0,
        "violated-directive": "img-src"
    }
}
  • Mesurement Protocol で、送付する項目について
    以下 のパラメータを送付するようにしました。

    • v
      version 番号 1 固定です。
    • t
      ヒットタイプ event を設定しました。
    • ni
      非インタラクション としたかったので、 1 を設定しました。
    • dl
      ドキュメント URL を設定しました。event の場合、設定不要かもしれません。
    • ec
      イベントカテゴリです。 CSPReport を固定で設定します。
    • ea
      イベントアクションです。violated-directive の値を取得して設定するようにしました。
    • el
      イベントラベルです。blocked-uri を設定しました。
    • ua
      ユーザーエージェントです。ブラウザのユーザーエージェントを設定するようにしました。
    • z
      プロキシサーバ等の Cache を無効にするためのパラメータです。
      POST で Mesurement Protocol を送付する場合は、意味がなさそうですが、設定しても捨てられるだけなので設定しています。
    • ds
      データソース、WebApp を設定しました。
    • ul
      言語コードです。ブラウザの言語コードを設定しました。
  • 非同期処理で、リクエスト転送をしたほうがいいかもしれない
    POST された結果を、同期処理で、Google Analytics に転送していますが、同期処理でブラウザ側に処理を待っていてもらう必要もないので、非同期でスレッドを立ち上げて処理させてもよさそうです。


まとめ

report-uri に指定した、サーバの処理から Mesurement Protocol で Google Analytics に記録してみました。
Mesurement Protocol の送信処理 が、Google Analytics のフィルタ設定で除外対象になっており、それに気づかず、結構長い時間を費やしてしまいました。
Mesurement Protocol だと、ga 関数では透過的に送信される情報も含めて送信してあげないといけないのが気をつけるポイントかと思いました。
以上です。

コメント