CSPの設定を以下のStrict CSPを参考に基本的にはstrict-dynamicを利用して全てのscriptにnonceを付与し、信頼されたscriptから読み込まれたscriptのみを実行するように変更してみたのでメモ📝
CSPの概要的な話は以下の記事を参考にして下さい。
以下が最終的な設定値です。(Vite及びVue.jsの完全ビルドを採用しているサービです)
Rails.application.configure do config.content_security_policy do |policy| policy.default_src :self, :https policy.font_src :self, :https policy.img_src :self, :https, :data policy.object_src :none# NOTE: Vue.jsの完全ビルドを動作されるのに必要なunsafe-evalは許可。 policy.script_src :strict_dynamic, :unsafe_eval policy.style_src :self, :httpsifRails.env.development? # NOTE: 開発環境ではviteの開発サーバーとhttp/wsでの通信を許可する# Allow @vite/client to hot reload javascript changes in development policy.connect_src :ws, :http# NOTE: viteの制約上、unsafe-inlineを許可する必要がある# https://github.com/vitejs/vite/issues/11862 policy.style_src(*policy.style_src, :unsafe_inline) endend# Generate session nonces for permitted importmap and inline scripts config.content_security_policy_nonce_generator = ->(request) { # NOTE: テスト実行時に以下となりSessionが取得できずnonceが空文字になりCSP違反が発生してしまうので強制的にロードする# #<ActionDispatch::Request::Session:0xd610 not yet loaded># https://github.com/rails/rails/issues/10813#issuecomment-297204965 request.session[:init] = true request.session.id.to_s } config.content_security_policy_nonce_directives = %w[script-src]# Report CSP violations to a specified URI. See:# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only# config.content_security_policy_report_only = trueend
Strict CSP を参考にstrict-dynamic
でnonce付与されたスクリプト自体 + そこからロードされるスクリプトを許可する。
strict-dynamic
を利用すると以下の通り、nonceを付与したスクリプト及び、それによって読み込まれる全てのスクリプトを許可することができます。
'strict-dynamic'ソース式は、マークアップ中のスクリプトに明示的に与えられた信頼が、ノンスやハッシュを伴って、そのルートスクリプトによって読み込まれるすべてのスクリプトに伝搬されることを指定します。同時に、 'self'や 'unsafe-inline'のようなホワイトリストやソース表現は無視されます。 https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#strict-dynamic
strict-dynamic
は設定するとscript-src
にhttpsといったホスト指定、
unsafe-inlineと
self`のキーワード指定は無視されるようです。
host-source and scheme-source expressions, as well as the "'unsafe-inline'" and "'self' keyword-sources will be ignored when loading script. hash-source and nonce-source expressions will be honored. https://www.w3.org/TR/CSP3/#strict-dynamic-usage
あくまでscriptの読み込みに対してのみ有効なので、画像やCSS等の読み込みには引き続き明示的に許可を与える必要がある点には注意です。
What about CSS or Images Loaded via strict-dynamic JavaScript? If one of your trusted scripts loads JavaScript using a safe method (document.createElement), then it can load arbitrary scripts with strict-dynamic. However if your trusted scripts attempt to load an image, a stylesheet, etc., then the img-src, and style-src directives still apply. https://content-security-policy.com/strict-dynamic/
そのためstrict-dynamic
で読み込まれたinline style
を許可する場合には、style-src
にunsafe-inline
を追加する必要があります。
strict-dynamicはブラウザポートの懸念があり、GoogleのStrict CSPのページにもサポート状態を考慮した指定が記載されていますが、現在ではモダンブラウザであれば基本サポートされているため問題なさそうに思いました。
(微妙かも?)stlict_dynamicを有効にした場合にhttp通信も許可されないようにhttpsへアップグレードする
strict_dynamic
を利用する場合nonce
で許可したスクリプトから動的に読み込まれたスクリプトも許可されるが、
その場合にhttp通信が入るとcookie漏洩や盗聴等のリスクがありそうだなぁと思っていたが、
upgrade_insecure_requests
を有効化するとブラウザにhttp通信をhttps通信にアップグレードさせることができる。
https通信できない場合のリソース取得は以下の通り失敗しブロックされる。
GET https://myapp.com:3000/packs/assets/Vue-DZm-d5Zi.js net::ERR_SSL_PROTOCOL_ERROR
これらの URL は、リクエストが行われる前に書き直されます。つまり、安全でないリクエストがネットワークに侵入しないようにします。なお、リクエストされたリソースが実際に HTTPS経由で利用可能ではない場合、リクエストは HTTP で代替されずに失敗することに注意してください。 https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/upgrade-insecure-requests
上記の通りCSPのエラーに乗らず普通にリクエストが失敗してしまうので困っていたが、Content-Security-Policy-Report-Only
ヘッダーとreport-uri
ディレクティブを利用してhttpでのリクエストを検知することが可能っぽい。(自分が使っているエラー通知サービス(Rollbar)によしなに通知されないかな〜と思ったけどダメだった)
安全ではないリクエストの発見 Content-Security-Policy-Report-Only ヘッダーと report-uriディレクティブを利用して、強制ポリシーと報告されたポリシーを次のように設定することができます。 https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/upgrade-insecure-requests#%E5%AE%89%E5%85%A8%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%84%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%AE%E7%99%BA%E8%A6%8B
ただし、Railsのcontent_security_policy
ではreport_only
がbool値での指定のみのサポートで、レポートかエラーかの切り替えはできないっぽいので、この方式は難しそう😢
あと、upgrade_insecure_requests
はlocalhostでもsafariでは有効になってしまう問題があるのと、自分の環境で試した場合にContent-Security-Policy-Report-Only
にしてもhttpsへの接続に切り替えてしまうような挙動がありそうだったので、ちょっと本番活用するには安定感が不安かもしれない。。。