Skip to content
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

Secureheaders as rack middleware? #167

Closed
rlwinter opened this issue Sep 7, 2015 · 17 comments
Closed

Secureheaders as rack middleware? #167

rlwinter opened this issue Sep 7, 2015 · 17 comments

Comments

@rlwinter
Copy link

rlwinter commented Sep 7, 2015

I'd like to insert secureheaders as a rack middleware app in a rails stack. I think I need an equivalent to the SecureHeaders::RailtIe code to override conflicting headers set elsewhere in the Rails code. Is this usage of Secureheaders supported by the current codebase?

@oreoshake
Copy link
Contributor

I'm not sure I understand. Did you mean to say rack middleware in a non-rails stack? CSP is set in the middleware in case script hashes are used.

Can you describe your use case a little more in detail so I can provide a better answer? i.e. what about the current setup is not ideal?

@rlwinter
Copy link
Author

No, I mean rack middleware in a rails stack. I want to be absolutely sure that all HTTP responses pass through secureheaders so that secureheaders overrides any other settings. I'm fairly sure that requests handled by Rails Engines and api's built with Grape (i.e. things mounted in routes.rb) do not invoke the ActionController so 'ensure_security_headers' in application_controller.rb will not protect those responses. Maybe secureheaders will do what I want so seeing an example would help a lot. My google searches haven't turned up anything.

@HoneyryderChuck
Copy link

👍

@oreoshake
Copy link
Contributor

Ah, I see. Thank you for elaborating. I'm not sure how railties would be involved (I've used them exactly once in my life) but you can use the header classes directly in a middleware. That being said, secureheaders could be augmented to make this a little less hacky:

::SecureHeaders::Configuration.configure do |config|
  config.hsts = {:max_age => 20.years.to_i, :include_subdomains => true}
  config.x_frame_options = 'DENY'
  config.x_content_type_options = "nosniff"
  config.x_xss_protection = {:value => 1, :mode => 'block'}
  config.x_download_options = 'noopen'
  config.x_permitted_cross_domain_policies = 'none'
  config.csp = {
    :default_src => "https: self",
    :enforce => true,
    :frame_src => "https: http:.twimg.com http://itunes.apple.com",
    :img_src => "https:",
    :report_uri => '//example.com/uri-directive'
  }
  config.hpkp = {
    :max_age => 60.days.to_i,
    :include_subdomains => true,
    :report_uri => '//example.com/uri-directive',
    :pins => [
      {:sha256 => 'abc'},
      {:sha256 => '123'}
    ]
  }
end

class MySecureHeaders
  def initialize(app)
  @app = app
 end

 def call(env)
   status, headers, response = @app.call(env)
   csp = SecureHeaders::ContentSecurityPolicy.new(::SecureHeaders::Configuration.csp)
   headers[csp.name] = csp.value
   hsts = SecureHeaders::StrictTransportSecurity.new(::SecureHeaders::Configuration.hsts)
   headers[hsts.name] = hsts.value
   x_xss = SecureHeaders::XXssProtection.new(::SecureHeaders::Configuration.x_xss_protection)
   headers[x_xss.name] = x_xss.value

  # ... set the remaining headers manually

   [status, headers, [response.body]]
 end
end


module Testapp
  class Application < Rails::Application
    config.middleware.use MySecureHeaders
  end
end
  1. Having to supply SecureHeaders::Configuration.<header setting name> is pretty bad. I'm sure this could be cleaned up easily.
  2. Having a convenience method that builds all of the headers and adds them to the headers hash.

PRs welcome!

@oreoshake
Copy link
Contributor

To be clear, I'd rather fix the code to support this use case rather than adding the above example to any README

@oreoshake
Copy link
Contributor

Perhaps a convenience method that returns a hash of all of the headers that can be merged into the rack headers.

@oreoshake
Copy link
Contributor

@rlwinter @TiagoCardoso1983 how does the above sound to you? Does it satisfy your use case with a satisfactory API?

@HoneyryderChuck
Copy link

@oreoshake , anyway you do it, I think the cleanest way is to move your code away from rails and closer to the rack, as it concerns headers and stuff which is usually dealt with, this way you make this gem compatible with any rack framework.

@oreoshake
Copy link
Contributor

I disagree. Moving closer to rack yes, moving away from rails no. CSP is often not something that can be applied globally unless you want an overly permissive policy. That is why the proposal for adding this to rails core as a middleware has stalled out. Having convenience methods inside of controllers is very handy for one-off CSP config changes. Sure you can do this in rack too, but I'm not really a rack person and haven't really heard any concrete suggestions on how to make it more rack friendly.

@rlwinter
Copy link
Author

@oreoshake A convenience method returning a hash of headers would be perfect for building a rack app version of SecureHeaders. Then the call method would be:

def call(env)
   status, headers, response = @app.call(env)

  # create or retrieve secure_headers hash here
  secure_headers = ?

   [status, headers.merge(secure_headers), [response.body]]
 end
end

I think the RailTies code led me off on a tangent since it explicitly deletes headers.

@rlwinter
Copy link
Author

@oreoshake I'm wondering about 2 items: one is that CSP might need to be split off from the secureheaders gem, since most of the secure headers are universal to an app, whereas CSP is specific to each HTTP response. The second is that the Asset Pipeline and views already have all the information needed to create the CSP header so what prevents generating the CSP headers from the pipeline? What am I missing?

@oreoshake
Copy link
Contributor

The asset pipeline does not have the necessary information to build a policy for any non-trivial application. For example, google analytics loads resources from two other domains. Twitter/facebook buttons do the same. The examples of this can go on and on, and sometime the host names are dynamic (yes I know this is terrible, but this is the internet). If you only serve JS from your domain (i.e. 'self`) then sure, it will work. Also, it would have to be able to detect instance of inline javascript and CSS. Not impossible, but probably very difficult.

So there's already 4 ways to do "per response" ways of settings headers. This is horribly undocumented, however it is fully supported but is rails specific. Again, I'd love to make this more rack friendly but I need concrete input or a pull request.

  1. manually setting headers like the above example or like this
  2. Any config value can be a lambda which gets an instance of the controller, which has the request.
  3. Using the secure_header_options_for controller instance method
  4. Or on a per-controller basis by adding ensure_security_headers csp: config

Lastly, from my understanding grape is for API requests. Hardly anyone sets CSP on API requests because they aren't rendered (typically) and the policy is often longer than the body of the response (sometimes the policy is longer than all of the rest of the response combined). If so, ANY policy for an API should be default-src 'none' which likely won't work for any other endpoints.

@oreoshake
Copy link
Contributor

I'll take a crack at the "return a hash of all headers" convenience method sometime soon.

@oreoshake
Copy link
Contributor

@oreoshake
Copy link
Contributor

I took a stab at this: #168

@rlwinter
Copy link
Author

Hey awesome!

I read over the code changes - they look pretty good to me!

Thanks,
-Robert

On Wed, Sep 23, 2015 at 6:20 PM, Neil Matatall [email protected]
wrote:

I took a stab at this: #168
#168


Reply to this email directly or view it on GitHub
#167 (comment)
.

@oreoshake
Copy link
Contributor

Closed via #168

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants