Implement CSP with secure_headers gem

pull/5884/head
Oliver Günther 7 years ago
parent 1ea9648283
commit 06472450c6
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 3
      Gemfile
  2. 4
      Gemfile.lock
  3. 10
      app/cells/rails_cell.rb
  4. 13
      app/helpers/application_helper.rb
  5. 2
      app/views/boards/show.html.erb
  6. 4
      app/views/custom_styles/_inline_css.erb
  7. 2
      app/views/layouts/angular.html.erb
  8. 2
      app/views/layouts/base.html.erb
  9. 4
      app/views/layouts/help.html.erb
  10. 3
      app/views/my/page_layout.html.erb
  11. 2
      app/views/projects/_form.html.erb
  12. 4
      app/views/projects/form/attributes/_identifier.html.erb
  13. 7
      app/views/queries/_filters.html.erb
  14. 2
      app/views/settings/_display.html.erb
  15. 2
      app/views/timelog/index.html.erb
  16. 1
      app/views/users/deletion_info.html.erb
  17. 2
      app/views/wiki/edit_parent_page.html.erb
  18. 2
      app/views/work_packages/bulk/edit.html.erb
  19. 41
      config/initializers/secure_headers.rb
  20. 2
      frontend/doc/MISC.md
  21. 2
      lib/redmine/wiki_formatting/textile/helper.rb
  22. 1
      spec/views/layouts/base.html.erb_spec.rb

@ -102,6 +102,9 @@ gem 'rack-protection', '~> 2.0.0'
# https://github.com/kickstarter/rack-attack
gem 'rack-attack', '~> 5.0.1'
# CSP headers
gem 'secure_headers', '~> 3.7.0'
# Providing health checks
gem 'okcomputer', '~> 1.16.0'

@ -538,6 +538,8 @@ GEM
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
selenium-webdriver (3.6.0)
secure_headers (3.7.0)
useragent
childprocess (~> 0.5)
rubyzip (~> 1.0)
shoulda-context (1.2.2)
@ -589,6 +591,7 @@ GEM
unicorn-worker-killer (0.4.4)
get_process_mem (~> 0)
unicorn (>= 4, < 6)
useragent (0.16.8)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
@ -711,6 +714,7 @@ DEPENDENCIES
sass (= 3.5.1)
sass-rails (~> 5.0.6)
selenium-webdriver (~> 3.6)
secure_headers (~> 3.7.0)
shoulda-context (~> 1.2)
shoulda-matchers (~> 3.1)
simplecov (~> 0.14.0)

@ -2,6 +2,7 @@ class RailsCell < Cell::ViewModel
include Escaped
include ApplicationHelper
include ActionView::Helpers::TranslationHelper
include SecureHeaders::ViewHelpers
self.view_paths = ['app/cells/views']
@ -32,6 +33,11 @@ class RailsCell < Cell::ViewModel
end
def show
# Set the _request from AS::Controller that doesn't get passed into the rails cell.
# Workaround for when using middlewares such as SecureHeaders that relies on it,
# but don't use the request method itself.
@_request = request
render
end
@ -52,4 +58,8 @@ class RailsCell < Cell::ViewModel
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
super
end
def request
controller.request
end
end

@ -469,7 +469,7 @@ module ApplicationHelper
def calendar_for(field_id)
include_calendar_headers_tags
javascript_tag("jQuery(function() { jQuery('##{field_id}').datepicker(); })")
nonced_javascript_tag("jQuery(function() { jQuery('##{field_id}').datepicker(); })")
end
def include_calendar_headers_tags
@ -490,8 +490,9 @@ module ApplicationHelper
'""'
end
# FIXME: Get rid of this abomination
js = "var CS = { lang: '#{current_language.to_s.downcase}', firstDay: #{start_of_week} };"
javascript_tag(js)
nonced_javascript_tag do
"var CS = { lang: '#{current_language.to_s.downcase}', firstDay: #{start_of_week} };".html_safe
end.html_safe
end
end
end
@ -499,7 +500,8 @@ module ApplicationHelper
# Returns the javascript tags that are included in the html layout head
def user_specific_javascript_includes
tags = ''
tags += javascript_tag(%{
tags += nonced_javascript_tag do
%{
window.openProject = new OpenProject({
urlRoot : '#{OpenProject::Configuration.rails_relative_url_root}',
environment: '#{Rails.env}',
@ -507,7 +509,8 @@ module ApplicationHelper
});
I18n.defaultLocale = "#{I18n.default_locale}";
I18n.locale = "#{I18n.locale}";
})
}.html_safe
end
tags.html_safe
end

@ -27,7 +27,7 @@ See docs/COPYRIGHT.rdoc for more details.
++#%>
<%= include_gon %>
<%= include_gon(nonce: content_security_policy_nonce(:script)) %>
<div id="add-message" style="display:none;">
<% if authorize_for('messages', 'new') %>

@ -27,7 +27,7 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<style>
<% nonced_javascript_tag do %>
<% if CustomStyle.current.logo.present? %>
#logo .home-link {
margin-top: 12px;
@ -77,4 +77,4 @@ See doc/COPYRIGHT.rdoc for more details.
}
<% end %>
<% end %>
</style>
<% end %>

@ -28,7 +28,7 @@ See docs/COPYRIGHT.rdoc for more details.
++#%>
<%= content_for :header_tags do %>
<%= include_gon %>
<%= include_gon(nonce: content_security_policy_nonce(:script)) %>
<!-- plug-in specific tags -->
<%= call_hook :view_work_package_overview_attributes %>
<% end -%>

@ -176,7 +176,7 @@ See docs/COPYRIGHT.rdoc for more details.
</div>
<%# Properly decides main menu expanded state before its drawn. Fixes flickering side menu
where menu is first expanded, then being collapsed in angular. %>
<%= javascript_tag do %>
<%= nonced_javascript_tag do %>
(function($) {
var wrapper = $('#wrapper');
if (window.sessionStorage.getItem('openproject:navigation-toggle') === 'collapsed') {

@ -31,9 +31,9 @@ See docs/COPYRIGHT.rdoc for more details.
<head>
<title><%= h html_title %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<style type="text/css">
<%= nonced_style_tag do %>
<%= yield(:styles) %>
</style>
<% end %>
</head>
<body>
<%= yield %>

@ -27,6 +27,7 @@ See docs/COPYRIGHT.rdoc for more details.
++#%>
<script language="JavaScript">
<%= nonced_javascript_tag do %>
jQuery(document).ready(function($) {
// Canonical list of containers that will exchange draggable elements.
var containers = jQuery('.dragula-container').toArray();
@ -61,7 +62,7 @@ See docs/COPYRIGHT.rdoc for more details.
});
});
});
</script>
<% end %>
<%= toolbar title: l(:label_my_page) do %>
<%= styled_form_tag({ action: "add_block" }, remote: true) do %>

@ -50,7 +50,7 @@ See docs/COPYRIGHT.rdoc for more details.
<%= render partial: "projects/form/modules", locals: { form: f } %>
</section>
<%= javascript_tag 'observeProjectModules();' %>
<%= nonced_javascript_tag 'observeProjectModules();' %>
<% end %>
<% if ((project.module_enabled?('work_package_tracking')) && render_types) %>

@ -38,13 +38,13 @@ See docs/COPYRIGHT.rdoc for more details.
<% if identifier_editable %>
<%= form.text_field :identifier, container_class: '-middle'%>
<script type="text/javascript" charset="utf-8">
<%= nonced_javascript_tag do %>
projectIdentifierMaxLength = <%= id_max_length %>;
projectIdentifierDefault = '<%= id %>';
projectIdentifierLocked = false;
observeProjectIdentifier();
observeProjectName();
</script>
<% end %>
<div class="form--field-instructions">
<%= l(:text_length_between, min: 1,

@ -27,7 +27,7 @@ See docs/COPYRIGHT.rdoc for more details.
++#%>
<%# TODO Remove when not needed any more %>
<script type="text/javascript">
<%= nonced_javascript_tag do %>
//<![CDATA[
function add_filter() {
var select = jQuery('#add_filter_select');
@ -102,7 +102,7 @@ See docs/COPYRIGHT.rdoc for more details.
jQuery(apply_filters_observer);
//]]>
</script>
<% end %>
<ul class="advanced-filters--filters">
<% query
.available_filters
@ -160,8 +160,9 @@ See docs/COPYRIGHT.rdoc for more details.
<% end %>
</div>
<script type="text/javascript">
<%= nonced_javascript_tag do %>
toggle_filter('<%= field %>');
</script>
<% end %>
</li>
<% end %>

@ -70,7 +70,7 @@ See docs/COPYRIGHT.rdoc for more details.
method: :post %>
</div>
<%= javascript_tag do -%>
<%= nonced_javascript_tag do -%>
jQuery(document).ready(function () {
Administration.init_language_selection_handling();
});

@ -27,7 +27,7 @@ See docs/COPYRIGHT.rdoc for more details.
++#%>
<%= include_gon %>
<%= include_gon(nonce: content_security_policy_nonce(:script)) %>
<%= toolbar title: l(:label_spent_time) do %>
<% if User.current.allowed_to?({controller: :timelog, action: :new}, @project) %>

@ -65,4 +65,3 @@ See docs/COPYRIGHT.rdoc for more details.
</section>
</div>
<% end %>

@ -42,7 +42,7 @@ See docs/COPYRIGHT.rdoc for more details.
{ class: "parent-select" } %>
</p>
<%= javascript_tag do -%>
<%= nonced_javascript_tag do -%>
jQuery(function() {
var parent_select = jQuery('#wiki_page_parent_id');
parent_select.attr('size', parent_select.children().length);

@ -115,7 +115,7 @@ See docs/COPYRIGHT.rdoc for more details.
</div>
</div>
<div id="parent_work_package_candidates" class="autocomplete"></div>
<%= javascript_tag "observeParentIssueField('#{work_packages_auto_complete_path(project_id: @project.id)}')" %>
<%= nonced_javascript_tag "observeParentIssueField('#{work_packages_auto_complete_path(project_id: @project.id)}')" %>
<% end %>
<div class="form--field">
<label class="form--label" for='work_package_start_date'><%= WorkPackage.human_attribute_name(:start_date) %></label>

@ -0,0 +1,41 @@
SecureHeaders::Configuration.default do |config|
config.cookies = {
secure: true,
httponly: true
}
# Add "; preload" and submit the site to hstspreload.org for best protection.
config.hsts = "max-age=#{20.years.to_i}; includeSubdomains"
config.x_frame_options = "DENY"
config.x_content_type_options = "nosniff"
config.x_xss_protection = "1; mode=block"
config.x_permitted_cross_domain_policies = "none"
config.referrer_policy = "origin-when-cross-origin"
# Valid for assets
assets_src = ["'self'"]
asset_host = OpenProject::Configuration.rails_asset_host
assets_src << asset_host if asset_host.present?
config.csp = {
preserve_schemes: true,
# Fallback when no value is defined
default_src: %w(https: 'self'),
# Allowed uri in <base> tag
base_uri: %w('self'),
# Allow fonts from self, asset host, or DATA uri
font_src: assets_src + %w(data:),
# Form targets can only be self
form_action: %w('self'),
# Allow iframe from vimeo (welcome video)
frame_src: %w(https://*.vimeo.com),
frame_ancestors: %w('self'),
# Allow images from anywhere
img_src: %w(*),
# Allow scripts from self (not inline, but)
script_src: %w('self'),
# Allow unsafe-inline styles
style_src: assets_src + %w('unsafe-inline'),
}
end

@ -19,7 +19,7 @@ There are three ways of passing information from Rails to `angular`:
This is included by all layouts in `<head>`:
```
<%= include_gon %>
<%= include_gon(nonce: content_security_policy_nonce(:script)) %>
```
`gon` will provide arbitrary settings from Rails to all JavaScript functionality, including `angular`. In an `angular` context a `ConfigurationService` is provided for picking up the settings.

@ -44,7 +44,7 @@ module Redmine
:'aria-label' => ::I18n.t('js.inplace.link_formatting_help'),
title: ::I18n.t('js.inplace.link_formatting_help')
javascript_tag(<<-EOF)
nonced_javascript_tag(<<-EOF)
// initialSetup the toolbar later, so that i18n-js has a chance to set the translations
// for the wiki-buttons first.
jQuery(document).ready(function(){

@ -198,7 +198,6 @@ describe 'layouts/base', type: :view do
render
expect(DesignColor.overwritten.size).to eq(1)
expect(response).to render_template partial: 'custom_styles/_inline_css'
expect(rendered).to match /--primary-color:\s*#{primary_color.get_hexcode}/
end
end

Loading…
Cancel
Save