-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SRI #301
Add SRI #301
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be worth adding a runtime dependency for this gem on "sprockets-rails > 3.2". All we have at the moment is "rails > 3.1" but with the integrity attribute we need sprockets-rails 3.2 or greater. I don't know if it'll break in earlier rails versions though, or worse it still works but produces html tags with integrity attributes reading "true" that would block them from being loaded in modern browsers. Worth checking I think.
@@ -84,8 +85,10 @@ def extractable_options?(options) | |||
|
|||
def stringify_keys(options) | |||
result = {} | |||
options.each_key do |key| | |||
result[key.to_s] = options[key] | |||
if !options.empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor nitpick: do we need this conditional? if options is .empty?
then the .each_key
iterator won't do anything, so it should be safe to leave this method as it was.
4d3df24
to
064488a
Compare
options.instance_of?(Hash) | ||
end | ||
|
||
def stringify_keys(options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we rely on 'active_support/core_ext/hash' we can call .stringify_keys
on the hash directly, so we don't need to implement this here. It's in rails 3.1 so doesn't break our dependency (see: http://guides.rubyonrails.org/v3.1/active_support_core_extensions.html#stringify_keys-and-stringify_keys).
"<#{name}#{options}/>" | ||
end | ||
|
||
def extract_options!(options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we added a require for activesupport/core/ext/array
as well as the hash one then we could also rely on Array#extract_options!
and not need to implement it here. It's also in rails 3.1 and wouldn't break our dependency (see: http://guides.rubyonrails.org/v3.1/active_support_core_extensions.html#options-extraction)
b5450ee
to
ef12bd6
Compare
sprockets-rails was extracted from rails 4.0+, so unfortunately it won't be very helpful to see a dependency on sprockets-rails if your version of rails is still 3.1. I've added details about how SRI can be used with |
c911477
to
f77832c
Compare
spec/support/examples/processor.rb
Outdated
describe "#exclude_sri_fields" do | ||
let(:options) { {"rel"=>"stylesheet", "media"=>"screen", "href"=>processor.asset_path(css_source), "integrity" => true, "crossorigin" => "anonymous" } } | ||
|
||
it "should parse the hash" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"should parse the hash" could be more descriptive. "removes integrity and crossorigin keys from the hash" perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
spec/support/examples/processor.rb
Outdated
describe "#tag_options" do | ||
let(:options) { {"rel"=>"stylesheet", "media"=>"screen", "href"=>processor.asset_path(css_source) } } | ||
|
||
it "should parse the hash" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"should parse the hash" could be more descriptive. "flattens the hash into a string of quoted html attributes" perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
13e1833
to
967298a
Compare
This will support work for adding SRI to our templates. This will let us add an 'integrity' attribute to both the stylesheet_link_tag and the javascript_include_tag. What this does It adds the ability to understand and convert stylesheet_link_tag and javascript_include_tag to html for the other languages in which our erb template is being compiled to (e.g. Django, Mustache, Liquid, Jinja)
How to test ----------- In the application using govuk_template, you will have to set `config.assets.debug = false` in config/environments/development.rb or run the application in production mode. This is because sprockets-rails checks for this flag when deciding if it should calculate the integrity attribute: https://github.com/rails/sprockets-rails/blob/10bc1bd096b39a3dd632571dd517788314657056/lib/sprockets/rails/helper.rb#L168 What is SRI ----------- SRI will add an integrity attribute on your script tags: <script src="https://example.com/example.css" integrity_no="sha384oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w" crossorigin="anonymous"></script> The example above is generated automatically by sprockets-rails in your project if you do this: <%= stylesheet_script_tag 'example', integrity: true %> The way the digest value for the integrity attribute is calculated is by applying SHA256 on the contents of the file. In that way, the browser can double check that the right file is being received and hasn’t been tampered with (which would mean the contents would have changed and the resulting digest would be incorrect). The problem with using this in govuk_template --------------------------------------------- The css files it provided by govuk_template (for example: to the static project), are still css.erb files. In which case the contents of those files will change when they are processed in static (specifically there’s multiple <%= asset_path(...)%>s in there). So here’s the problem illustrated: 1. The govuk_template gem compiles a file called govuk_template.css.erb which contains multiple asset_path lines, e.g. background-image: url(<%= asset_path 'images/govuk-crest.png' %>); 2. static receives this file and replaces all the asset_path lines with it’s own path, e.g. background-image: url(**http://static.dev.gov.uk/static**/images/govuk-crest-2x.png); Notice that this url will vary depending on the app that uses the gem => this means the contents of the resulting govuk_template.css will vary depending on which app is using the gem so the digest will also vary. This means you can’t calculate the digest inside the gem, and will have to be left to the apps to do it individually 3. You cannot use sprockets-rails in govuk_template directly because the assets it serves are also compiled into django, play, liquid, mustache and other crazy formats. So you can’t do this: <%= stylesheet_link_tag ‘govuk-template-print’, integrity: true%> and then leave it up to sprockets-rails to calculate the digest for you, because this will not work for django and all the other formats. They don’t know what stylesheet_tag is. Instead, this is what govuk_template does so it can be compiled into multiple languages: <link href="<%= asset_path "govuk-template-print.css" %>" media="print" rel="stylesheet" /> 4. You cannot calculate the integrity digest by hand in the gem, because of point 2 -> the content will change Proposed solution ----------------- We use stylesheet_link_tag and set integrity: true, but we leave it up to the apps using the gem to actually calculate the integrity digest. For the other languages (django, jijna, liquid, mustache) we have defined a stylesheet_include_tag method in their respective processors that will translate that tag into something they can understand: <link href="<%= asset_path "govuk-template-print.css" %>" media="print" rel="stylesheet" />
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super happy you've introduced tests for all the processors used in the compilation step. ➕ 💯
Do we need to back this out because of alphagov/government-frontend#368 If yes, do we also need to yank old versions? |
@fofr maybe we should back this out and re-release, but I'd say we don't need to yank old versions as it's not broken or a security risk or anything like that. Before we do anything though we need to investigate the impact more; it might be we're comfortable with the % of users it would affect and want to remerge the SRI stuff on government frontend. |
This is a manual revert, as the PR could not be reverted automatically. We're reverting this PR because there is a bug in the SRI implementation of Firefox versions upto 52 which at time of writing accounts for 0.7% of total traffic (~315k users). We still want to implement SRI, but for now we're holding off until we'd impact fewer users.
This is a manual revert, as the PR could not be reverted automatically. We're reverting this PR because there is a bug in the SRI implementation of Firefox versions upto 52 which at time of writing accounts for 0.7% of total traffic (~315k users). We still want to implement SRI, but for now we're holding off until we'd impact fewer users.
This is a manual revert, as the PR could not be reverted automatically. We're reverting this PR because there is a bug in the SRI implementation of Firefox versions upto 52 which at time of writing accounts for 0.7% of total traffic (~315k users). We still want to implement SRI, but for now we're holding off until we'd impact fewer users.
Trello card
Github issue
Based on a previous attempt to add SRI - see PR here
What this does
This PR enables govuk_template to use SRI for the css and js assets it is serving to other projects.
Background
What is SRI
SRI will add an
integrity
attribute on your script tags:The example above is generated automatically by sprockets-rails in your project if you do this:
<%= stylesheet_script_tag 'example', integrity: true %>
The way the digest value for the
integrity
attribute is calculated is by applying SHA256 on the contents of the file. In that way, the browser can double check that the right file is being received and hasn’t been tampered with (which would mean the contents would have changed and the resulting digest would be incorrect).The problem with using SRI in govuk_template
The css files provided by govuk_template (for example: to the static project) are still
css.erb
files. In which case the contents of those files will change when they are processed in static (specifically there’s multiple<%= asset_path(...)%>
s in there).So here’s the problem illustrated:
The
govuk_template
gem compiles a file calledgovuk_template.css.erb
which contains multipleasset_path
lines, e.g.background-image: url(<%= asset_path 'images/govuk-crest.png' %>);
static receives this file and replaces all the asset_path lines with it’s own path:
background-image: url(**http://static.dev.gov.uk/static**/images/govuk-crest-2x.png);
Notice that this url will vary depending on the app that uses the gem. As a result, the contents of the resulting
govuk_template.css
will vary depending on which app is using the gem so the digest will also vary.This means you can’t generate the digest inside the gem, and the calculation will have to be left to the apps to do individually.
You can't use sprockets-rails in govuk_template directly because the assets it serves are also compiled into django, play, liquid, mustache and other crazy formats.
So you can’t do this:
<%= stylesheet_link_tag ‘govuk-template-print’, integrity: true%>
and then leave it up to sprockets-rails to calculate the digest for you, because this will not work for django and all the other formats. They don’t know what
stylesheet_tag
is.Instead, this is what govuk_template does so it can be compiled into multiple languages:
<link href="<%= asset_path "govuk-template-print.css" %>" media="print" rel="stylesheet" />
You can't calculate the integrity digest by hand in the gem, because of point 2 -> the content of the file will change depending on which application is using it.
Proposed solution
We use
stylesheet_link_tag
and setintegrity: true
, but we leave it up to the apps using the gem to actually generate the integrity digest. For the other languages (django, jijna, liquid, mustache) we define astylesheet_include_tag
method in their respective processors that will translate that tag into something they can understand:<link href="<%= asset_path "govuk-template-print.css" %>" media="print" rel="stylesheet" />