aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authoryuuji.yaginuma <yuuji.yaginuma@gmail.com>2019-02-03 11:33:44 +0900
committeryuuji.yaginuma <yuuji.yaginuma@gmail.com>2019-06-22 12:44:37 +0900
commit09d55b302266cf002a4b307f8d37a105d2838a18 (patch)
treea85cf250ab0171a780f34dd1c0edae56bea20e6d /actionpack
parenta2a515d9de4ef0ddf4d78b05fcb0b838d2e1b5e3 (diff)
downloadrails-09d55b302266cf002a4b307f8d37a105d2838a18.tar.gz
rails-09d55b302266cf002a4b307f8d37a105d2838a18.tar.bz2
rails-09d55b302266cf002a4b307f8d37a105d2838a18.zip
Add the ability to set the CSP nonce only to the specified directives
I changed to set CSP nonce to `style-src` directive in #32932. But this causes an issue when `unsafe-inline` is specified to `style-src` (If a nonce is present, a nonce takes precedence over `unsafe-inline`). So, I fixed to nonce directives configurable. By configure this, users can make CSP as before. Fixes #35137.
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md6
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb29
-rw-r--r--actionpack/test/dispatch/content_security_policy_test.rb54
3 files changed, 80 insertions, 9 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 55592585ea..0dd170fd28 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Add the ability to set the CSP nonce only to the specified directives.
+
+ Fixes #35137.
+
+ *Yuji Yaginuma*
+
* Keep part when scope option has value.
When a route was defined within an optional scope, if that route didn't
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index 5c6fa2dfa7..7dedecef34 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -22,8 +22,9 @@ module ActionDispatch #:nodoc:
if policy = request.content_security_policy
nonce = request.content_security_policy_nonce
+ nonce_directives = request.content_security_policy_nonce_directives
context = request.controller_instance || request
- headers[header_name(request)] = policy.build(context, nonce)
+ headers[header_name(request)] = policy.build(context, nonce, nonce_directives)
end
response
@@ -54,6 +55,7 @@ module ActionDispatch #:nodoc:
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
NONCE = "action_dispatch.content_security_policy_nonce"
+ NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives"
def content_security_policy
get_header(POLICY)
@@ -79,6 +81,14 @@ module ActionDispatch #:nodoc:
set_header(NONCE_GENERATOR, generator)
end
+ def content_security_policy_nonce_directives
+ get_header(NONCE_DIRECTIVES)
+ end
+
+ def content_security_policy_nonce_directives=(generator)
+ set_header(NONCE_DIRECTIVES, generator)
+ end
+
def content_security_policy_nonce
if content_security_policy_nonce_generator
if nonce = get_header(NONCE)
@@ -131,9 +141,9 @@ module ActionDispatch #:nodoc:
worker_src: "worker-src"
}.freeze
- NONCE_DIRECTIVES = %w[script-src style-src].freeze
+ DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
- private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
+ private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
attr_reader :directives
@@ -202,8 +212,9 @@ module ActionDispatch #:nodoc:
end
end
- def build(context = nil, nonce = nil)
- build_directives(context, nonce).compact.join("; ")
+ def build(context = nil, nonce = nil, nonce_directives = nil)
+ nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
+ build_directives(context, nonce, nonce_directives).compact.join("; ")
end
private
@@ -226,10 +237,10 @@ module ActionDispatch #:nodoc:
end
end
- def build_directives(context, nonce)
+ def build_directives(context, nonce, nonce_directives)
@directives.map do |directive, sources|
if sources.is_a?(Array)
- if nonce && nonce_directive?(directive)
+ if nonce && nonce_directive?(directive, nonce_directives)
"#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
else
"#{directive} #{build_directive(sources, context).join(' ')}"
@@ -264,8 +275,8 @@ module ActionDispatch #:nodoc:
end
end
- def nonce_directive?(directive)
- NONCE_DIRECTIVES.include?(directive)
+ def nonce_directive?(directive, nonce_directives)
+ nonce_directives.include?(directive)
end
end
end
diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb
index 30c340ae9e..a4634626bb 100644
--- a/actionpack/test/dispatch/content_security_policy_test.rb
+++ b/actionpack/test/dispatch/content_security_policy_test.rb
@@ -542,3 +542,57 @@ class DisabledContentSecurityPolicyIntegrationTest < ActionDispatch::Integration
assert_equal "default-src https://example.com", response.headers["Content-Security-Policy"]
end
end
+
+class NonceDirectiveContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest
+ class PolicyController < ActionController::Base
+ def index
+ head :ok
+ end
+ end
+
+ ROUTES = ActionDispatch::Routing::RouteSet.new
+ ROUTES.draw do
+ scope module: "nonce_directive_content_security_policy_integration_test" do
+ get "/", to: "policy#index"
+ end
+ end
+
+ POLICY = ActionDispatch::ContentSecurityPolicy.new do |p|
+ p.default_src -> { :self }
+ p.script_src -> { :https }
+ p.style_src -> { :https }
+ end
+
+ class PolicyConfigMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env["action_dispatch.content_security_policy"] = POLICY
+ env["action_dispatch.content_security_policy_nonce_generator"] = proc { "iyhD0Yc0W+c=" }
+ env["action_dispatch.content_security_policy_report_only"] = false
+ env["action_dispatch.content_security_policy_nonce_directives"] = %w(script-src)
+ env["action_dispatch.show_exceptions"] = false
+
+ @app.call(env)
+ end
+ end
+
+ APP = build_app(ROUTES) do |middleware|
+ middleware.use PolicyConfigMiddleware
+ middleware.use ActionDispatch::ContentSecurityPolicy::Middleware
+ end
+
+ def app
+ APP
+ end
+
+ def test_generate_nonce_only_specified_in_nonce_directives
+ get "/"
+
+ assert_response :success
+ assert_match "script-src https: 'nonce-iyhD0Yc0W+c='", response.headers["Content-Security-Policy"]
+ assert_no_match "style-src https: 'nonce-iyhD0Yc0W+c='", response.headers["Content-Security-Policy"]
+ end
+end