Repository: static-web-server/static-web-server (language: rust)

Issue:
Support HSTS and turn on by default
https web servers should support RFC 6797 "HTTP Strict Transport Security (HSTS)".

This should probably be on by default, and also default to a sensible time period (e.g. 2 years).  A command-line switch could optionally disable HSTS (e.g. `--hsts-seconds=0`).

https://www.globalsign.com/en/blog/what-is-hsts-and-how-do-i-use-it


Discussion / hints:
Yeah, that's good idea since this normally tends to happen at proxy level.

Fix patch:
diff --git a/src/config.rs b/src/config.rs
index 5966f1a2..b94ca73f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -108,4 +108,17 @@ pub struct Config {
     )]
     /// Enable directory listing for all requests ending with the slash character (‘/’).
     pub directory_listing: bool,
+
+    #[structopt(
+        long,
+        parse(try_from_str),
+        required_if("http2", "true"),
+        default_value_if("http2", Some("true"), "true"),
+        default_value = "false",
+        env = "SERVER_SECURITY_HEADERS"
+    )]
+    /// Enable security headers by default when HTTP/2 feature is activated.
+    /// Headers included: "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload" (2 years max-age),
+    /// "X-Frame-Options: DENY", "X-XSS-Protection: 1; mode=block" and "Content-Security-Policy: frame-ancestors 'self'".
+    pub security_headers: bool,
 }
diff --git a/src/handler.rs b/src/handler.rs
index e6f643a9..c755d46f 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -2,7 +2,7 @@ use http::StatusCode;
 use hyper::{Body, Request, Response};
 use std::{future::Future, path::PathBuf, sync::Arc};
 
-use crate::{compression, control_headers, cors, static_files};
+use crate::{compression, control_headers, cors, security_headers, static_files};
 use crate::{error_page, Error, Result};
 
 // It defines options for a request handler.
@@ -11,6 +11,7 @@ pub struct RequestHandlerOpts {
     pub compression: bool,
     pub dir_listing: bool,
     pub cors: Option<Arc<cors::Configured>>,
+    pub security_headers: bool,
 }
 
 // It defines the main request handler for Hyper service request.
@@ -50,6 +51,11 @@ impl RequestHandler {
                 .await
             {
                 Ok(mut resp) => {
+                    // Append Security Headers
+                    if self.opts.security_headers {
+                        security_headers::with_security_headers(&mut resp);
+                    }
+
                     // Auto compression based on the `Accept-Encoding` header
                     if self.opts.compression {
                         resp = compression::auto(method, headers, resp)?;
diff --git a/src/lib.rs b/src/lib.rs
index 1b3d95cf..b1b4ae7f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,6 +11,7 @@ pub mod error_page;
 pub mod handler;
 pub mod helpers;
 pub mod logger;
+pub mod security_headers;
 pub mod server;
 pub mod service;
 pub mod signals;
diff --git a/src/security_headers.rs b/src/security_headers.rs
new file mode 100644
index 00000000..c9f82f50
--- /dev/null
+++ b/src/security_headers.rs
@@ -0,0 +1,35 @@
+use http::header::{
+    CONTENT_SECURITY_POLICY, STRICT_TRANSPORT_SECURITY, X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS,
+    X_XSS_PROTECTION,
+};
+use hyper::{Body, Response};
+
+/// It appends security headers like `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` (2 years max-age),
+///`X-Frame-Options: DENY`, `X-XSS-Protection: 1; mode=block` and `Content-Security-Policy: frame-ancestors 'self'`.
+pub fn with_security_headers(resp: &mut Response<Body>) {
+    // Strict-Transport-Security (HSTS)
+    resp.headers_mut().insert(
+        STRICT_TRANSPORT_SECURITY,
+        "max-age=63072000; includeSubDomains; preload"
+            .parse()
+            .unwrap(),
+    );
+
+    // X-Frame-Options
+    resp.headers_mut()
+        .insert(X_FRAME_OPTIONS, "DENY".parse().unwrap());
+
+    // X-XSS-Protection
+    resp.headers_mut()
+        .insert(X_XSS_PROTECTION, "1; mode=block".parse().unwrap());
+
+    // X-Content-Type-Options
+    resp.headers_mut()
+        .insert(X_CONTENT_TYPE_OPTIONS, "nosniff".parse().unwrap());
+
+    // Content Security Policy (CSP)
+    resp.headers_mut().insert(
+        CONTENT_SECURITY_POLICY,
+        "frame-ancestors 'self'".parse().unwrap(),
+    );
+}
diff --git a/src/server.rs b/src/server.rs
index 5d549429..b0bb7352 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -89,9 +89,13 @@ impl Server {
             .set(helpers::read_file_content(opts.page50x.as_ref()))
             .expect("page 50x is not initialized");
 
+        // Security Headers option
+        let security_headers = opts.security_headers;
+        tracing::info!("security headers: enabled={}", security_headers);
+
         // Auto compression based on the `Accept-Encoding` header
         let compression = opts.compression;
-        tracing::info!("auto compression compression: enabled={}", compression);
+        tracing::info!("auto compression: enabled={}", compression);
 
         // Directory listing option
         let dir_listing = opts.directory_listing;
@@ -110,6 +114,7 @@ impl Server {
                 compression,
                 dir_listing,
                 cors,
+                security_headers,
             },
         });
 


Test patch: