From 728f1d2a7a3005990bd15d13bae33f79c621cb23 Mon Sep 17 00:00:00 2001 From: Kazuyoshi Kato Date: Mon, 6 Jan 2020 20:46:00 -0800 Subject: [PATCH 1/3] Move static HTTP response headers to nginx.conf We'd like to have these headers on the FastBoot server as well. --- config/nginx.conf.erb | 4 ++++ src/middleware/security_headers.rs | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/config/nginx.conf.erb b/config/nginx.conf.erb index fe5b2799c11..0253c8c1618 100644 --- a/config/nginx.conf.erb +++ b/config/nginx.conf.erb @@ -121,6 +121,10 @@ http { expires max; } + add_header X-Content-Type-Options "no-sniff"; + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header Strict-Transport-Security "max-age=31536000" always; add_header Vary 'Accept, Accept-Encoding, Cookie'; proxy_set_header Host $http_host; diff --git a/src/middleware/security_headers.rs b/src/middleware/security_headers.rs index 1641c3bbce5..23e18ec7b36 100644 --- a/src/middleware/security_headers.rs +++ b/src/middleware/security_headers.rs @@ -13,12 +13,6 @@ impl SecurityHeaders { pub fn new(uploader: &Uploader) -> Self { let mut headers = HashMap::new(); - headers.insert("X-Content-Type-Options".into(), vec!["nosniff".into()]); - - headers.insert("X-Frame-Options".into(), vec!["SAMEORIGIN".into()]); - - headers.insert("X-XSS-Protection".into(), vec!["1; mode=block".into()]); - let s3_host = match *uploader { Uploader::S3 { ref bucket, From ece67ecd46e319ed37d51501ff2d7880d26781df Mon Sep 17 00:00:00 2001 From: Kazuyoshi Kato Date: Wed, 29 Jan 2020 07:07:58 -0800 Subject: [PATCH 2/3] Add Content-Security-Policy on nginx.conf.erb It allows us to share the header between Rust and FastBoot server. --- config/nginx.conf.erb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/config/nginx.conf.erb b/config/nginx.conf.erb index 0253c8c1618..cae7583a38a 100644 --- a/config/nginx.conf.erb +++ b/config/nginx.conf.erb @@ -1,3 +1,21 @@ +<% +def s3_host(env) + cdn = env['S3_CDN'] + if cdn and !cdn.empty? + return cdn + end + + region = env['S3_REGION'] + bucket = env['S3_BUCKET'] + + if region and !region.empty? + region = "-#{region}" + end + + return "#{bucket}.s3#{region}.amazonaws.com" +end +%> + daemon off; #Heroku dynos have at least 4 cores. worker_processes <%= ENV['NGINX_WORKERS'] || 4 %>; @@ -121,9 +139,10 @@ http { expires max; } - add_header X-Content-Type-Options "no-sniff"; + add_header X-Content-Type-Options "nosniff"; add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; + add_header Content-Security-Policy "default-src 'self'; connect-src 'self' https://docs.rs https://<%= s3_host(ENV) %>; script-src 'self' 'unsafe-eval' https://www.google.com; style-src 'self' https://www.google.com https://ajax.googleapis.com; img-src *; object-src 'none'"; add_header Strict-Transport-Security "max-age=31536000" always; add_header Vary 'Accept, Accept-Encoding, Cookie'; From 93ea166ddaf15db0c9ac886abfba5fffdca1c860 Mon Sep 17 00:00:00 2001 From: Kazuyoshi Kato Date: Wed, 5 Feb 2020 22:07:36 -0800 Subject: [PATCH 3/3] Completely remove SecurityHeaders middleware Now the functionality is owned by Nginx. --- src/middleware.rs | 5 --- src/middleware/security_headers.rs | 60 ------------------------------ 2 files changed, 65 deletions(-) delete mode 100644 src/middleware/security_headers.rs diff --git a/src/middleware.rs b/src/middleware.rs index 09b51e626e4..bfd62d3ea50 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -15,7 +15,6 @@ use self::debug::*; use self::ember_index_rewrite::EmberIndexRewrite; use self::head::Head; use self::log_connection_pool_status::LogConnectionPoolStatus; -use self::security_headers::SecurityHeaders; use self::static_or_continue::StaticOrContinue; pub mod app; @@ -28,7 +27,6 @@ mod head; mod log_connection_pool_status; mod log_request; mod require_user_agent; -mod security_headers; mod static_or_continue; use conduit_conditional_get::ConditionalGet; @@ -74,9 +72,6 @@ pub fn build_middleware(app: Arc, endpoints: R404) -> MiddlewareBuilder { env == Env::Production, )); - if env == Env::Production { - m.add(SecurityHeaders::new(&config.uploader)); - } m.add(AppMiddleware::new(app)); // Parse and save the user_id from the session cookie as part of the authentication logic diff --git a/src/middleware/security_headers.rs b/src/middleware/security_headers.rs deleted file mode 100644 index 23e18ec7b36..00000000000 --- a/src/middleware/security_headers.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! Middleware that adds secuirty headers to http responses in production. - -use super::prelude::*; -use crate::Uploader; -use std::collections::HashMap; - -#[derive(Clone, Debug)] -pub struct SecurityHeaders { - headers: HashMap>, -} - -impl SecurityHeaders { - pub fn new(uploader: &Uploader) -> Self { - let mut headers = HashMap::new(); - - let s3_host = match *uploader { - Uploader::S3 { - ref bucket, - ref cdn, - .. - } => match *cdn { - Some(ref s) => s.clone(), - None => bucket.host(), - }, - _ => unreachable!( - "This middleware should only be used in the production environment, \ - which should also require an S3 uploader, QED" - ), - }; - - // It would be better if we didn't have to have 'unsafe-eval' in the `script-src` - // policy, but google charts (used for the download graph on crate pages) uses `eval` - // to load scripts. Remove 'unsafe-eval' if google fixes the issue: - // https://github.com/google/google-visualization-issues/issues/1356 - // or if we switch to a different graph generation library. - headers.insert( - "Content-Security-Policy".into(), - vec![format!( - "default-src 'self'; \ - connect-src 'self' https://docs.rs https://{}; \ - script-src 'self' 'unsafe-eval' https://www.google.com; \ - style-src 'self' https://www.google.com https://ajax.googleapis.com; \ - img-src *; \ - object-src 'none'", - s3_host - )], - ); - - SecurityHeaders { headers } - } -} - -impl Middleware for SecurityHeaders { - fn after(&self, _: &mut dyn Request, mut res: Result) -> Result { - if let Ok(ref mut response) = res { - response.headers.extend(self.headers.clone()); - } - res - } -}