mirror of https://github.com/rwf2/Rocket.git
Rename 'space_helmet' to 'helmet'. Rework API.
This commit is contained in:
parent
c5167f1150
commit
1bb23b8115
|
@ -8,7 +8,6 @@ members = [
|
|||
"core/http/",
|
||||
"contrib/lib",
|
||||
"contrib/codegen",
|
||||
"examples/space_helmet",
|
||||
"examples/cookies",
|
||||
"examples/errors",
|
||||
"examples/form_validation",
|
||||
|
|
|
@ -21,7 +21,7 @@ json = ["serde", "serde_json"]
|
|||
msgpack = ["serde", "rmp-serde"]
|
||||
tera_templates = ["tera", "templates"]
|
||||
handlebars_templates = ["handlebars", "templates"]
|
||||
space_helmet = ["time"]
|
||||
helmet = ["time"]
|
||||
serve = []
|
||||
|
||||
# The barage of user-facing database features.
|
||||
|
@ -73,8 +73,5 @@ 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
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use rocket::http::uncased::UncasedStr;
|
||||
use rocket::fairing::{Fairing, Info, Kind};
|
||||
use rocket::{Request, Response, Rocket};
|
||||
|
||||
use helmet::*;
|
||||
|
||||
/// A [`Fairing`](../../rocket/fairing/trait.Fairing.html) that adds HTTP
|
||||
/// headers to outgoing responses that control security features on the browser.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// To use `SpaceHelmet`, first construct an instance of it. To use the default
|
||||
/// set of headers, construct with [`SpaceHelmet::default()`](#method.default).
|
||||
/// For an instance with no preset headers, use [`SpaceHelmet::new()`]. To
|
||||
/// enable an additional header, use [`enable()`](SpaceHelmet::enable()), and to
|
||||
/// disable a header, use [`disable()`](SpaceHelmet::disable()):
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket_contrib::helmet::SpaceHelmet;
|
||||
/// use rocket_contrib::helmet::{XssFilter, ExpectCt};
|
||||
///
|
||||
/// // A `SpaceHelmet` with the default headers:
|
||||
/// let helmet = SpaceHelmet::default();
|
||||
///
|
||||
/// // A `SpaceHelmet` with the default headers minus `XssFilter`:
|
||||
/// let helmet = SpaceHelmet::default().disable::<XssFilter>();
|
||||
///
|
||||
/// // A `SpaceHelmet` with the default headers plus `ExpectCt`.
|
||||
/// let helmet = SpaceHelmet::default().enable(ExpectCt::default());
|
||||
///
|
||||
/// // A `SpaceHelmet` with only `XssFilter` and `ExpectCt`.
|
||||
/// let helmet = SpaceHelmet::default()
|
||||
/// .enable(XssFilter::default())
|
||||
/// .enable(ExpectCt::default());
|
||||
/// ```
|
||||
///
|
||||
/// Then, attach the instance of `SpaceHelmet` to your application's instance of
|
||||
/// `Rocket`:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// # use rocket_contrib::helmet::SpaceHelmet;
|
||||
/// # let helmet = SpaceHelmet::default();
|
||||
/// rocket::ignite()
|
||||
/// // ...
|
||||
/// .attach(helmet)
|
||||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
/// The fairing will inject all enabled headers into all outgoing responses
|
||||
/// _unless_ the response already contains a header with the same name. If it
|
||||
/// does contain the header, a warning is emitted, and the header is not
|
||||
/// overwritten.
|
||||
///
|
||||
/// # TLS and HSTS
|
||||
///
|
||||
/// If TLS is configured and enabled when the application is launched in a
|
||||
/// non-development environment (e.g., staging or production), HSTS is
|
||||
/// automatically enabled with its default policy and a warning is issued.
|
||||
///
|
||||
/// To get rid of this warning, explicitly [`enable()`](SpaceHelmet::enable())
|
||||
/// an [`Hsts`] policy.
|
||||
pub struct SpaceHelmet {
|
||||
policies: HashMap<&'static UncasedStr, Box<dyn SubPolicy>>,
|
||||
force_hsts: AtomicBool,
|
||||
}
|
||||
|
||||
impl Default for SpaceHelmet {
|
||||
/// Returns a new `SpaceHelmet` instance. See the [table] for a description
|
||||
/// of the policies used by default.
|
||||
///
|
||||
/// [table]: ./#supported-headers
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// use rocket_contrib::helmet::SpaceHelmet;
|
||||
///
|
||||
/// let helmet = SpaceHelmet::default();
|
||||
/// ```
|
||||
fn default() -> Self {
|
||||
SpaceHelmet::new()
|
||||
.enable(NoSniff::default())
|
||||
.enable(Frame::default())
|
||||
.enable(XssFilter::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl SpaceHelmet {
|
||||
/// Returns an instance of `SpaceHelmet` with no headers enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket_contrib::helmet::SpaceHelmet;
|
||||
///
|
||||
/// let helmet = SpaceHelmet::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
SpaceHelmet {
|
||||
policies: HashMap::new(),
|
||||
force_hsts: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables the policy header `policy`.
|
||||
///
|
||||
/// If the poliicy was previously enabled, the configuration is replaced
|
||||
/// with that of `policy`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket_contrib::helmet::SpaceHelmet;
|
||||
/// use rocket_contrib::helmet::NoSniff;
|
||||
///
|
||||
/// let helmet = SpaceHelmet::new().enable(NoSniff::default());
|
||||
/// ```
|
||||
pub fn enable<P: Policy>(mut self, policy: P) -> Self {
|
||||
self.policies.insert(P::NAME.into(), Box::new(policy));
|
||||
self
|
||||
}
|
||||
|
||||
/// Disables the policy header `policy`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket_contrib::helmet::SpaceHelmet;
|
||||
/// use rocket_contrib::helmet::NoSniff;
|
||||
///
|
||||
/// let helmet = SpaceHelmet::default().disable::<NoSniff>();
|
||||
/// ```
|
||||
pub fn disable<P: Policy>(mut self) -> Self {
|
||||
self.policies.remove(UncasedStr::new(P::NAME));
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if the policy `P` is enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket_contrib::helmet::SpaceHelmet;
|
||||
/// use rocket_contrib::helmet::{XssFilter, NoSniff, Frame};
|
||||
/// use rocket_contrib::helmet::{Hsts, ExpectCt, Referrer};
|
||||
///
|
||||
/// let helmet = SpaceHelmet::default();
|
||||
///
|
||||
/// assert!(helmet.is_enabled::<XssFilter>());
|
||||
/// assert!(helmet.is_enabled::<NoSniff>());
|
||||
/// assert!(helmet.is_enabled::<Frame>());
|
||||
///
|
||||
/// assert!(!helmet.is_enabled::<Hsts>());
|
||||
/// assert!(!helmet.is_enabled::<ExpectCt>());
|
||||
/// assert!(!helmet.is_enabled::<Referrer>());
|
||||
/// ```
|
||||
pub fn is_enabled<P: Policy>(&self) -> bool {
|
||||
self.policies.contains_key(UncasedStr::new(P::NAME))
|
||||
}
|
||||
|
||||
/// Sets all of the headers in `self.policies` in `response` as long as the
|
||||
/// header is not already in the response.
|
||||
fn apply(&self, response: &mut Response) {
|
||||
for policy in self.policies.values() {
|
||||
let name = policy.name();
|
||||
if response.headers().contains(name.as_str()) {
|
||||
warn!("Space Helmet: response contains a '{}' header.", name);
|
||||
warn_!("Refusing to overwrite existing header.");
|
||||
continue
|
||||
}
|
||||
|
||||
// FIXME: Cache the rendered header.
|
||||
response.set_header(policy.header());
|
||||
}
|
||||
|
||||
if !self.force_hsts.load(Ordering::Relaxed) {
|
||||
response.set_header(Policy::header(&Hsts::default()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fairing for SpaceHelmet {
|
||||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "Space Helmet",
|
||||
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.is_enabled::<Hsts>()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
//! Security and privacy headers for all outgoing responses.
|
||||
//!
|
||||
//! [`SpaceHelmet`] provides a typed interface for HTTP security headers. It
|
||||
//! takes some inspiration from [helmetjs], a similar piece of middleware for
|
||||
//! [express].
|
||||
//!
|
||||
//! [fairing]: https://rocket.rs/v0.4/guide/fairings/
|
||||
//! [helmetjs]: https://helmetjs.github.io/
|
||||
//! [express]: https://expressjs.com
|
||||
//! [`SpaceHelmet`]: helmet::SpaceHelmet
|
||||
//!
|
||||
//! # Supported Headers
|
||||
//!
|
||||
//! | HTTP Header | Description | Policy | Default? |
|
||||
//! | --------------------------- | -------------------------------------- | ------------ | -------- |
|
||||
//! | [X-XSS-Protection] | Prevents some reflected XSS attacks. | [`XssFilter`] | ✔ |
|
||||
//! | [X-Content-Type-Options] | Prevents client sniffing of MIME type. | [`NoSniff`] | ✔ |
|
||||
//! | [X-Frame-Options] | Prevents [clickjacking]. | [`Frame`] | ✔ |
|
||||
//! | [Strict-Transport-Security] | Enforces strict use of HTTPS. | [`Hsts`] | ? |
|
||||
//! | [Expect-CT] | Enables certificate transparency. | [`ExpectCt`] | ✗ |
|
||||
//! | [Referrer-Policy] | Enables referrer policy. | [`Referrer`] | ✗ |
|
||||
//!
|
||||
//! <small>? If TLS is enabled when the application is launched, in a
|
||||
//! non-development environment (e.g., staging or production), HSTS is
|
||||
//! automatically enabled with its default policy and a warning is
|
||||
//! issued.</small>
|
||||
//!
|
||||
//! [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
|
||||
//!
|
||||
//! [`XssFilter`]: helmet::XssFilter
|
||||
//! [`NoSniff`]: helmet::NoSniff
|
||||
//! [`Frame`]: helmet::Frame
|
||||
//! [`Hsts`]: helmet::Hsts
|
||||
//! [`ExpectCt`]: helmet::ExpectCt
|
||||
//! [`Referrer`]: helmet::Referrer
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! To apply default headers, simply attach an instance of [`SpaceHelmet`]
|
||||
//! before launching:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate rocket;
|
||||
//! # extern crate rocket_contrib;
|
||||
//! use rocket_contrib::helmet::SpaceHelmet;
|
||||
//!
|
||||
//! let rocket = rocket::ignite().attach(SpaceHelmet::default());
|
||||
//! ```
|
||||
//!
|
||||
//! Each header can be configured individually. To enable a particular header,
|
||||
//! call the chainable [`enable()`](helmet::SpaceHelmet::enable()) method
|
||||
//! on an instance of `SpaceHelmet`, passing in the configured policy type.
|
||||
//! Similarly, to disable a header, call the chainable
|
||||
//! [`disable()`](helmet::SpaceHelmet::disable()) method on an instance of
|
||||
//! `SpaceHelmet`:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate rocket;
|
||||
//! # extern crate rocket_contrib;
|
||||
//! use rocket::http::uri::Uri;
|
||||
//! use rocket_contrib::helmet::{SpaceHelmet, Frame, XssFilter, Hsts, NoSniff};
|
||||
//!
|
||||
//! let site_uri = Uri::parse("https://mysite.example.com").unwrap();
|
||||
//! let report_uri = Uri::parse("https://report.example.com").unwrap();
|
||||
//! let helmet = SpaceHelmet::default()
|
||||
//! .enable(Hsts::default())
|
||||
//! .enable(Frame::AllowFrom(site_uri))
|
||||
//! .enable(XssFilter::EnableReport(report_uri))
|
||||
//! .disable::<NoSniff>();
|
||||
//! ```
|
||||
//!
|
||||
//! # FAQ
|
||||
//!
|
||||
//! * **Which policies should I choose?**
|
||||
//!
|
||||
//! See the links in the table above for individual header documentation. The
|
||||
//! [helmetjs] docs are also a good resource, and [OWASP] has a collection of
|
||||
//! references on these headers.
|
||||
//!
|
||||
//! * **Do I need any headers beyond what `SpaceHelmet` enables by default?**
|
||||
//!
|
||||
//! Maybe! The other headers can protect against many important
|
||||
//! vulnerabilities. Please consult their documentation and other resources to
|
||||
//! determine if they are needed for your project.
|
||||
//!
|
||||
//! [OWASP]: https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#tab=Headers
|
||||
|
||||
extern crate time;
|
||||
|
||||
mod helmet;
|
||||
mod policy;
|
||||
|
||||
pub use self::helmet::SpaceHelmet;
|
||||
pub use self::policy::*;
|
|
@ -0,0 +1,401 @@
|
|||
//! Module containing the [`Policy`] trait and types that implement it.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rocket::http::{Header, uri::Uri, uncased::UncasedStr};
|
||||
|
||||
use helmet::time::Duration;
|
||||
|
||||
/// Trait implemented by security and privacy policy headers.
|
||||
///
|
||||
/// Types that implement this trait can be [`enable()`]d and [`disable()`]d on
|
||||
/// instances of [`SpaceHelmet`].
|
||||
///
|
||||
/// [`SpaceHelmet`]: ::helmet::SpaceHelmet
|
||||
/// [`enable()`]: ::helmet::SpaceHelmet::enable()
|
||||
/// [`disable()`]: ::helmet::SpaceHelmet::disable()
|
||||
pub trait Policy: Default + Send + Sync + 'static {
|
||||
/// The actual name of the HTTP header.
|
||||
///
|
||||
/// This name must uniquely identify the header as it is used to determine
|
||||
/// whether two implementations of `Policy` are for the same header. Use the
|
||||
/// real HTTP header's name.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// # use rocket::http::Header;
|
||||
/// use rocket_contrib::helmet::Policy;
|
||||
///
|
||||
/// #[derive(Default)]
|
||||
/// struct MyPolicy;
|
||||
///
|
||||
/// impl Policy for MyPolicy {
|
||||
/// const NAME: &'static str = "X-My-Policy";
|
||||
/// # fn header(&self) -> Header<'static> { unimplemented!() }
|
||||
/// }
|
||||
/// ```
|
||||
const NAME: &'static str;
|
||||
|
||||
/// Returns the [`Header`](../../rocket/http/struct.Header.html) to attach
|
||||
/// to all outgoing responses.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # extern crate rocket_contrib;
|
||||
/// use rocket::http::Header;
|
||||
/// use rocket_contrib::helmet::Policy;
|
||||
///
|
||||
/// #[derive(Default)]
|
||||
/// struct MyPolicy;
|
||||
///
|
||||
/// impl Policy for MyPolicy {
|
||||
/// # const NAME: &'static str = "X-My-Policy";
|
||||
/// fn header(&self) -> Header<'static> {
|
||||
/// Header::new(Self::NAME, "value-to-enable")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn header(&self) -> Header<'static>;
|
||||
}
|
||||
|
||||
crate trait SubPolicy: Send + Sync {
|
||||
fn name(&self) -> &'static UncasedStr;
|
||||
fn header(&self) -> Header<'static>;
|
||||
}
|
||||
|
||||
impl<P: Policy> SubPolicy for P {
|
||||
fn name(&self) -> &'static UncasedStr {
|
||||
P::NAME.into()
|
||||
}
|
||||
|
||||
fn header(&self) -> Header<'static> {
|
||||
Policy::header(self)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_policy {
|
||||
($T:ty, $name:expr) => (
|
||||
impl Policy for $T {
|
||||
const NAME: &'static str = $name;
|
||||
|
||||
fn header(&self) -> Header<'static> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
impl_policy!(XssFilter, "X-XSS-Protection");
|
||||
impl_policy!(NoSniff, "X-Content-Type-Options");
|
||||
impl_policy!(Frame, "X-Frame-Options");
|
||||
impl_policy!(Hsts, "Strict-Transport-Security");
|
||||
impl_policy!(ExpectCt, "Expect-CT");
|
||||
impl_policy!(Referrer, "Referrer-Policy");
|
||||
|
||||
/// The [Referrer-Policy] header: controls the value set by the browser for the
|
||||
/// [Referer] header.
|
||||
///
|
||||
/// Tells the browser if it 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 Referrer {
|
||||
/// 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 [`Referrer::NoReferrer`]. Tells the browser to omit the
|
||||
/// `Referer` header.
|
||||
impl Default for Referrer {
|
||||
fn default() -> Referrer {
|
||||
Referrer::NoReferrer
|
||||
}
|
||||
}
|
||||
|
||||
impl<'h, 'a> Into<Header<'h>> for &'a Referrer {
|
||||
fn into(self) -> Header<'h> {
|
||||
let policy_string = match self {
|
||||
Referrer::NoReferrer => "no-referrer",
|
||||
Referrer::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
|
||||
Referrer::Origin => "origin",
|
||||
Referrer::OriginWhenCrossOrigin => "origin-when-cross-origin",
|
||||
Referrer::SameOrigin => "same-origin",
|
||||
Referrer::StrictOrigin => "strict-origin",
|
||||
Referrer::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
|
||||
Referrer::UnsafeUrl => "unsafe-url",
|
||||
};
|
||||
|
||||
Header::new(Referrer::NAME, policy_string)
|
||||
}
|
||||
}
|
||||
|
||||
/// The [Expect-CT] header: enables [Certificate Transparency] to detect and
|
||||
/// prevent misuse of TLS certificates.
|
||||
///
|
||||
/// [Certificate Transparency] solves a variety of problems with public TLS/SSL
|
||||
/// certificate management and is valuable measure for all public applications.
|
||||
/// If you're just [getting started] with certificate transparency, ensure that
|
||||
/// your [site is in compliance][getting started] before you enable enforcement
|
||||
/// with [`ExpectCt::Enforce`] or [`ExpectCt::ReportAndEnforce`]. Failure to do
|
||||
/// so will result in the browser refusing to communicate with your application.
|
||||
/// _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 ExpectCt {
|
||||
/// Enforce certificate compliance for the next [`Duration`]. Ensure that
|
||||
/// your certificates are in compliance before turning on enforcement.
|
||||
/// (_SpaceHelmet_ default).
|
||||
Enforce(Duration),
|
||||
|
||||
/// Report to `Uri`, but do not enforce, compliance violations for the next
|
||||
/// [`Duration`]. 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<'static>),
|
||||
|
||||
/// Enforce compliance and report violations to `Uri` for the next
|
||||
/// [`Duration`].
|
||||
ReportAndEnforce(Duration, Uri<'static>),
|
||||
}
|
||||
|
||||
/// Defaults to [`ExpectCt::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 Default for ExpectCt {
|
||||
fn default() -> ExpectCt {
|
||||
ExpectCt::Enforce(Duration::days(30))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Header<'static>> for &'a ExpectCt {
|
||||
fn into(self) -> Header<'static> {
|
||||
let policy_string = match self {
|
||||
ExpectCt::Enforce(age) => format!("max-age={}, enforce", age.num_seconds()),
|
||||
ExpectCt::Report(age, uri) => {
|
||||
format!(r#"max-age={}, report-uri="{}""#, age.num_seconds(), uri)
|
||||
}
|
||||
ExpectCt::ReportAndEnforce(age, uri) => {
|
||||
format!("max-age={}, enforce, report-uri=\"{}\"", age.num_seconds(), uri)
|
||||
}
|
||||
};
|
||||
|
||||
Header::new(ExpectCt::NAME, policy_string)
|
||||
}
|
||||
}
|
||||
|
||||
/// The [X-Content-Type-Options] header: turns off [mime sniffing] which can
|
||||
/// prevent certain [attacks].
|
||||
///
|
||||
/// [mime sniffing]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#MIME_sniffing
|
||||
/// [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 NoSniff {
|
||||
/// Turns off mime sniffing.
|
||||
Enable,
|
||||
}
|
||||
|
||||
/// Defaults to [`NoSniff::Enable`], turns off mime sniffing.
|
||||
impl Default for NoSniff {
|
||||
fn default() -> NoSniff {
|
||||
NoSniff::Enable
|
||||
}
|
||||
}
|
||||
|
||||
impl<'h, 'a> Into<Header<'h>> for &'a NoSniff {
|
||||
fn into(self) -> Header<'h> {
|
||||
Header::new(NoSniff::NAME, "nosniff")
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP [Strict-Transport-Security] (HSTS) header: enforces strict HTTPS
|
||||
/// usage.
|
||||
///
|
||||
/// HSTS 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 when the application
|
||||
/// is run in the staging or production environments.
|
||||
///
|
||||
/// While HSTS is important for HTTPS security, 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, but roll it out with care.
|
||||
///
|
||||
/// [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/helmet/enum.Hsts.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 Hsts {
|
||||
/// Browser should only permit this site to be accesses by HTTPS for the
|
||||
/// next [`Duration`].
|
||||
Enable(Duration),
|
||||
|
||||
/// Like [`Hsts::Enable`], but also apply to all of the site's 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 `Hsts::Enable(Duration::weeks(52))`.
|
||||
impl Default for Hsts {
|
||||
fn default() -> Hsts {
|
||||
Hsts::Enable(Duration::weeks(52))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Header<'static>> for &'a Hsts {
|
||||
fn into(self) -> Header<'static> {
|
||||
let policy_string = match self {
|
||||
Hsts::Enable(age) => format!("max-age={}", age.num_seconds()),
|
||||
Hsts::IncludeSubDomains(age) => {
|
||||
format!("max-age={}; includeSubDomains", age.num_seconds())
|
||||
}
|
||||
Hsts::Preload(age) => format!("max-age={}; preload", age.num_seconds()),
|
||||
};
|
||||
|
||||
Header::new(Hsts::NAME, policy_string)
|
||||
}
|
||||
}
|
||||
|
||||
/// The [X-Frame-Options] header: helps prevent [clickjacking] attacks.
|
||||
///
|
||||
/// 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 Frame {
|
||||
/// Page cannot be displayed in a frame.
|
||||
Deny,
|
||||
|
||||
/// Page can only be displayed in a frame if the page trying to render it is
|
||||
/// in the same origin. Interpretation of same-origin is [browser
|
||||
/// dependent][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 is
|
||||
/// in the origin for `Uri`. Interpretation of origin is [browser
|
||||
/// dependent][X-Frame-Options].
|
||||
///
|
||||
/// [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
||||
AllowFrom(Uri<'static>),
|
||||
}
|
||||
|
||||
/// Defaults to [`Frame::SameOrigin`].
|
||||
impl Default for Frame {
|
||||
fn default() -> Frame {
|
||||
Frame::SameOrigin
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Header<'static>> for &'a Frame {
|
||||
fn into(self) -> Header<'static> {
|
||||
let policy_string: Cow<'static, str> = match self {
|
||||
Frame::Deny => "DENY".into(),
|
||||
Frame::SameOrigin => "SAMEORIGIN".into(),
|
||||
Frame::AllowFrom(uri) => format!("ALLOW-FROM {}", uri).into(),
|
||||
};
|
||||
|
||||
Header::new(Frame::NAME, policy_string)
|
||||
}
|
||||
}
|
||||
|
||||
/// The [X-XSS-Protection] header: filters some forms of reflected [XSS]
|
||||
/// attacks.
|
||||
///
|
||||
/// [X-XSS-Protection]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
|
||||
/// [XSS]: https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting
|
||||
pub enum XssFilter {
|
||||
/// Disables XSS filtering.
|
||||
Disable,
|
||||
|
||||
/// Enables XSS filtering. If XSS is detected, the browser will sanitize
|
||||
/// before rendering the page (_SpaceHelmet default_).
|
||||
Enable,
|
||||
|
||||
/// Enables XSS filtering. If XSS is detected, the browser will not
|
||||
/// render the page.
|
||||
EnableBlock,
|
||||
|
||||
/// Enables XSS filtering. If XSS is detected, the browser will sanitize and
|
||||
/// render the page and report the violation to the given `Uri`. (_Chromium
|
||||
/// only_)
|
||||
EnableReport(Uri<'static>),
|
||||
}
|
||||
|
||||
/// Defaults to [`XssFilter::Enable`].
|
||||
impl Default for XssFilter {
|
||||
fn default() -> XssFilter {
|
||||
XssFilter::Enable
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Header<'static>> for &'a XssFilter {
|
||||
fn into(self) -> Header<'static> {
|
||||
let policy_string: Cow<'static, str> = match self {
|
||||
XssFilter::Disable => "0".into(),
|
||||
XssFilter::Enable => "1".into(),
|
||||
XssFilter::EnableBlock => "1; mode=block".into(),
|
||||
XssFilter::EnableReport(u) => format!("{}{}", "1; report=", u).into(),
|
||||
};
|
||||
|
||||
Header::new(XssFilter::NAME, policy_string)
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@
|
|||
//! * [tera_templates](templates) - Tera Templating
|
||||
//! * [uuid](uuid) - UUID (de)serialization
|
||||
//! * [${database}_pool](databases) - Database Configuration and Pooling
|
||||
//! * [space_helmet](space_helmet)
|
||||
//! * [helmet](helmet) - Fairing for Security and Privacy Headers
|
||||
//!
|
||||
//! The recommend way to include features from this crate via Cargo in your
|
||||
//! project is by adding a `[dependencies.rocket_contrib]` section to your
|
||||
|
@ -50,7 +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 = "helmet")] pub mod helmet;
|
||||
|
||||
#[cfg(feature="databases")] extern crate rocket_contrib_codegen;
|
||||
#[cfg(feature="databases")] #[doc(hidden)] pub use rocket_contrib_codegen::*;
|
||||
|
|
|
@ -1,285 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
//! [`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::*;
|
|
@ -1,302 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
#[macro_use]
|
||||
#[cfg(feature = "helmet")]
|
||||
extern crate rocket;
|
||||
|
||||
#[cfg(feature = "helmet")]
|
||||
mod helmet_tests {
|
||||
extern crate time;
|
||||
extern crate rocket_contrib;
|
||||
|
||||
use rocket;
|
||||
use rocket::http::{Status, uri::Uri};
|
||||
use rocket::local::{Client, LocalResponse};
|
||||
|
||||
use self::rocket_contrib::helmet::*;
|
||||
use self::rocket_contrib::helmet::*;
|
||||
use self::time::Duration;
|
||||
|
||||
#[get("/")] fn hello() { }
|
||||
|
||||
macro_rules! assert_header {
|
||||
($response:ident, $name:expr, $value:expr) => {
|
||||
match $response.headers().get_one($name) {
|
||||
Some(value) => assert_eq!(value, $value),
|
||||
None => panic!("missing header '{}' with value '{}'", $name, $value)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_no_header {
|
||||
($response:ident, $name:expr) => {
|
||||
if let Some(value) = $response.headers().get_one($name) {
|
||||
panic!("unexpected header: '{}={}", $name, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! dispatch {
|
||||
($helmet:expr, $closure:expr) => {{
|
||||
let rocket = rocket::ignite().mount("/", routes![hello]).attach($helmet);
|
||||
let client = Client::new(rocket).unwrap();
|
||||
let response = client.get("/").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
$closure(response)
|
||||
}}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_headers_test() {
|
||||
dispatch!(SpaceHelmet::default(), |response: LocalResponse| {
|
||||
assert_header!(response, "X-XSS-Protection", "1");
|
||||
assert_header!(response, "X-Frame-Options", "SAMEORIGIN");
|
||||
assert_header!(response, "X-Content-Type-Options", "nosniff");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disable_headers_test() {
|
||||
let helmet = SpaceHelmet::default().disable::<XssFilter>();
|
||||
dispatch!(helmet, |response: LocalResponse| {
|
||||
assert_header!(response, "X-Frame-Options", "SAMEORIGIN");
|
||||
assert_header!(response, "X-Content-Type-Options", "nosniff");
|
||||
assert_no_header!(response, "X-XSS-Protection");
|
||||
});
|
||||
|
||||
let helmet = SpaceHelmet::default().disable::<Frame>();
|
||||
dispatch!(helmet, |response: LocalResponse| {
|
||||
assert_header!(response, "X-XSS-Protection", "1");
|
||||
assert_header!(response, "X-Content-Type-Options", "nosniff");
|
||||
assert_no_header!(response, "X-Frame-Options");
|
||||
});
|
||||
|
||||
let helmet = SpaceHelmet::default()
|
||||
.disable::<Frame>()
|
||||
.disable::<XssFilter>()
|
||||
.disable::<NoSniff>();
|
||||
|
||||
dispatch!(helmet, |response: LocalResponse| {
|
||||
assert_no_header!(response, "X-Frame-Options");
|
||||
assert_no_header!(response, "X-XSS-Protection");
|
||||
assert_no_header!(response, "X-Content-Type-Options");
|
||||
});
|
||||
|
||||
dispatch!(SpaceHelmet::new(), |response: LocalResponse| {
|
||||
assert_no_header!(response, "X-Frame-Options");
|
||||
assert_no_header!(response, "X-XSS-Protection");
|
||||
assert_no_header!(response, "X-Content-Type-Options");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn additional_headers_test() {
|
||||
let helmet = SpaceHelmet::default()
|
||||
.enable(Hsts::default())
|
||||
.enable(ExpectCt::default())
|
||||
.enable(Referrer::default());
|
||||
|
||||
dispatch!(helmet, |response: LocalResponse| {
|
||||
assert_header!(
|
||||
response,
|
||||
"Strict-Transport-Security",
|
||||
format!("max-age={}", Duration::weeks(52).num_seconds())
|
||||
);
|
||||
|
||||
assert_header!(
|
||||
response,
|
||||
"Expect-CT",
|
||||
format!("max-age={}, enforce", Duration::days(30).num_seconds())
|
||||
);
|
||||
|
||||
assert_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::default()
|
||||
.enable(Frame::AllowFrom(allow_uri))
|
||||
.enable(XssFilter::EnableReport(report_uri))
|
||||
.enable(ExpectCt::ReportAndEnforce(Duration::seconds(30), enforce_uri));
|
||||
|
||||
dispatch!(helmet, |response: LocalResponse| {
|
||||
assert_header!(response, "X-Frame-Options",
|
||||
"ALLOW-FROM https://www.google.com");
|
||||
|
||||
assert_header!(response, "X-XSS-Protection",
|
||||
"1; report=https://www.google.com");
|
||||
|
||||
assert_header!(response, "Expect-CT",
|
||||
"max-age=30, enforce, report-uri=\"https://www.google.com\"");
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
#![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\""
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
[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"
|
|
@ -1,4 +0,0 @@
|
|||
# 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"
|
|
@ -1,30 +0,0 @@
|
|||
-----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-----
|
|
@ -1,51 +0,0 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -1,33 +0,0 @@
|
|||
#![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();
|
||||
}
|
|
@ -86,7 +86,7 @@ if [ "$1" = "--contrib" ]; then
|
|||
tera_templates
|
||||
handlebars_templates
|
||||
serve
|
||||
space_helmet
|
||||
helmet
|
||||
diesel_postgres_pool
|
||||
diesel_sqlite_pool
|
||||
diesel_mysql_pool
|
||||
|
|
Loading…
Reference in New Issue