4

We recently upgraded our Application to Rails 5.2. We are also using Turbolinks (together with the Rails Engine) and RailsUJS.

With Rails 5.2 we have new DSL for CSP (Content security policy). It is configured like this in initializers/content_security_policy.rb:

Rails.application.config.content_security_policy do |policy|
  policy.object_src  :none # disallow <object> tags (Good-bye Flash!)

  policy.default_src :self, :https
  policy.font_src    :self, :https, :data, Rails.configuration.application.asset_host, Rails.configuration.application.aws_s3_media_cdn
  policy.img_src     :self, :https, :data, Rails.configuration.application.asset_host, Rails.configuration.application.aws_s3_media_cdn
  policy.script_src  :self, :https, Rails.configuration.application.asset_host
  policy.style_src   :self, :https, Rails.configuration.application.asset_host

  if Rails.env.development? || Rails.env.test?
    # Fix for webpack-dev-server and ActionCable
    policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035", "ws://localhost:3000"
  end

  # Specify URI for violation reports
  # policy.report_uri "/csp-violation-report-endpoint"
end

# If you are using UJS then enable automatic nonce generation
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) 

This works fine. But I cannot get Turbolinks and RailsUJS to work together.

In app/views/layouts/application.html.haml we have (simplified):

!!!5
%html
  %head
    = csrf_meta_tags
    = csp_meta_tag
    = javascript_pack_tag 'application', 'data-turbolinks-track': :reload
    = action_cable_meta_tag
  %body
    = yield

Assuming we have a ujs button like this:

<a class="btn btn-danger" data-disable-with="Processing..." data-params="device%5Bstatus%5D=inactive" data-remote="true" rel="nofollow" data-method="patch" href="/devices/1">Shutdown</a>

And a controller looking very simple:

class DevicesController < ApplicationController
  respond_to :html

  #...
  def update
    @device.update(device_params)

    respond_with @device, location: :devices
  end
end

This works fine and we get a proper response in browser coming from the Turbolinks rails engine:

Turbolinks.clearCache()
Turbolinks.visit("http://localhost:3000/devices", {"action":"replace"})

But we get the error:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' https: 'nonce-QAz+FlHz5wo0IwU5sIMZ/w==' 'nonce-IsrK1b0jw1w7cqRhHeZ7ug==' 'nonce-Rpl8hMBgap79cfwdlXXwjA==' 'nonce-1Wq7MbBEYMDCkEWGexwQ9Q==' 'nonce-EUL22iiKHn0hkNuW3fpkbA==' 'nonce-F5Vg50g0JvAvkXHHu+p0qw==' 'nonce-slHxjCy9VVEvvoIcJ870lg==' 'nonce-lboTgbdLG4iCgUozIK4LPQ==' 'nonce-K9yAPOgjZDXRTvnJb3glTA==' 'nonce-ux2kfUZjU/nxJn30LaTFjQ==' 'nonce-8E8cTAX+jWNpvl5lw0Ydjw==' 'nonce-BvJ4wU3AqjZRWY930+W8kg==' 'nonce-PsS01n7AnjmiThKQJFzUBA==' 'nonce-RCoOSLXbx6Cj8aw+LuBSwA==' 'nonce-o5MfDl/crSPzjSyMzIvXNA==' 'nonce-s8NPaOETMpU2f48LR2SuqQ==' 'nonce-Omuo2P68l09PTBFxmk4DkA==' 'nonce-N3YQfaIuPSrURB8jhVz3Sw==' 'nonce-Ts4Bp4GUqawLcHI1mRLcxw==' 'nonce-fTZ6W2u9eh8K5yCJMPfJGg==' 'nonce-1ST0058rq41fDhw8CforxA==' 'nonce-tA+jUJ1x841ZseUUjvQn9w==' 'nonce-CVjBLiByDSqBNHdG6/izBA==' 'nonce-1z6mH6xtPajsxVmojM8SNA==' 'nonce-0zlDfL8I0go9aII/DGZUzg==' 'nonce-WOrw4qdxeKfZQ1R7xUfdbQ==' 'nonce-G8kmjJA5E35Asgy6mj80PQ=='". Either the 'unsafe-inline' keyword, a hash ('sha256-9KVlOPCQBe0v+kIJoBA6hi7N+aI2yVDUXS9gYk4PizU='), or a nonce ('nonce-...') is required to enable inline execution.

So the first question here: Where are those many nonces coming from?

Another investigation: After I call Turbolinks.visit in the javascript console, the csp-nonce and csrf-token values are changing. I think that has to be the problem. Since Rails UJS uses the original value to add nonce to the inline javascript tag it creates. But after everything gets rendered the nonce has changed. So the inline tag is no longer valid. How can I avoid this?

-- NOTE: We are setting up Turbolinks and RailsUJS via webpacker like this:

import Turbolinks from 'turbolinks'
import Rails from 'rails-ujs'
// Start application
Rails.start(); // Rails ujs
Turbolinks.start(); // Turbolinks, obviously...

Thanks in advance,
spa

Spa
  • 473
  • 4
  • 10

2 Answers2

4

Follow up:

The multitude of nonces was a bug in rails that has been corrected in Rails 5.2.1.

Hank Snow
  • 524
  • 5
  • 6
2

You are getting that error because somewhere in the page you're using inline script, like:

<script>
  ...js
</script>

To get away from this error you need to either include the 'unsafe-inline', a hash ('sha256-...'), or a nonce ('nonce-...') in your script-src CSP directive. For the details see: https://content-security-policy.com/

Better yet just remove the plain script from page body into a separate javascript file.

Where are those many nonces coming from?

These nonces are added by Rails because you have enabled automatic nonce generation for UJS.

the csp-nonce and csrf-token values are changing

That is how they work. If nonces and CSRF tokens were static there would be no point in them.

NARKOZ
  • 27,203
  • 7
  • 68
  • 90
  • Unfortunately not. I don't have any inline scripts in my application. Only Google Tag Manager. But it is inserted with `javascript_tag nonce: true` and disabled in development. I know that nonces have to change. But not in a ujs request. If I go in with a debugger, I see that the injected inline-script from rails-ujs has the old nonce. But the head has a new one after response. So it is not valid. That is the problem. – Spa May 07 '18 at 07:17
  • The error clearly indicates that you've an inline script in your source. Maybe you're inserting it dynamically via some other script? – NARKOZ May 07 '18 at 07:21