Add ability to apply two headers support tuning policies through experimentation
This commit is contained in:
Родитель
ebadff95ad
Коммит
d1cccc1366
|
@ -2,7 +2,7 @@ guard 'spork', :rspec_env => { 'RAILS_ENV' => 'test' } do
|
|||
watch('spec/spec_helper.rb') { :rspec }
|
||||
end
|
||||
|
||||
guard 'rspec', :cli => "--color --drb", :keep_failed => true, :all_after_pass => true do
|
||||
guard 'rspec', :cli => "--color --drb", :keep_failed => true, :all_after_pass => true, :focus_on_failed => true do
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
||||
watch(%r{^app/controllers/(.+)\.rb$}) { |m| "spec/controllers/#{m[1]}_spec.rb" }
|
||||
|
|
13
README.md
13
README.md
|
@ -131,6 +131,19 @@ and [Firefox CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
|
|||
# would produce the directive: "img-src https://* http://*;"
|
||||
# when over http, ignored for https requests
|
||||
:http_additions => {}
|
||||
|
||||
# If you have enforce => true, you can use the `experiments` block to
|
||||
# also produce a report-only header. Values in this block override the
|
||||
# parent config for the report-only, and leave the enforcing header
|
||||
# unaltered. http_additions work the same way described above, but
|
||||
# are added to your report-only header as expected.
|
||||
:experimental => {
|
||||
:script_src => 'self',
|
||||
:img_src => 'https://mycdn.example.com',
|
||||
:http_additions {
|
||||
:img_src => 'http://mycdn.example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -53,6 +53,10 @@ module SecureHeaders
|
|||
|
||||
header = ContentSecurityPolicy.new(request, options)
|
||||
set_header(header.name, header.value)
|
||||
if options && options[:experimental] && options[:enforce]
|
||||
header = ContentSecurityPolicy.new(request, options, :experimental => true)
|
||||
set_header(header.name, header.value)
|
||||
end
|
||||
end
|
||||
|
||||
def set_a_header(name, klass, options=nil)
|
||||
|
|
|
@ -30,7 +30,8 @@ module SecureHeaders
|
|||
alias :ssl_request? :ssl_request
|
||||
|
||||
|
||||
def initialize request = nil, config = nil
|
||||
def initialize(request=nil, config=nil, options={})
|
||||
@experimental = !!options.delete(:experimental)
|
||||
if config
|
||||
configure request, config
|
||||
elsif request
|
||||
|
@ -41,6 +42,12 @@ module SecureHeaders
|
|||
def configure request, opts
|
||||
@config = opts.dup
|
||||
|
||||
experimental_config = @config.delete(:experimental)
|
||||
if @experimental && experimental_config
|
||||
@config[:http_additions] = experimental_config[:http_additions]
|
||||
@config.merge!(experimental_config)
|
||||
end
|
||||
|
||||
parse_request request
|
||||
META.each do |meta|
|
||||
self.send(meta.to_s + "=", @config.delete(meta))
|
||||
|
@ -63,7 +70,9 @@ module SecureHeaders
|
|||
WEBKIT_CSP_HEADER_NAME
|
||||
end
|
||||
|
||||
base += "-Report-Only" unless enforce
|
||||
if !enforce || @experimental
|
||||
base += "-Report-Only"
|
||||
end
|
||||
base
|
||||
end
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ module SecureHeaders
|
|||
FIREFOX_18 = "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/18.0 Firefox/18.0"
|
||||
CHROME = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4"
|
||||
|
||||
def request_for user_agent, request_uri = nil
|
||||
double(:ssl? => false, :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://example.com') )
|
||||
def request_for user_agent, request_uri=nil, options={:ssl => false}
|
||||
double(:ssl? => options[:ssl], :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://example.com') )
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
|
@ -43,6 +43,14 @@ module SecureHeaders
|
|||
specify { ContentSecurityPolicy.new(request_for(FIREFOX_18), opts).name.should == FIREFOX_CSP_HEADER_NAME}
|
||||
specify { ContentSecurityPolicy.new(request_for(CHROME), opts).name.should == WEBKIT_CSP_HEADER_NAME}
|
||||
end
|
||||
|
||||
context "when in experimental mode" do
|
||||
let(:opts) { default_opts.merge(:enforce => true).merge(:experimental => {})}
|
||||
specify { ContentSecurityPolicy.new(request_for(IE), opts, :experimental => true).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
||||
specify { ContentSecurityPolicy.new(request_for(FIREFOX), opts, :experimental => true).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
|
||||
specify { ContentSecurityPolicy.new(request_for(FIREFOX_18), opts, :experimental => true).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
|
||||
specify { ContentSecurityPolicy.new(request_for(CHROME), opts, :experimental => true).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
|
||||
end
|
||||
end
|
||||
|
||||
describe "#fill_directives" do
|
||||
|
@ -302,6 +310,29 @@ module SecureHeaders
|
|||
end
|
||||
end
|
||||
|
||||
context "when supplying a experimental values" do
|
||||
let(:options) {{
|
||||
:disable_chrome_extension => true,
|
||||
:disable_fill_missing => true,
|
||||
:default_src => 'self',
|
||||
:script_src => 'https://*',
|
||||
:experimental => {
|
||||
:script_src => 'self'
|
||||
}
|
||||
}}
|
||||
|
||||
let(:header) {}
|
||||
it "returns the original value" do
|
||||
header = ContentSecurityPolicy.new(request_for(CHROME), options)
|
||||
header.value.should == "default-src 'self'; script-src https://*;"
|
||||
end
|
||||
|
||||
it "it returns the experimental value if requested" do
|
||||
header = ContentSecurityPolicy.new(request_for(CHROME), options, :experimental => true)
|
||||
header.value.should == "default-src 'self'; script-src 'self';"
|
||||
end
|
||||
end
|
||||
|
||||
context "when supplying additional http directive values" do
|
||||
let(:options) {
|
||||
default_opts.merge({
|
||||
|
@ -318,11 +349,45 @@ module SecureHeaders
|
|||
end
|
||||
|
||||
it "does not add the directive values if requesting https" do
|
||||
request = request_for(CHROME)
|
||||
request.stub(:ssl?).and_return(true)
|
||||
csp = ContentSecurityPolicy.new(request, options)
|
||||
csp = ContentSecurityPolicy.new(request_for(CHROME, '/', :ssl => true), options)
|
||||
csp.value.should == "default-src https://*; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* chrome-extension: about:; report-uri /csp_report;"
|
||||
end
|
||||
|
||||
context "when supplying an experimental block" do
|
||||
# this simulates the situation where we are enforcing that scripts
|
||||
# only come from http[s]? depending if we're on ssl or not. The
|
||||
# report only tag will allow scripts from self over ssl, and
|
||||
# from a secure CDN over non-ssl
|
||||
let(:options) {{
|
||||
:disable_chrome_extension => true,
|
||||
:disable_fill_missing => true,
|
||||
:default_src => 'self',
|
||||
:script_src => 'https://*',
|
||||
:http_additions => {
|
||||
:script_src => 'http://*'
|
||||
},
|
||||
:experimental => {
|
||||
:script_src => 'self',
|
||||
:http_additions => {
|
||||
:script_src => 'https://mycdn.example.com'
|
||||
}
|
||||
}
|
||||
}}
|
||||
# for comparison purposes, if not using the experimental header this would produce
|
||||
# "allow 'self'; script-src https://*" for https requests
|
||||
# and
|
||||
# "allow 'self; script-src https://* http://*" for http requests
|
||||
|
||||
it "uses the value in the experimental block over SSL" do
|
||||
csp = ContentSecurityPolicy.new(request_for(FIREFOX, '/', :ssl => true), options, :experimental => true)
|
||||
csp.value.should == "allow 'self'; script-src 'self';"
|
||||
end
|
||||
|
||||
it "merges the values from experimental/http_additions when not over SSL" do
|
||||
csp = ContentSecurityPolicy.new(request_for(FIREFOX), options, :experimental => true)
|
||||
csp.value.should == "allow 'self'; script-src 'self' https://mycdn.example.com;"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -221,5 +221,32 @@ describe SecureHeaders do
|
|||
subject.set_csp_header request
|
||||
end
|
||||
end
|
||||
|
||||
context "when using the experimental key" do
|
||||
before(:each) do
|
||||
stub_user_agent(USER_AGENTS[:chrome])
|
||||
@opts = {
|
||||
:enforce => true,
|
||||
:default_src => 'self',
|
||||
:script_src => 'https://mycdn.example.com',
|
||||
:experimental => {
|
||||
:script_src => 'self',
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it "does not set the header in enforce mode if experimental is supplied, but enforce is disabled" do
|
||||
opts = @opts.merge(:enforce => false)
|
||||
should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
|
||||
should_not_assign_header(WEBKIT_CSP_HEADER_NAME)
|
||||
subject.set_csp_header request, opts
|
||||
end
|
||||
|
||||
it "sets a header in enforce mode as well as report-only mode" do
|
||||
should_assign_header(WEBKIT_CSP_HEADER_NAME, anything)
|
||||
should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
|
||||
subject.set_csp_header request, @opts
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Загрузка…
Ссылка в новой задаче