Недавно мы обновили наше приложение до Rails 5.2. Мы также используем Turbolinks (вместе с Rails Engine) и RailsUJS.
С Rails 5.2 у нас появился новый DSL для CSP (Политика безопасности контента). Это настроено так в 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:
Turbolinks.clearCache()
Turbolinks.visit("http://localhost:3000/devices", {"action":"replace"})
Но мы получаем ошибку:
Отказ от выполнения встроенного сценария, поскольку он нарушает следующую директиву политики безопасности содержимого: "script-src 'self' https: 'nonce-QAz + FlHz5wo0IwU5sIMZ / w ==' 'nonce-IsrK1b0jw1w7cqRhHeZ7ug ==' nonce-RwlAxfx8 = ' '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-ТА + jUJ1x841ZseUUjvQn9w =='' nonce-CVjBLiByDSqBNHdG6 / izBA == '' nonce-1z6mH6xtPajsxVmojM8SNA == '' nonce-0zlDfL8I0go9aII / DGZUzg == '' nonce-WOrw4qdxeKf'Ud17 = G0RQ1 8kmjJA5E35Asgy6mj80PQ == '». Для включения встроенного выполнения требуется ключевое слово unsafe-inline, хеш (sha256-9KVlOPCQBe0v + kIJoBA6hi7N + aI2yVDUXS9gYk4PizU = ') или одноразовый номер (' nonce -... ') для включения встроенного выполнения.
Итак, первый вопрос здесь: откуда взялись эти многоразовые числа?
Еще одно расследование: после того, как я вызвал Turbolinks.visit в консоли javascript, значения csp-nonce
и csrf-token
меняются. Я думаю, что это должно быть проблемой. Поскольку Rails UJS использует исходное значение, чтобы добавить nonce во встроенный тег javascript, который он создает. Но после того, как все оказано, одноразовый номер изменился Таким образом, встроенный тег больше не действителен.
Как я могу избежать этого?
-
ПРИМЕЧАНИЕ. Мы настраиваем Turbolinks и RailsUJS через веб-упаковщик следующим образом:
import Turbolinks from 'turbolinks'
import Rails from 'rails-ujs'
// Start application
Rails.start(); // Rails ujs
Turbolinks.start(); // Turbolinks, obviously...
Заранее спасибо,
спа