diff --git a/Gemfile b/Gemfile index 6ae9086a5415cd90a41ca9f7ac0dfdb0548bd3a0..8a30a819660d562dca91c071c3b1709e59f16607 100644 --- a/Gemfile +++ b/Gemfile @@ -349,3 +349,6 @@ gem 'health_check', '~> 2.1.0' # System information gem 'vmstat', '~> 2.1.0' gem 'sys-filesystem', '~> 1.1.6' + +# Secure headers for Content Security Policy +gem 'secure_headers', '~> 3.3' diff --git a/Gemfile.lock b/Gemfile.lock index 9ec5fe1282064ee93244392a8b1aad51bfab47fd..f3f4d895ae4a9b2707a4bc4dfbcfc9823e3e365d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -645,6 +645,8 @@ GEM sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) + secure_headers (3.3.2) + useragent seed-fu (2.3.6) activerecord (>= 3.1) activesupport (>= 3.1) @@ -767,6 +769,7 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.9.0) + useragent (0.16.7) uuid (2.3.8) macaddr (~> 1.0) version_sorter (2.0.0) @@ -944,6 +947,7 @@ DEPENDENCIES sass-rails (~> 5.0.0) scss_lint (~> 0.47.0) sdoc (~> 0.3.20) + secure_headers (~> 3.3) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) sentry-raven (~> 1.1.0) diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb new file mode 100644 index 0000000000000000000000000000000000000000..9fd24a667cc2a79ecaf5559df04091104682d9b5 --- /dev/null +++ b/config/initializers/secure_headers.rb @@ -0,0 +1,109 @@ +# CSP headers have to have single quotes, so failures relating to quotes +# inside Ruby string arrays are irrelevant. +# rubocop:disable Lint/PercentStringArray +require 'gitlab/current_settings' +include Gitlab::CurrentSettings + +# If Sentry is enabled and the Rails app is running in production mode, +# this will construct the Report URI for Sentry. +if Rails.env.production? && current_application_settings.sentry_enabled + uri = URI.parse(current_application_settings.sentry_dsn) + CSP_REPORT_URI = "#{uri.scheme}://#{uri.host}/api#{uri.path}/csp-report/?sentry_key=#{uri.user}" +else + CSP_REPORT_URI = '' +end + +# Content Security Policy Headers +# For more information on CSP see: +# - https://gitlab.com/gitlab-org/gitlab-ce/issues/18231 +# - https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives +SecureHeaders::Configuration.default do |config| + # Mark all cookies as "Secure", "HttpOnly", and "SameSite=Strict". + config.cookies = { + secure: true, + httponly: true, + samesite: { + strict: true + } + } + 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" + config.referrer_policy = "origin-when-cross-origin" + config.csp = { + # "Meta" values. + report_only: true, + preserve_schemes: true, + + # "Directive" values. + # Default source allows nothing, more permissive values are set per-policy. + default_src: %w('none'), + # (Deprecated) Don't allow iframes. + frame_src: %w('none'), + # Only allow XMLHTTPRequests from the GitLab instance itself. + connect_src: %w('self'), + # Only load local fonts. + font_src: %w('self'), + # Load local images, any external image available over HTTPS. + img_src: %w(* 'self' data:), + # Audio and video can't be played on GitLab currently, so it's disabled. + media_src: %w('none'), + # Don't allow , , or elements. + object_src: %w('none'), + # Allow local scripts and inline scripts. + script_src: %w('unsafe-inline' 'unsafe-eval' 'self'), + # Allow local stylesheets and inline styles. + style_src: %w('unsafe-inline' 'self'), + # The URIs that a user agent may use as the document base URL. + base_uri: %w('self'), + # Only allow local iframes and service workers + child_src: %w('self'), + # Only submit form information to the GitLab instance. + form_action: %w('self'), + # Disallow any parents from embedding a page in an iframe. + frame_ancestors: %w('none'), + # Don't allow any plugins (Flash, Shockwave, etc.) + plugin_types: %w(), + # Blocks all mixed (HTTP) content. + block_all_mixed_content: true, + # Upgrades insecure requests to HTTPS when possible. + upgrade_insecure_requests: true + } + + # Reports are sent to Sentry if it's enabled. + if current_application_settings.sentry_enabled + config.csp[:report_uri] = %W(#{CSP_REPORT_URI}) + end + + # Allow Bootstrap Linter in development mode. + if Rails.env.development? + config.csp[:script_src] << "maxcdn.bootstrapcdn.com" + end + + # reCAPTCHA + if current_application_settings.recaptcha_enabled + config.csp[:script_src] << "https://www.google.com/recaptcha/" + config.csp[:script_src] << "https://www.gstatic.com/recaptcha/" + config.csp[:frame_src] << "https://www.google.com/recaptcha/" + config.x_frame_options = "SAMEORIGIN" + end + + # Gravatar + if current_application_settings.gravatar_enabled? + config.csp[:img_src] << "www.gravatar.com" + config.csp[:img_src] << "secure.gravatar.com" + config.csp[:img_src] << Gitlab.config.gravatar.host + end + + # Piwik + if Gitlab.config.extra.has_key?('piwik_url') && Gitlab.config.extra.has_key?('piwik_site_id') + config.csp[:script_src] << Gitlab.config.extra.piwik_url + config.csp[:img_src] << Gitlab.config.extra.piwik_url + end + + # Google Analytics + if Gitlab.config.extra.has_key?('google_analytics_id') + config.csp[:script_src] << "https://www.google-analytics.com" + end +end