diff --git a/Gemfile b/Gemfile index 13904f3783b..918eeef510c 100644 --- a/Gemfile +++ b/Gemfile @@ -56,7 +56,6 @@ gem 'ruby-saml' gem 'safe_target_blank', '>= 1.0.2' gem 'saml_idp', github: '18F/saml_idp', tag: '0.16.0-18f' gem 'scrypt' -gem 'secure_headers', '~> 6.3' gem 'simple_form', '>= 5.0.2' gem 'stringex', require: false gem 'strong_migrations', '>= 0.4.2' diff --git a/Gemfile.lock b/Gemfile.lock index b358894f075..075994f7182 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -574,7 +574,6 @@ GEM faraday (> 0.8, < 2.0) scrypt (3.0.7) ffi-compiler (>= 1.0, < 2.0) - secure_headers (6.3.3) securecompare (1.0.0) selenium-webdriver (4.1.0) childprocess (>= 0.5, < 5.0) @@ -772,7 +771,6 @@ DEPENDENCIES safe_target_blank (>= 1.0.2) saml_idp! scrypt - secure_headers (~> 6.3) shoulda-matchers (~> 4.0) simple_form (>= 5.0.2) simplecov (~> 0.21.0) diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb index fd81223e62b..8ce946d7589 100644 --- a/config/initializers/secure_headers.rb +++ b/config/initializers/secure_headers.rb @@ -1,5 +1,3 @@ -require 'feature_management' - Rails.application.configure do config.ssl_options = { secure_cookies: true, @@ -12,113 +10,3 @@ 'X-Download-Options' => 'noopen', ) end - -SecureHeaders::Configuration.default do |config| # rubocop:disable Metrics/BlockLength - config.hsts = "max-age=#{365.days.to_i}; includeSubDomains; preload" - config.x_frame_options = 'DENY' - config.x_content_type_options = 'nosniff' - config.x_xss_protection = '1; mode=block' - config.x_download_options = 'noopen' - config.x_permitted_cross_domain_policies = 'none' - - connect_src = ["'self'", '*.nr-data.net', '*.google-analytics.com', 'us.acas.acuant.net'] - default_csp_config = { - default_src: ["'self'"], - child_src: ["'self'"], # CSP 2.0 only; replaces frame_src - form_action: ["'self'"], - block_all_mixed_content: true, # CSP 2.0 only; - connect_src: connect_src.flatten, - font_src: ["'self'", 'data:', IdentityConfig.store.asset_host.presence], - img_src: [ - "'self'", - 'data:', - 'login.gov', - IdentityConfig.store.asset_host.presence, - 'idscangoweb.acuant.com', - IdentityConfig.store.aws_region.presence && - "https://s3.#{IdentityConfig.store.aws_region}.amazonaws.com", - ].select(&:present?), - media_src: ["'self'"], - object_src: ["'none'"], - script_src: [ - "'self'", - 'js-agent.newrelic.com', - '*.nr-data.net', - 'dap.digitalgov.gov', - '*.google-analytics.com', - IdentityConfig.store.asset_host.presence, - ], - style_src: ["'self'", IdentityConfig.store.asset_host.presence], - base_uri: ["'self'"], - preserve_schemes: true, - disable_nonce_backwards_compatibility: IdentityConfig.store.disable_csp_unsafe_inline, - } - - if IdentityConfig.store.rails_mailer_previews_enabled - default_csp_config[:style_src] << "'unsafe-inline'" - # CSP 2.0 only; overriden by x_frame_options in some browsers - default_csp_config[:frame_ancestors] = %w['self'] - end - - default_csp_config[:script_src] = ["'self'", "'unsafe-eval'"] if !Rails.env.production? - - if ENV['WEBPACK_PORT'] - default_csp_config[:connect_src] << "ws://localhost:#{ENV['WEBPACK_PORT']}" - default_csp_config[:script_src] << "localhost:#{ENV['WEBPACK_PORT']}" - end - - config.csp = SecureHeaders::OPT_OUT - - config.cookies = { - secure: true, # mark all cookies as "Secure" - httponly: true, # mark all cookies as "HttpOnly" - samesite: { - lax: true, # SameSite setting. - }, - } - - # Temporarily disabled until we configure pinning. See GitHub issue #1895. - # config.hpkp = { - # report_only: false, - # max_age: 60.days.to_i, - # include_subdomains: true, - # pins: [ - # { sha256: 'abc' }, - # { sha256: '123' } - # ] - # } -end - -# A tiny middleware that calls a block on each request. When both: -# 1) the block returns true -# 2) the response is a 2XX response -# It deletes the Content-Security-Policy header. This is intended so that we can override -# SecureHeaders behavior and not set the headers on asset files, because the headers should be set -# on the document that links to the assets, not the assets themselves. -class SecureHeaders::RemoveContentSecurityPolicy - # @yieldparam [Rack::Request] request - def initialize(app, &block) - @app = app - @block = block - end - - def call(env) - status, headers, body = @app.call(env) - - if (200...300).cover?(status) && @block.call(Rack::Request.new(env)) - headers.delete('Content-Security-Policy') - end - - [status, headers, body] - end -end - -# We need this to be called after the SecureHeaders::Railtie adds its own middleware at the top -Rails.application.configure do |config| - config.middleware.insert_before( - SecureHeaders::Middleware, - SecureHeaders::RemoveContentSecurityPolicy, - ) do |request| - request.path.start_with?('/acuant/') - end -end diff --git a/spec/config/initializers/secure_headers_spec.rb b/spec/config/initializers/secure_headers_spec.rb new file mode 100644 index 00000000000..3695f20bcbb --- /dev/null +++ b/spec/config/initializers/secure_headers_spec.rb @@ -0,0 +1,14 @@ +RSpec.describe 'config.ssl_options' do + subject(:ssl_options) { Rails.application.config.ssl_options } + + it 'is configured to use Strict-Transport-Security (HSTS)' do + basic_app = lambda { |env| [200, {}, []] } + ssl_middleware = ActionDispatch::SSL.new(basic_app, **ssl_options) + + request = { 'HTTPS' => 'on' } + _status, headers, _body = ssl_middleware.call(request) + + expect(headers['Strict-Transport-Security']). + to eq('max-age=31556952; includeSubDomains; preload') + end +end diff --git a/spec/requests/headers_spec.rb b/spec/requests/headers_spec.rb index ed2b8cef25d..bbf84a83e92 100644 --- a/spec/requests/headers_spec.rb +++ b/spec/requests/headers_spec.rb @@ -32,13 +32,6 @@ end context 'secure headers' do - it 'includes Strict-Transport-Security (HSTS)' do - get root_path, headers: { 'HTTPS' => 'on' } - - expect(response.headers['Strict-Transport-Security']). - to eq('max-age=31536000; includeSubDomains; preload') - end - it 'sets the right values for X-headers' do get root_path