Add 'space_helmet' contrib module.

This commit is contained in:
Tal 2018-08-30 15:52:54 -07:00 committed by Sergio Benitez
parent 0ac6c18deb
commit c5167f1150
13 changed files with 921 additions and 0 deletions

View File

@ -8,6 +8,7 @@ members = [
"core/http/", "core/http/",
"contrib/lib", "contrib/lib",
"contrib/codegen", "contrib/codegen",
"examples/space_helmet",
"examples/cookies", "examples/cookies",
"examples/errors", "examples/errors",
"examples/form_validation", "examples/form_validation",

View File

@ -21,6 +21,7 @@ json = ["serde", "serde_json"]
msgpack = ["serde", "rmp-serde"] msgpack = ["serde", "rmp-serde"]
tera_templates = ["tera", "templates"] tera_templates = ["tera", "templates"]
handlebars_templates = ["handlebars", "templates"] handlebars_templates = ["handlebars", "templates"]
space_helmet = ["time"]
serve = [] serve = []
# The barage of user-facing database features. # The barage of user-facing database features.
@ -66,8 +67,14 @@ r2d2_cypher = { version = "0.4", optional = true }
redis = { version = "0.9", optional = true } redis = { version = "0.9", optional = true }
r2d2_redis = { version = "0.8", optional = true } r2d2_redis = { version = "0.8", optional = true }
# SpaceHelmet dependencies
time = { version = "0.1.40", optional = true }
[target.'cfg(debug_assertions)'.dependencies] [target.'cfg(debug_assertions)'.dependencies]
notify = { version = "^4.0" } notify = { version = "^4.0" }
[dev-dependencies]
rocket_codegen = { version = "0.4.0-dev", path = "../../core/codegen" }
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -24,6 +24,7 @@
//! * [tera_templates](templates) - Tera Templating //! * [tera_templates](templates) - Tera Templating
//! * [uuid](uuid) - UUID (de)serialization //! * [uuid](uuid) - UUID (de)serialization
//! * [${database}_pool](databases) - Database Configuration and Pooling //! * [${database}_pool](databases) - Database Configuration and Pooling
//! * [space_helmet](space_helmet)
//! //!
//! The recommend way to include features from this crate via Cargo in your //! The recommend way to include features from this crate via Cargo in your
//! project is by adding a `[dependencies.rocket_contrib]` section to your //! project is by adding a `[dependencies.rocket_contrib]` section to your
@ -49,6 +50,7 @@
#[cfg(feature="templates")] pub mod templates; #[cfg(feature="templates")] pub mod templates;
#[cfg(feature="uuid")] pub mod uuid; #[cfg(feature="uuid")] pub mod uuid;
#[cfg(feature="databases")] pub mod databases; #[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")] extern crate rocket_contrib_codegen;
#[cfg(feature="databases")] #[doc(hidden)] pub use rocket_contrib_codegen::*; #[cfg(feature="databases")] #[doc(hidden)] pub use rocket_contrib_codegen::*;

View File

@ -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<ExpectCtPolicy<'a>>,
no_sniff_policy: Option<NoSniffPolicy>,
xss_protect_policy: Option<XssPolicy<'a>>,
frameguard_policy: Option<FramePolicy<'a>>,
hsts_policy: Option<HstsPolicy>,
force_hsts_policy: Option<HstsPolicy>,
force_hsts: AtomicBool,
referrer_policy: Option<ReferrerPolicy>,
}
// 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<T: Into<Option<XssPolicy<'a>>>>(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<T: Into<Option<NoSniffPolicy>>>(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<T: Into<Option<FramePolicy<'a>>>>(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<T: Into<Option<HstsPolicy>>>(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<T: Into<Option<ExpectCtPolicy<'a>>>>(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<T: Into<Option<ReferrerPolicy>>>(
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);
}
}
}

View File

@ -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::*;

View File

@ -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 `<frame>`, [`<iframe>`][iframe] or `<object>`. This can be used
/// to prevent [clickjacking] attacks.
///
///
/// [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
/// [clickjacking]: https://en.wikipedia.org/wiki/Clickjacking
/// [owasp-clickjacking]: https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet
/// [iframe]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
pub enum FramePolicy<'a> {
/// Page cannot be displayed in a frame.
Deny,
/// Page can only be displayed in a frame if the page trying to render it came from
/// the same-origin. Interpretation of same-origin is [browser dependant][X-Frame-Options].
///
/// [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
SameOrigin,
/// Page can only be displayed in a frame if the page trying to render it came from
/// the origin given `Uri`. Interpretation of origin is [browser dependant][X-Frame-Options].
///
/// [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
AllowFrom(Uri<'a>),
}
///Defaults to [`FramePolicy::SameOrigin`].
impl<'a> Default for FramePolicy<'a> {
fn default() -> FramePolicy<'a> {
FramePolicy::SameOrigin
}
}
impl<'a, 'b> From<&'a FramePolicy<'a>> for Header<'b> {
fn from(policy: &FramePolicy<'a>) -> Header<'b> {
let policy_string: Cow<'static, str> = match policy {
FramePolicy::Deny => "DENY".into(),
FramePolicy::SameOrigin => "SAMEORIGIN".into(),
FramePolicy::AllowFrom(uri) => format!("ALLOW-FROM {}", uri).into(),
};
Header::new("X-Frame-Options", policy_string)
}
}
/// The [X-XSS-Protection] header tells the browsers to filter some forms of reflected
/// XSS([cross-site scripting]) attacks.
///
/// [X-XSS-Protection]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
/// [cross-site scripting]: https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting
pub enum XssPolicy<'a> {
/// Disables XSS filtering.
Disable,
/// Enables XSS filtering, if XSS detected browser will sanitize and render page (_often browser
/// default_).
Enable,
/// Enables XSS filtering, if XSS detected browser will block rendering of page (_SpaceHelmet default_).
EnableBlock,
/// Enables XSS filtering, if XSS detected browser will sanitize and render page and report the
/// violation to the given `Uri`. (Chromium only)
EnableReport(Uri<'a>),
}
/// Defaults to [`XssPolicy::EnableBlock`], turns on XSS filtering and blocks page rendering if
/// detected.
impl<'a> Default for XssPolicy<'a> {
fn default() -> XssPolicy<'a> {
XssPolicy::EnableBlock
}
}
impl<'a, 'b> From<&'a XssPolicy<'a>> for Header<'b> {
fn from(policy: &XssPolicy) -> Header<'b> {
let policy_string: Cow<'static, str> = match policy {
XssPolicy::Disable => "0".into(),
XssPolicy::Enable => "1".into(),
XssPolicy::EnableBlock => "1; mode=block".into(),
XssPolicy::EnableReport(u) => format!("{}{}", "1; report=", u).into(),
};
Header::new("X-XSS-Protection", policy_string)
}
}

View File

@ -0,0 +1,99 @@
#![cfg_attr(test, feature(plugin, decl_macro))]
#![cfg_attr(test, plugin(rocket_codegen))]
#![feature(proc_macro_non_items)]
#[macro_use] extern crate rocket;
extern crate rocket_contrib;
#[cfg(feature = "space_helmet")]
extern crate time;
#[cfg(feature = "space_helmet")]
mod space_helmet_tests {
use rocket;
use rocket::http::uri::Uri;
use rocket::http::Status;
use rocket::local::Client;
use rocket_contrib::space_helmet::*;
use time::Duration;
#[get("/")]
fn hello() -> &'static str {
"Hello, world!"
}
macro_rules! check_header {
($response:ident, $header_name:expr, $header_param:expr) => {
match $response.headers().get_one($header_name) {
Some(string) => assert_eq!(string, $header_param),
None => panic!("missing header parameters")
}
};
}
#[test]
fn default_headers_test() {
let helmet = SpaceHelmet::new();
let rocket = rocket::ignite().mount("/", routes![hello]).attach(helmet);
let client = Client::new(rocket).unwrap();
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some("Hello, world!".into()));
check_header!(response, "X-XSS-Protection", "1; mode=block");
check_header!(response, "X-Frame-Options", "SAMEORIGIN");
check_header!(response, "X-Content-Type-Options", "nosniff");
}
#[test]
fn additional_headers_test() {
let helmet = SpaceHelmet::new()
.hsts(HstsPolicy::default())
.expect_ct(ExpectCtPolicy::default())
.referrer_policy(ReferrerPolicy::default());
let rocket = rocket::ignite().mount("/", routes![hello]).attach(helmet);
let client = Client::new(rocket).unwrap();
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some("Hello, world!".into()));
check_header!(
response,
"Strict-Transport-Security",
format!("max-age={}", Duration::weeks(52).num_seconds())
);
check_header!(
response,
"Expect-CT",
format!("max-age={}, enforce", Duration::days(30).num_seconds())
);
check_header!(response, "Referrer-Policy", "no-referrer");
}
#[test]
fn uri_test() {
let allow_uri = Uri::parse("https://www.google.com").unwrap();
let report_uri = Uri::parse("https://www.google.com").unwrap();
let enforce_uri = Uri::parse("https://www.google.com").unwrap();
let helmet = SpaceHelmet::new()
.frameguard(FramePolicy::AllowFrom(allow_uri))
.xss_protect(XssPolicy::EnableReport(report_uri))
.expect_ct(ExpectCtPolicy::ReportAndEnforce(Duration::seconds(30), enforce_uri));
let rocket = rocket::ignite().mount("/", routes![hello]).attach(helmet);
let client = Client::new(rocket).unwrap();
let response = client.get("/").dispatch();
check_header!(
response,
"X-Frame-Options",
"ALLOW-FROM https://www.google.com"
);
check_header!(
response,
"X-XSS-Protection",
"1; report=https://www.google.com"
);
check_header!(
response,
"Expect-CT",
"max-age=30, enforce, report-uri=\"https://www.google.com\""
);
}
}

View File

@ -0,0 +1,18 @@
[package]
name = "space_helmet"
version = "0.0.0"
workspace = "../../"
publish = false
[dependencies]
rocket = { path = "../../core/lib", features = ["tls"]}
rocket_codegen = { path = "../../core/codegen" }
[dependencies.rocket_contrib]
path = "../../contrib/lib"
default-features = false
features = ["space_helmet"]
[[bin]]
name = "hello"
path = "src/hello.rs"

View File

@ -0,0 +1,4 @@
# Example cert and key pair from the tls example, NEVER use these in production, for demonstration only!!!
[global.tls]
certs = "private/cert.pem"
key = "private/key.pem"

View File

@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFITCCAwmgAwIBAgIJAII1fQkonYEEMA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEChMJUm9ja2V0IENBMRcwFQYDVQQD
Ew5Sb2NrZXQgUm9vdCBDQTAeFw0xNzA5MDExMDAyMjhaFw0yNzA4MzAxMDAyMjha
MD8xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEChMGUm9ja2V0MRIw
EAYDVQQDEwlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQDqe/Ps0tJf11HBuxJ4HvgC4VJeeiSl3D4P8ZT6uamCj8XD0MPtfRjGgfZPRjfY
ksiYRs4Wg3Wy3aiQR6IVrNAxtfU1ZA3vRGCBwV0oWkfyPJKQOtF0Ih0/MhmYdiWG
gDqs5qF/6B9K8qbinexal8v1oXpwQC5dod/NOuSLZQtQfkiYIeNqo0BbxtcaNE2u
kgOYg1Cvc9ui3KPNA2JTN+Uzq6A8n4Pej6erG2NeCAoov9nrkPyustDWLQ76wdTp
5YU6zwwsl+fJtb5scNUmagujoXTTqn06WoCMDUsSjC/jlGMIrzmx90Wq8Dg6HBGn
Cscz3M/AUXYzJtShkxMNZCsdxH+8x5oyO/RrtyeRyN8iDiOolz+SfQROVXMU0zkx
nRl7hIxgB/QeDi6MMXGLTd08vpIAohk3hnycsGgTwTCT5LxWJnorpm4wdr1bDmCY
InUO5hX0rFWtS0ij78GTUbpajkNTEXIXXwa1VnSE2kIeUX6aiKhJsm3KWp496JuM
ahIR7XCP9PyGclWI+Pa0eq5L8nnuSfqUAwCeOvvwdBOxUvKmecly1IHLoUXGnhy0
46MjYo80yYFqrGgop6lUEZ0ThYpDpMxq+JIeUoyGaCJFDvundzt0u0sh9i+hUCVe
v3zsgxwvBeJy0L1G1uGkpCqERkYJQt9O+qLM8i7hf7ONkQIDAQABoxgwFjAUBgNV
HREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIBAAcXycXdWVvwLl+7
ryG8V7FpB9bp0lHNA4q3DpEINCNyuA2cbnVU/u2L3ddvroTcrPjE+hhNbcVaz/NH
w4sQqlDzkV+G9kZ4qBTlk5zon6aGGow181J+B5SVznt6lO4/KYYpEN0vX+uMvsgK
OG7ydsRMDxPpsnVS9SFx0Ke8AlmUet5S/NGYCfedd4rwCu+oJHUWhXNwFZqLF3Yn
s8lg3xdM0kJt8g4m1/KUpunanX3w+DdZaIwbltEZs4NriXn0VVbEPRpHyiGMosgf
mEUV2z49f6S2joEnSn2Y/ILOdKFQ2mKFXtXJP43Qzj8Mr5mSb2bXyABlrn0pl/+o
HBkyVyDx5BKqWKe5uK3YCDsbIJj026AkCdTKF+BSBWfB+EqdSIOvVrpHtQK7BwFx
pS5rdQBLA86f1NC0e235L6pwFKm+imazr6Jn7fbbwq1y9PSL36rUn4e/+R2Yoia9
S7zDOqGbnyv9h7eE3Muiy26kJsJfCrjse/dmce+6YnB1FC5RKPn7kM86t7MyDrgx
W60xRMdgmcGfPjei2V4MdVM6ysOlNoeh39DizjkV9+r8iGl4vngplJrPgAIvywQz
v1pLk6dSlSOwgqY94hqxqNvG80xSoYsmMjDrPmtBVERjhbffsdIDHjcPVsJKH6l6
8wg+/u6aK2bMHt41f3XE/UTY+A57
-----END CERTIFICATE-----

View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,33 @@
#![feature(decl_macro)]
#![feature(plugin)]
#![plugin(rocket_codegen)]
#![feature(proc_macro_non_items)]
#[macro_use] extern crate rocket;
extern crate rocket_contrib;
use rocket::http::uri::Uri;
use rocket_contrib::space_helmet::*;
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
fn rocket() -> rocket::Rocket {
let allow_uri = Uri::parse("https://mysite.example.com").unwrap();
let report_uri = Uri::parse("https://report.example.com").unwrap();
let helmet = SpaceHelmet::new()
//illustrates how to disable a header by using None as the policy.
.no_sniff(None)
.frameguard(FramePolicy::AllowFrom(allow_uri))
.xss_protect(XssPolicy::EnableReport(report_uri))
//we need an hsts policy as we are using tls
.hsts(HstsPolicy::default())
.expect_ct(ExpectCtPolicy::default());
rocket::ignite().mount("/", routes![index]).attach(helmet)
}
fn main() {
rocket().launch();
}

View File

@ -86,6 +86,7 @@ if [ "$1" = "--contrib" ]; then
tera_templates tera_templates
handlebars_templates handlebars_templates
serve serve
space_helmet
diesel_postgres_pool diesel_postgres_pool
diesel_sqlite_pool diesel_sqlite_pool
diesel_mysql_pool diesel_mysql_pool