From c5167f1150dd602d0f84e695c5d4fe31b5dff5fc Mon Sep 17 00:00:00 2001 From: Tal Date: Thu, 30 Aug 2018 15:52:54 -0700 Subject: [PATCH] Add 'space_helmet' contrib module. --- Cargo.toml | 1 + contrib/lib/Cargo.toml | 7 + contrib/lib/src/lib.rs | 2 + contrib/lib/src/space_helmet/helmet.rs | 285 +++++++++++++++++++++++ contrib/lib/src/space_helmet/mod.rs | 88 +++++++ contrib/lib/src/space_helmet/policy.rs | 302 +++++++++++++++++++++++++ contrib/lib/tests/space_helmet.rs | 99 ++++++++ examples/space_helmet/Cargo.toml | 18 ++ examples/space_helmet/Rocket.toml | 4 + examples/space_helmet/private/cert.pem | 30 +++ examples/space_helmet/private/key.pem | 51 +++++ examples/space_helmet/src/hello.rs | 33 +++ scripts/test.sh | 1 + 13 files changed, 921 insertions(+) create mode 100644 contrib/lib/src/space_helmet/helmet.rs create mode 100644 contrib/lib/src/space_helmet/mod.rs create mode 100644 contrib/lib/src/space_helmet/policy.rs create mode 100644 contrib/lib/tests/space_helmet.rs create mode 100644 examples/space_helmet/Cargo.toml create mode 100644 examples/space_helmet/Rocket.toml create mode 100644 examples/space_helmet/private/cert.pem create mode 100644 examples/space_helmet/private/key.pem create mode 100644 examples/space_helmet/src/hello.rs diff --git a/Cargo.toml b/Cargo.toml index 9f7f11cb..963cbaae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "core/http/", "contrib/lib", "contrib/codegen", + "examples/space_helmet", "examples/cookies", "examples/errors", "examples/form_validation", diff --git a/contrib/lib/Cargo.toml b/contrib/lib/Cargo.toml index 992766bb..0e2dd051 100644 --- a/contrib/lib/Cargo.toml +++ b/contrib/lib/Cargo.toml @@ -21,6 +21,7 @@ json = ["serde", "serde_json"] msgpack = ["serde", "rmp-serde"] tera_templates = ["tera", "templates"] handlebars_templates = ["handlebars", "templates"] +space_helmet = ["time"] serve = [] # The barage of user-facing database features. @@ -66,8 +67,14 @@ r2d2_cypher = { version = "0.4", optional = true } redis = { version = "0.9", optional = true } r2d2_redis = { version = "0.8", optional = true } +# SpaceHelmet dependencies +time = { version = "0.1.40", optional = true } + [target.'cfg(debug_assertions)'.dependencies] notify = { version = "^4.0" } +[dev-dependencies] +rocket_codegen = { version = "0.4.0-dev", path = "../../core/codegen" } + [package.metadata.docs.rs] all-features = true diff --git a/contrib/lib/src/lib.rs b/contrib/lib/src/lib.rs index 8ff47c60..8459e9c5 100644 --- a/contrib/lib/src/lib.rs +++ b/contrib/lib/src/lib.rs @@ -24,6 +24,7 @@ //! * [tera_templates](templates) - Tera Templating //! * [uuid](uuid) - UUID (de)serialization //! * [${database}_pool](databases) - Database Configuration and Pooling +//! * [space_helmet](space_helmet) //! //! The recommend way to include features from this crate via Cargo in your //! project is by adding a `[dependencies.rocket_contrib]` section to your @@ -49,6 +50,7 @@ #[cfg(feature="templates")] pub mod templates; #[cfg(feature="uuid")] pub mod uuid; #[cfg(feature="databases")] pub mod databases; +#[cfg(feature = "space_helmet")] pub mod space_helmet; #[cfg(feature="databases")] extern crate rocket_contrib_codegen; #[cfg(feature="databases")] #[doc(hidden)] pub use rocket_contrib_codegen::*; diff --git a/contrib/lib/src/space_helmet/helmet.rs b/contrib/lib/src/space_helmet/helmet.rs new file mode 100644 index 00000000..411d91c2 --- /dev/null +++ b/contrib/lib/src/space_helmet/helmet.rs @@ -0,0 +1,285 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use rocket::fairing::{Fairing, Info, Kind}; +use rocket::http::Header; +use rocket::{Request, Response, Rocket}; + +use super::policy::*; + +/// A [`Fairing`](/rocket/fairing/trait.Fairing.html) +/// that adds HTTP headers to outgoing responses that control security features +/// on the browser. +/// +/// # Usage +/// +/// `SpaceHelmet` can be used in several ways. +/// +/// To use it with its [defaults](#method.default), create a new instance and +/// attach it to [Rocket](/rocket/struct.Rocket.html) +/// as shown. +/// +/// ```rust +/// # extern crate rocket; +/// # extern crate rocket_contrib; +/// use rocket_contrib::space_helmet::SpaceHelmet; +/// +/// let rocket = rocket::ignite().attach(SpaceHelmet::default()); +/// ``` +/// +/// To enable an additional header, call the method for that header, with the +/// policy for that header, before attach e.g. +/// +/// ```rust +/// # extern crate rocket; +/// # extern crate rocket_contrib; +/// use rocket_contrib::space_helmet::{SpaceHelmet, ReferrerPolicy}; +/// +/// let helmet = SpaceHelmet::default().referrer_policy(ReferrerPolicy::NoReferrer); +/// let rocket = rocket::ignite().attach(helmet); +/// ``` +/// +/// To disable a header, call the method for that header with `None` as +/// the policy. +/// +/// ```rust +/// # extern crate rocket; +/// # extern crate rocket_contrib; +/// use rocket_contrib::space_helmet::SpaceHelmet; +/// +/// let helmet = SpaceHelmet::default().no_sniff(None); +/// let rocket = rocket::ignite().attach(helmet); +/// ``` +/// +/// `SpaceHelmet` supports the builder pattern to configure multiple policies +/// +/// ```rust +/// # extern crate rocket; +/// # extern crate rocket_contrib; +/// use rocket_contrib::space_helmet::{HstsPolicy, ExpectCtPolicy, ReferrerPolicy, SpaceHelmet}; +/// +/// let helmet = SpaceHelmet::default() +/// .hsts(HstsPolicy::default()) +/// .expect_ct(ExpectCtPolicy::default()) +/// .referrer_policy(ReferrerPolicy::default()); +/// +/// let rocket = rocket::ignite().attach(helmet); +/// ``` +/// +/// # TLS and HSTS +/// +/// If TLS is enabled when a [Rocket](rocket/struct.Rocket.html) +/// is [launched](/rocket/fairing/trait.Fairing.html#method.on_launch) +/// in a non-development environment e.g. [Staging](rocket/struct.Config.html#method.staging) +/// or [Production](/rocket/struct.Config.html#method.production) +/// `SpaceHelmet` enables hsts with its default policy and issue a +/// warning. +/// +/// To get rid of this warning, set an [hsts](#method.hsts) policy if you are using tls. +pub struct SpaceHelmet<'a> { + expect_ct_policy: Option>, + no_sniff_policy: Option, + xss_protect_policy: Option>, + frameguard_policy: Option>, + hsts_policy: Option, + force_hsts_policy: Option, + force_hsts: AtomicBool, + referrer_policy: Option, +} + +// helper for Helmet.apply +macro_rules! try_apply_header { + ($self:ident, $response:ident, $policy_name:ident) => { + if let Some(ref policy) = $self.$policy_name { + if $response.set_header(policy) { + warn_!( + "(Space Helmet): set_header failed, found existing header \"{}\"", + Header::from(policy).name + ); + } + } + }; +} + +impl<'a> Default for SpaceHelmet<'a> { + /// Returns a new `SpaceHelmet` instance. See [table](/rocket_contrib/space_helmet/index.html#what-it-supports) for + /// a description of what policies are used by default. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// # extern crate rocket_contrib; + /// use rocket_contrib::space_helmet::SpaceHelmet; + /// + /// let helmet = SpaceHelmet::default(); + /// ``` + fn default() -> Self { + Self { + expect_ct_policy: None, + no_sniff_policy: Some(NoSniffPolicy::default()), + frameguard_policy: Some(FramePolicy::default()), + xss_protect_policy: Some(XssPolicy::default()), + hsts_policy: None, + force_hsts_policy: Some(HstsPolicy::default()), + force_hsts: AtomicBool::new(false), + referrer_policy: None, + } + } +} + +impl<'a> SpaceHelmet<'a> { + /// Same as [`SpaceHelmet::default()`]. + pub fn new() -> Self { + SpaceHelmet::default() + } + + /// Sets the [X-XSS-Protection]( + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) + /// header to the given `policy` or disables it if `policy == None`. + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// # extern crate rocket_contrib; + /// use rocket::http::uri::Uri; + /// use rocket_contrib::space_helmet::{SpaceHelmet, XssPolicy}; + /// + /// let report_uri = Uri::parse("https://www.google.com").unwrap(); + /// let helmet = SpaceHelmet::new().xss_protect(XssPolicy::EnableReport(report_uri)); + /// ``` + pub fn xss_protect>>>(mut self, policy: T) -> SpaceHelmet<'a> { + self.xss_protect_policy = policy.into(); + self + } + + /// Sets the [X-Content-Type-Options]( + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) + /// header to `policy` or disables it if `policy == None`. + /// # Example + /// + /// ```rust + /// + /// use rocket_contrib::space_helmet::{SpaceHelmet, NoSniffPolicy}; + /// + /// let helmet = SpaceHelmet::new().no_sniff(NoSniffPolicy::Enable); + /// ``` + pub fn no_sniff>>(mut self, policy: T) -> SpaceHelmet<'a> { + self.no_sniff_policy = policy.into(); + self + } + + /// Sets the [X-Frame-Options]( + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) + /// header to `policy`, or disables it if `policy == None`. + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// # extern crate rocket_contrib; + /// + /// use rocket::http::uri::Uri; + /// use rocket_contrib::space_helmet::{SpaceHelmet, FramePolicy}; + /// + /// let allow_uri = Uri::parse("https://www.google.com").unwrap(); + /// let helmet = SpaceHelmet::new().frameguard(FramePolicy::AllowFrom(allow_uri)); + /// ``` + pub fn frameguard>>>(mut self, policy: T) -> SpaceHelmet<'a> { + self.frameguard_policy = policy.into(); + self + } + + /// Sets the [Strict-Transport-Security]( + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) + /// header to `policy`, or disables it if `policy == None`. + /// # Example + /// + /// ```rust + /// use rocket_contrib::space_helmet::{SpaceHelmet, HstsPolicy}; + /// + /// let helmet = SpaceHelmet::new().hsts(HstsPolicy::default()); + /// ``` + pub fn hsts>>(mut self, policy: T) -> SpaceHelmet<'a> { + self.hsts_policy = policy.into(); + self + } + + /// Sets the [Expect-CT]( + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT) + /// header to `policy`, or disables it if `policy == None`. + /// ```rust + /// # extern crate rocket; + /// # extern crate rocket_contrib; + /// # extern crate time; + /// use rocket::http::uri::Uri; + /// use rocket_contrib::space_helmet::{SpaceHelmet, ExpectCtPolicy}; + /// use time::Duration; + /// + /// let report_uri = Uri::parse("https://www.google.com").unwrap(); + /// let helmet = SpaceHelmet::new() + /// .expect_ct(ExpectCtPolicy::ReportAndEnforce( + /// Duration::days(30), report_uri)); + /// ``` + pub fn expect_ct>>>(mut self, policy: T) -> SpaceHelmet<'a> { + self.expect_ct_policy = policy.into(); + self + } + + /// Sets the [Referrer-Policy]( + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) + /// header to `policy`, or disables it if `policy == None`. + /// ```rust + /// # extern crate rocket; + /// # extern crate rocket_contrib; + /// + /// use rocket_contrib::space_helmet::{ReferrerPolicy, SpaceHelmet}; + /// + /// let helmet = SpaceHelmet::new().referrer_policy(ReferrerPolicy::NoReferrer); + /// ``` + pub fn referrer_policy>>( + mut self, + policy: T, + ) -> SpaceHelmet<'a> { + self.referrer_policy = policy.into(); + self + } + + fn apply(&self, response: &mut Response) { + try_apply_header!(self, response, no_sniff_policy); + try_apply_header!(self, response, xss_protect_policy); + try_apply_header!(self, response, frameguard_policy); + try_apply_header!(self, response, expect_ct_policy); + try_apply_header!(self, response, referrer_policy); + if self.hsts_policy.is_some() { + try_apply_header!(self, response, hsts_policy); + } else { + if self.force_hsts.load(Ordering::Relaxed) { + try_apply_header!(self, response, force_hsts_policy); + } + } + } +} + +impl Fairing for SpaceHelmet<'static> { + fn info(&self) -> Info { + Info { + name: "Rocket SpaceHelmet (HTTP Security Headers)", + kind: Kind::Response | Kind::Launch, + } + } + + fn on_response(&self, _request: &Request, response: &mut Response) { + self.apply(response); + } + + fn on_launch(&self, rocket: &Rocket) { + if rocket.config().tls_enabled() + && !rocket.config().environment.is_dev() + && !self.hsts_policy.is_some() + { + warn_!("(Space Helmet): deploying with TLS without enabling hsts."); + warn_!("Enabling default HSTS policy."); + info_!("To disable this warning, configure an HSTS policy."); + self.force_hsts.store(true, Ordering::Relaxed); + } + } +} diff --git a/contrib/lib/src/space_helmet/mod.rs b/contrib/lib/src/space_helmet/mod.rs new file mode 100644 index 00000000..9a100841 --- /dev/null +++ b/contrib/lib/src/space_helmet/mod.rs @@ -0,0 +1,88 @@ +//! [`SpaceHelmet`] is a [`Fairing`](/rocket/fairing/trait.Fairing.html) that +//! turns on browsers security features by adding HTTP headers to all outgoing +//! responses. +//! +//! It provides a typed interface for http security headers and takes some +//! inspiration from [helmet](https://helmetjs.github.io/), a similar piece +//! of middleware for [express](https://expressjs.com). +//! +//! ### What it supports +//! +//! | HTTP Header | Description | Method | Enabled by Default? | +//! | --------------------------- | -------------------------------------- | ---------------------------------- | ------------------- | +//! | [X-XSS-Protection] | Prevents some reflected XSS attacks. | [`SpaceHelmet::xss_protect()`] | ✔ | +//! | [X-Content-Type-Options] | Prevents client sniffing of MIME type. | [`SpaceHelmet::no_sniff()`] | ✔ | +//! | [X-Frame-Options] | Prevents [clickjacking]. | [`SpaceHelmet::frameguard()`] | ✔ | +//! | [Strict-Transport-Security] | Enforces strict use of HTTPS. | [`SpaceHelmet::hsts()`] | ? | +//! | [Expect-CT] | Enables certificate transparency. | [`SpaceHelmet::expect_ct()`] | ✗ | +//! | [Referrer-Policy] | Enables referrer policy. | [`SpaceHelmet::referrer_policy()`] | ✗ | +//! +//! [X-XSS-Protection]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection +//! [X-Content-Type-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options +//! [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options +//! [Strict-Transport-Security]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security +//! [Expect-CT]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT +//! [Referrer-Policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy +//! [clickjacking]: https://en.wikipedia.org/wiki/Clickjacking +//! _? If tls is enabled when a [Rocket](/rocket/struct.Rocket.html) is +//! [launched()'ed](/rocket/fairing/trait.Fairing.html) +//! in a non-development environment e.g. [Staging](/rocket/struct.Config.html#method.staging) +//! or [Production](/rocket/struct.Config.html#method.production), `SpaceHelmet` enables hsts with its +//! default policy and outputs a warning._ +//! +//! ### Examples +//! +//! To apply the headers that are enabled by default, just create a new [`SpaceHelmet`] instance +//! and attach before launch. +//! +//! ```rust +//! # extern crate rocket; +//! # extern crate rocket_contrib; +//! use rocket_contrib::space_helmet::{SpaceHelmet}; +//! +//! let rocket = rocket::ignite().attach(SpaceHelmet::new()); +//! ``` +//! +//! Each header can be configured individually if desired. To enable a particular +//! header, call the method for the header on the [`SpaceHelmet`] instance. Multiple +//! method calls can be chained in a builder pattern as illustrated below. +//! +//! ```rust +//! # extern crate rocket; +//! # extern crate rocket_contrib; +//! use rocket::http::uri::Uri; +//! +//! // Every header has a corresponding policy type. +//! use rocket_contrib::space_helmet::{SpaceHelmet, FramePolicy, XssPolicy, HstsPolicy}; +//! +//! let site_uri = Uri::parse("https://mysite.example.com").unwrap(); +//! let report_uri = Uri::parse("https://report.example.com").unwrap(); +//! let helmet = SpaceHelmet::new() +//! .hsts(HstsPolicy::default()) // each policy has a default. +//! .no_sniff(None) // setting policy to None disables the header. +//! .frameguard(FramePolicy::AllowFrom(site_uri)) +//! .xss_protect(XssPolicy::EnableReport(report_uri)); +//! ``` +//! +//! #### Still have questions? +//! +//! * _What policy should I choose?_ Check out the links in the table +//! above for individual header documentation. The [helmetjs](https://helmetjs.github.io/) doc's +//! are also a good resource, and owasp has a collection of [references] on these headers. +//! +//! [references]: https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Headers +//! +//! * _Where I can I read more about using fairings?_ Check out the +//! [fairing](https://rocket.rs/guide/fairings/) section of the rocket guide. +//! +//! * _Do I only need those headers `SpaceHelmet` enables by default?_ Maybe, the other headers +//! can protect against many important vulnerabilities. Please consult their documentation and +//! other resources to find out if they are needed for your project. + +extern crate time; + +mod helmet; +mod policy; + +pub use self::helmet::*; +pub use self::policy::*; diff --git a/contrib/lib/src/space_helmet/policy.rs b/contrib/lib/src/space_helmet/policy.rs new file mode 100644 index 00000000..363d2c5e --- /dev/null +++ b/contrib/lib/src/space_helmet/policy.rs @@ -0,0 +1,302 @@ +use std::borrow::Cow; +use rocket::http::uri::Uri; +use rocket::http::Header; + +use super::time::Duration; + +/// The [Referrer-Policy] header tells the browser if should send all or part of URL of the current +/// page to the next site the user navigates to via the [Referer] header. This can be important +/// for security as the URL itself might expose sensitive data, such as a hidden file path or +/// personal identifier. +/// +/// [Referrer-Policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy +/// [Referer]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer +pub enum ReferrerPolicy { + /// Omits the `Referer` header (_SpaceHelmet default_). + NoReferrer, + + /// Omits the `Referer` header on connection downgrade i.e. following HTTP link from HTTPS site + /// (_Browser default_). + NoReferrerWhenDowngrade, + + /// Only send the origin of part of the URL, e.g. the origin of https://foo.com/bob.html is + /// https://foo.com + Origin, + + /// Send full URL for same-origin requests, only send origin part when replying + /// to [cross-origin] requests. + /// + /// [cross-origin]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS + OriginWhenCrossOrigin, + + /// Send full URL for same-origin requests only. + SameOrigin, + + /// Only send origin part of URL, only send if protocol security level remains the same e.g. + /// HTTPS to HTTPS. + StrictOrigin, + + /// Send full URL for same-origin requests. For cross-origin requests, only send origin + /// part of URL if protocl security level remains the same e.g. HTTPS to HTTPS. + StrictOriginWhenCrossOrigin, + + /// Send full URL for same-origin or cross-origin requests. _This will leak the full + /// URL of TLS protected resources to insecure origins. Use with caution._ + UnsafeUrl, + } + +/// Defaults to [`ReferrerPolicy::NoReferrer`]. Tells the browser Omit the `Referer` header. +impl Default for ReferrerPolicy { + fn default() -> ReferrerPolicy { + ReferrerPolicy::NoReferrer + } +} + +impl<'a, 'b> From<&'a ReferrerPolicy> for Header<'b> { + fn from(policy: &ReferrerPolicy) -> Header<'b> { + let policy_string = match policy { + ReferrerPolicy::NoReferrer => "no-referrer", + ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade", + ReferrerPolicy::Origin => "origin", + ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin", + ReferrerPolicy::SameOrigin => "same-origin", + ReferrerPolicy::StrictOrigin => "strict-origin", + ReferrerPolicy::StrictOriginWhenCrossOrigin => { + "strict-origin-when-cross-origin" + } + ReferrerPolicy::UnsafeUrl => "unsafe-url", + }; + + Header::new("Referrer-Policy", policy_string) + } +} + + +/// The [Expect-CT] header tells browser to enable [Certificate Transparency] checking, which +/// can detect and prevent the misuse of the site's certificate. Read all [`ExpectCtPolicy`] +/// documentation before use. +/// +/// [Certificate Transparency] +/// solves a variety of problems with public TLS/SSL certificate management and is valuable +/// measure if your standing up a public website. If your [getting started] with certificate +/// transparency, be sure that your [site is in compliance][getting started] before you turn on +/// enforcement with [`ExpectCtPolicy::Enforce`] or [`ExpectCtPolicy::ReportAndEnforce`] +/// otherwise the browser will stop talking to your site until you bring it into compliance +/// or [`Duration`] time elapses. _You have been warned_. +/// +/// +/// +/// [Expect-CT]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT +/// [Certificate Transparency]: http://www.certificate-transparency.org/what-is-ct +/// [getting started]: http://www.certificate-transparency.org/getting-started +pub enum ExpectCtPolicy<'a> { + /// Tells browser to enforce certificate compliance for [`Duration`] seconds. + /// Check if your site is in compliance before turning on enforcement. + /// (_SpaceHelmet_ default). + Enforce(Duration), + + /// Tells browser to report compliance violations certificate transparency for [`Duration`] + /// seconds. Doesn't provide any protection but is a good way make sure + /// things are working correctly before turning on enforcement in production. + Report(Duration, Uri<'a>), + + /// Enforces compliance and supports notification to if there has been a violation for + /// [`Duration`]. + ReportAndEnforce(Duration, Uri<'a>), +} + +/// Defaults to [`ExpectCtPolicy::Enforce(Duration::days(30))`], enforce CT +/// compliance, see [draft] standard for more. +/// +/// [draft]: https://tools.ietf.org/html/draft-ietf-httpbis-expect-ct-03#page-15 +impl<'a> Default for ExpectCtPolicy<'a> { + fn default() -> ExpectCtPolicy<'a> { + ExpectCtPolicy::Enforce(Duration::days(30)) + } +} + +impl<'a, 'b> From<&'a ExpectCtPolicy<'a>> for Header<'b> { + fn from(policy: &ExpectCtPolicy<'a>) -> Header<'b> { + let policy_string = match policy { + ExpectCtPolicy::Enforce(max_age) => format!("max-age={}, enforce", max_age.num_seconds()), + ExpectCtPolicy::Report(max_age, url) => { + format!("max-age={}, report-uri=\"{}\"", max_age.num_seconds(), url) + } + ExpectCtPolicy::ReportAndEnforce(max_age, url) => { + format!("max-age={}, enforce, report-uri=\"{}\"", max_age.num_seconds(), url) + } + }; + + Header::new("Expect-CT", policy_string) + } +} + +/// The [X-Content-Type-Options] header can tell the browser to turn off [mime sniffing]( +/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#MIME_sniffing) which +/// can prevent certain [attacks]. +/// +/// [X-Content-Type-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options +/// [attacks]: https://helmetjs.github.io/docs/dont-sniff-mimetype/ +pub enum NoSniffPolicy { + + ///Turns off mime sniffing. + Enable, +} + +/// Defaults to [`NoSniffPolicy::Enable`], turns off mime sniffing. +impl Default for NoSniffPolicy { + fn default() -> NoSniffPolicy { + NoSniffPolicy::Enable + } +} + +impl<'a, 'b> From<&'a NoSniffPolicy> for Header<'b> { + fn from(_policy: &NoSniffPolicy) -> Header<'b> { + Header::new("X-Content-Type-Options", "nosniff") + } +} + +/// The HTTP [Strict-Transport-Security] (HSTS) header tells the browser that the site should only +/// be accessed using HTTPS instead of HTTP. HSTS prevents a variety of downgrading attacks and +/// should always be used when TLS is enabled. `SpaceHelmet` will turn HSTS on and +/// issue a warning if you enable TLS without enabling HSTS in [Staging ] or [Production]. +/// Read full [`HstsPolicy`] documentation before you configure this. +/// +/// HSTS is important for HTTPS security, however, incorrectly configured HSTS can lead to problems as +/// you are disallowing access to non-HTTPS enabled parts of your site. [Yelp engineering] has good +/// discussion of potential challenges that can arise, and how to roll this out in a large scale setting. +/// So, if you use TLS, use HSTS, just roll it out with care. +/// +/// Finally, requiring TLS use with valid certificates may be something of a nuisance in +/// development settings, so you may with to restrict HSTS use to [Staging] and [Production]. +/// +/// [TLS]: https://rocket.rs/guide/configuration/#configuring-tls +/// [Strict-Transport-Security]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security +/// [default policy]: /rocket_contrib/space_helmet/enum.HstsPolicy.html#impl-Default +/// [Yelp engineering]: https://engineeringblog.yelp.com/2017/09/the-road-to-hsts.html +/// [Staging]: /rocket/config/enum.Environment.html#variant.Staging +/// [Production]: /rocket/config/enum.Environment.html#variant.Production +pub enum HstsPolicy { + /// Browser should only permit this site to be accesses by HTTPS for the next [`Duration`] + /// seconds. + Enable(Duration), + + /// Same as above, but also apply to all of the sites subdomains. + IncludeSubDomains(Duration), + + /// Google maintains an [HSTS preload service] that can be used to prevent + /// the browser from ever connecting to your site over an insecure connection. + /// Read more [here]. Don't enable this before you have registered your site + /// + /// [HSTS preload service]: https://hstspreload.org/ + /// [here]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#Preloading_Strict_Transport_Security + Preload(Duration), +} + +/// Defaults to `HstsPolicy::Enable(Duration::weeks(52))`. +impl Default for HstsPolicy { + fn default() -> HstsPolicy { + HstsPolicy::Enable(Duration::weeks(52)) + } +} + +impl<'a, 'b> From<&'a HstsPolicy> for Header<'b> { + fn from(policy: &HstsPolicy) -> Header<'b> { + let policy_string = match policy { + HstsPolicy::Enable(max_age) => format!("max-age={}", max_age.num_seconds()), + HstsPolicy::IncludeSubDomains(max_age) => { + format!("max-age={}; includeSubDomains", max_age.num_seconds()) + } + HstsPolicy::Preload(max_age) => format!("max-age={}; preload", max_age.num_seconds()), + }; + + Header::new("Strict-Transport-Security", policy_string) + } +} + +/// The [X-Frame-Options] header controls whether the browser should allow the page +/// to render in a ``, [`