Rails 5.2:Rails UJS,Turbolinks和CSP

4
我们最近将我们的应用程序升级到了Rails 5.2。我们还使用Turbolinks(与Rails Engine一起)和RailsUJS。
在Rails 5.2中,我们有了新的CSP(内容安全策略)DSL。它在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) 

这很好运作。但我无法让Turbolinks和RailsUJS一起工作。
app/views/layouts/application.html.haml中,我们有以下代码(简化):
!!!5
%html
  %head
    = csrf_meta_tags
    = csp_meta_tag
    = javascript_pack_tag 'application', 'data-turbolinks-track': :reload
    = action_cable_meta_tag
  %body
    = yield

假设我们有一个像这样的UJS按钮:

<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>

以下是一个看起来非常简单的控制器:

class DevicesController < ApplicationController
  respond_to :html

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

    respond_with @device, location: :devices
  end
end

这段代码正常工作,我们可以从Turbolinks Rails引擎中得到适当的响应。
Turbolinks.clearCache()
Turbolinks.visit("http://localhost:3000/devices", {"action":"replace"})

但我们得到了错误消息:

拒绝执行内联脚本,因为它违反了以下内容安全策略指令:"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=='"。要启用内联执行,需要 "unsafe-inline" 关键字、哈希 ("sha256-9KVlOPCQBe0v+kIJoBA6hi7N+aI2yVDUXS9gYk4PizU=") 或 nonce("nonce-...")。

因此,第一个问题是:这些许多nonce来自哪里?
另一项调查:在JavaScript控制台中调用Turbolinks.visit后,csp-nonce和csrf-token值会发生变化。我认为这就是问题所在。由于Rails UJS使用原始值将nonce添加到它创建的内联JavaScript标签中。但是在所有内容都被呈现之后,nonce已经改变了。因此,内联标记不再有效。如何避免这种情况?
注意:我们正在通过webpacker设置Turbolinks和RailsUJS,如下所示:
import Turbolinks from 'turbolinks'
import Rails from 'rails-ujs'
// Start application
Rails.start(); // Rails ujs
Turbolinks.start(); // Turbolinks, obviously...

提前致谢,
SPA


你是否通过Webpacker添加Rails-UJS? - rubendinho
2个回答

4

跟进:

大量的nonce是Rails中的一个bug,在Rails 5.2.1中已经得到修正。


那确实解决了我的问题。谢谢你的提示! - Spa

2
您之所以出现该错误,是因为页面中某处使用了内联脚本,例如:

您之所以出现该错误,是因为页面中某处使用了内联脚本,例如:

<script>
  ...js
</script>

要避免这个错误,您需要在script-src CSP指令中包括'unsafe-inline',哈希值('sha256-...')或者nonce('nonce-...')。详情请参见:https://content-security-policy.com/
更好的做法是将页面正文中的普通脚本移至单独的JavaScript文件中。

这些nonce从哪里来?

这些nonce是由Rails添加的,因为您已启用了UJS的自动nonce生成。

csp-nonce和csrf-token的值正在发生变化

这就是它们的工作方式。如果nonce和CSRF令牌是静态的,那么它们就没有任何意义。

很遗憾,我的应用程序中没有任何内联脚本,只有Google Tag Manager。但是它是通过javascript_tag nonce: true插入的,并在开发中禁用。我知道nonce必须更改,但不适用于ujs请求。如果我使用调试器进入,我会看到rails-ujs注入的内联脚本具有旧的nonce。但是响应后,头部有一个新的nonce。因此它无效。这就是问题所在。 - Spa
错误明确指出您的源代码中有一个内联脚本。也许您是通过其他脚本动态插入它的? - NARKOZ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接