initial wip
commit
3c558d9e5b
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "tower-helmet"
|
||||||
|
description = "Helps with securing your tower servers with various HTTP headers "
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Atrox <hello@atrox.dev>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/atrox/tower-helmet"
|
||||||
|
homepage = "https://github.com/atrox/tower-helmet"
|
||||||
|
categories = ["asynchronous", "network-programming", "web-programming"]
|
||||||
|
keywords = ["http", "tower", "security", "service", "header"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "0.3.18"
|
||||||
|
http = "0.2.5"
|
||||||
|
pin-project-lite = "0.2.7"
|
||||||
|
tower-layer = "0.3.1"
|
||||||
|
tower-service = "0.3.1"
|
||||||
|
lazy_static = "1.4.0"
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Atrox <hello@atrox.dev>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,40 @@
|
||||||
|
# tower-helmet
|
||||||
|
|
||||||
|
this is still very **work in progress**
|
||||||
|
|
||||||
|
a port of the beautiful [helmet.js](https://github.com/helmetjs/helmet) in the javascript world.
|
||||||
|
|
||||||
|
`tower-helmet` helps you secure your tower server by setting various HTTP headers. _It's not a silver bullet_, but it can help!
|
||||||
|
|
||||||
|
You can find a list of all available headers under the [header] module. By default (with [HelmetLayer::default]) **all of them** are enabled.
|
||||||
|
Please take a good look at [ContentSecurityPolicy]. Most of the time you will need to adapt this one to your needs.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use tower_helmet::header::{ContentSecurityPolicy, ExpectCt, XFrameOptions};
|
||||||
|
use tower_helmet::HelmetLayer;
|
||||||
|
|
||||||
|
// default layer with all security headers active
|
||||||
|
let layer = HelmetLayer::default();
|
||||||
|
|
||||||
|
// default layer with customizations applied
|
||||||
|
let mut directives = HashMap::new();
|
||||||
|
directives.insert("default-src", vec!["'self'", "https://example.com"]);
|
||||||
|
directives.insert("img-src", vec!["'self'", "data:", "https://example.com"]);
|
||||||
|
directives.insert("script-src", vec!["'self'", "'unsafe-inline'", "https://example.com"]);
|
||||||
|
let csp = ContentSecurityPolicy {
|
||||||
|
directives,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer = HelmetLayer::default()
|
||||||
|
.disable_strict_transport_security()
|
||||||
|
.disable_cross_origin_embedder_policy()
|
||||||
|
.content_security_policy(csp);
|
||||||
|
|
||||||
|
// completely blank layer, selectively enable and add headers
|
||||||
|
let layer = HelmetLayer::new()
|
||||||
|
.x_frame_options(XFrameOptions::SameOrigin)
|
||||||
|
.expect_ct(ExpectCt::default());
|
||||||
|
```
|
|
@ -0,0 +1,135 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref DEFAULT_DIRECTIVES: HashMap<&'static str, Vec<&'static str>> = {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert("default-src", vec!["'self'"]);
|
||||||
|
m.insert("base-uri", vec!["'self'"]);
|
||||||
|
m.insert("block-all-mixed-content", vec![]);
|
||||||
|
m.insert("font-src", vec!["'self'", "https:", "data:"]);
|
||||||
|
m.insert("frame-ancestors", vec!["'self'"]);
|
||||||
|
m.insert("img-src", vec!["'self'", "data:"]);
|
||||||
|
m.insert("object-src", vec!["'none'"]);
|
||||||
|
m.insert("script-src", vec!["'self'"]);
|
||||||
|
m.insert("script-src-attr", vec!["'none'"]);
|
||||||
|
m.insert("style-src", vec!["'self'", "https:", "'unsafe-inline'"]);
|
||||||
|
m.insert("upgrade-insecure-requests", vec![]);
|
||||||
|
m
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `ContentSecurityPolicy` sets the `Content-Security-Policy` header which helps mitigate cross-site scripting attacks, among other things.
|
||||||
|
/// See [MDN's introductory article on Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).
|
||||||
|
///
|
||||||
|
/// This middleware performs very little validation. You should rely on CSP checkers like [CSP Evaluator](https://csp-evaluator.withgoogle.com/) instead.
|
||||||
|
///
|
||||||
|
/// If no directive is supplied and `use_defaults` is `true`, the following policy is set (whitespace added for readability):
|
||||||
|
/// ```text
|
||||||
|
/// default-src 'self';
|
||||||
|
/// base-uri 'self';
|
||||||
|
/// block-all-mixed-content;
|
||||||
|
/// font-src 'self' https: data:;
|
||||||
|
/// frame-ancestors 'self';
|
||||||
|
/// img-src 'self' data:;
|
||||||
|
/// object-src 'none';
|
||||||
|
/// script-src 'self';
|
||||||
|
/// script-src-attr 'none';
|
||||||
|
/// style-src 'self' https: 'unsafe-inline';
|
||||||
|
/// upgrade-insecure-requests
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
/// TODO
|
||||||
|
pub struct ContentSecurityPolicy<'a> {
|
||||||
|
pub use_defaults: bool,
|
||||||
|
/// Each key is the directive name in kebab case (such as `default-src`).
|
||||||
|
/// Each value is a vector of strings for that directive
|
||||||
|
pub directives: HashMap<&'a str, Vec<&'a str>>,
|
||||||
|
/// If `true`, [the `Content-Security-Policy-Report-Only` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only) will be set instead.
|
||||||
|
pub report_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentSecurityPolicy<'static> {
|
||||||
|
/// Returns the default directives
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// default-src 'self';
|
||||||
|
/// base-uri 'self';
|
||||||
|
/// block-all-mixed-content;
|
||||||
|
/// font-src 'self' https: data:;
|
||||||
|
/// frame-ancestors 'self';
|
||||||
|
/// img-src 'self' data:;
|
||||||
|
/// object-src 'none';
|
||||||
|
/// script-src 'self';
|
||||||
|
/// script-src-attr 'none';
|
||||||
|
/// style-src 'self' https: 'unsafe-inline';
|
||||||
|
/// upgrade-insecure-requests
|
||||||
|
/// ```
|
||||||
|
pub fn default_directives() -> &'static HashMap<&'static str, Vec<&'static str>> {
|
||||||
|
&DEFAULT_DIRECTIVES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for ContentSecurityPolicy<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
ContentSecurityPolicy {
|
||||||
|
use_defaults: true,
|
||||||
|
directives: HashMap::new(),
|
||||||
|
report_only: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoHeader for ContentSecurityPolicy<'a> {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
if self.report_only {
|
||||||
|
http::header::CONTENT_SECURITY_POLICY_REPORT_ONLY
|
||||||
|
} else {
|
||||||
|
http::header::CONTENT_SECURITY_POLICY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
let directives = if self.use_defaults {
|
||||||
|
if self.directives.is_empty() {
|
||||||
|
DEFAULT_DIRECTIVES.clone()
|
||||||
|
} else {
|
||||||
|
let mut directives = DEFAULT_DIRECTIVES.clone();
|
||||||
|
directives.extend(self.directives.clone().into_iter());
|
||||||
|
|
||||||
|
directives
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.directives.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let header = directives
|
||||||
|
.iter()
|
||||||
|
.map(|(key, values)| format!("{} {}", key, values.join(" ")))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("; ");
|
||||||
|
|
||||||
|
HeaderValue::from_str(header.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wow() {
|
||||||
|
for (k, v) in DEFAULT_DIRECTIVES.iter() {
|
||||||
|
println!("{}:{:?}", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
let csp = ContentSecurityPolicy::default();
|
||||||
|
|
||||||
|
assert_eq!(csp.header_name(), "Content-Security-Policy");
|
||||||
|
assert_eq!(csp.header_value().unwrap(), "frame-ancestors 'self'; object-src 'none'; style-src 'self' https: 'unsafe-inline'; default-src 'self'; img-src 'self' data:; script-src-attr 'none'; upgrade-insecure-requests ; script-src 'self'; block-all-mixed-content ; base-uri 'self'; font-src 'self' https: data:");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
/// `CrossOriginEmbedderPolicy` sets the `Cross-Origin-Embedder-Policy` header to `require-corp`.
|
||||||
|
/// See [MDN's article on this header](https://developer.cdn.mozilla.net/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy) for more.
|
||||||
|
pub struct CrossOriginEmbedderPolicy;
|
||||||
|
|
||||||
|
impl Default for CrossOriginEmbedderPolicy {
|
||||||
|
fn default() -> Self {
|
||||||
|
CrossOriginEmbedderPolicy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for CrossOriginEmbedderPolicy {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
HeaderName::from_static("cross-origin-embedder-policy")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str("require-corp")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
/// `CrossOriginOpenerPolicy` sets the `Cross-Origin-Opener-Policy` header.
|
||||||
|
/// For more, see [MDN's article on this header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy).
|
||||||
|
pub enum CrossOriginOpenerPolicy {
|
||||||
|
UnsafeNone,
|
||||||
|
SameOriginAllowPopups,
|
||||||
|
SameOrigin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CrossOriginOpenerPolicy {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
CrossOriginOpenerPolicy::UnsafeNone => "unsafe-none",
|
||||||
|
CrossOriginOpenerPolicy::SameOriginAllowPopups => "same-origin-allow-popups",
|
||||||
|
CrossOriginOpenerPolicy::SameOrigin => "same-origin",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CrossOriginOpenerPolicy {
|
||||||
|
fn default() -> Self {
|
||||||
|
CrossOriginOpenerPolicy::SameOrigin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for CrossOriginOpenerPolicy {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
HeaderName::from_static("cross-origin-opener-policy")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str(self.to_string().as_str())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
/// `CrossOriginResourcePolicy` sets the `Cross-Origin-Resource-Policy` header.
|
||||||
|
/// For more, see ["Consider deploying Cross-Origin Resource Policy](https://resourcepolicy.fyi/) and [MDN's article on this header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy).
|
||||||
|
pub enum CrossOriginResourcePolicy {
|
||||||
|
SameSite,
|
||||||
|
SameOrigin,
|
||||||
|
CrossOrigin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CrossOriginResourcePolicy {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
CrossOriginResourcePolicy::SameSite => "same-site",
|
||||||
|
CrossOriginResourcePolicy::SameOrigin => "same-origin",
|
||||||
|
CrossOriginResourcePolicy::CrossOrigin => "cross-origin",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CrossOriginResourcePolicy {
|
||||||
|
fn default() -> Self {
|
||||||
|
CrossOriginResourcePolicy::SameOrigin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for CrossOriginResourcePolicy {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
HeaderName::from_static("cross-origin-resource-policy")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str(self.to_string().as_str())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// `ExpectCt` sets the `Expect-CT` header which helps mitigate misissued SSL certificates.
|
||||||
|
/// See [MDN's article on Certificate Transparency](https://developer.mozilla.org/en-US/docs/Web/Security/Certificate_Transparency) and the [`Expect-CT` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT) for more.
|
||||||
|
pub struct ExpectCt {
|
||||||
|
/// `max_age` is the number of seconds to expect Certificate Transparency.
|
||||||
|
pub max_age: Duration,
|
||||||
|
/// If `true`, the user agent (usually a browser) should refuse future connections that violate its Certificate Transparency policy.
|
||||||
|
pub enforce: bool,
|
||||||
|
/// If set, complying user agents will report Certificate Transparency failures to this URL.
|
||||||
|
pub report_uri: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ExpectCt {
|
||||||
|
fn default() -> Self {
|
||||||
|
ExpectCt {
|
||||||
|
max_age: Duration::from_secs(0),
|
||||||
|
enforce: false,
|
||||||
|
report_uri: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for ExpectCt {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
HeaderName::from_static("expect-ct")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
let mut directives = vec![format!("max-age={}", self.max_age.as_secs())];
|
||||||
|
|
||||||
|
if self.enforce {
|
||||||
|
directives.push("enforce".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(report_uri) = self.report_uri.as_ref() {
|
||||||
|
directives.push(format!("report-uri={}", report_uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderValue::from_str(directives.join(", ").as_str())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
mod content_security_policy;
|
||||||
|
mod cross_origin_embedder_policy;
|
||||||
|
mod cross_origin_opener_policy;
|
||||||
|
mod cross_origin_resource_policy;
|
||||||
|
mod expect_ct;
|
||||||
|
mod origin_agent_cluster;
|
||||||
|
mod referrer_policy;
|
||||||
|
mod strict_transport_security;
|
||||||
|
mod x_content_type_options;
|
||||||
|
mod x_dns_prefetch_control;
|
||||||
|
mod x_download_options;
|
||||||
|
mod x_frame_options;
|
||||||
|
mod x_permitted_cross_domain_policies;
|
||||||
|
mod x_xss_protection;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
content_security_policy::ContentSecurityPolicy,
|
||||||
|
cross_origin_embedder_policy::CrossOriginEmbedderPolicy,
|
||||||
|
cross_origin_opener_policy::CrossOriginOpenerPolicy,
|
||||||
|
cross_origin_resource_policy::CrossOriginResourcePolicy,
|
||||||
|
expect_ct::ExpectCt,
|
||||||
|
origin_agent_cluster::OriginAgentCluster,
|
||||||
|
referrer_policy::{ReferrerPolicy, ReferrerPolicyValue},
|
||||||
|
strict_transport_security::StrictTransportSecurity,
|
||||||
|
x_content_type_options::XContentTypeOptions,
|
||||||
|
x_dns_prefetch_control::XDnsPrefetchControl,
|
||||||
|
x_download_options::XDownloadOptions,
|
||||||
|
x_frame_options::XFrameOptions,
|
||||||
|
x_permitted_cross_domain_policies::XPermittedCrossDomainPolicies,
|
||||||
|
x_xss_protection::XXSSProtection,
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
/// `OriginAgentCluster` sets the `Origin-Agent-Cluster` header, which provides a mechanism to allow web applications to isolate their origins.
|
||||||
|
/// Read more about it [in the spec](https://whatpr.org/html/6214/origin.html#origin-keyed-agent-clusters).
|
||||||
|
pub struct OriginAgentCluster;
|
||||||
|
|
||||||
|
impl Default for OriginAgentCluster {
|
||||||
|
fn default() -> Self {
|
||||||
|
OriginAgentCluster
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for OriginAgentCluster {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
HeaderName::from_static("origin-agent-cluster")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str("?1")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
/// `ReferrerPolicy` sets the `Referrer-Policy` header which controls what information is set in [the `Referer` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer).
|
||||||
|
/// See ["Referer header: privacy and security concerns"](https://developer.mozilla.org/en-US/docs/Web/Security/Referer_header:_privacy_and_security_concerns) and [the header's documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) on MDN for more.
|
||||||
|
pub struct ReferrerPolicy(pub Vec<ReferrerPolicyValue>);
|
||||||
|
|
||||||
|
pub enum ReferrerPolicyValue {
|
||||||
|
NoReferrer,
|
||||||
|
NoReferrerWhenDowngrade,
|
||||||
|
Origin,
|
||||||
|
OriginWhenCrossOrigin,
|
||||||
|
SameOrigin,
|
||||||
|
StrictOrigin,
|
||||||
|
StrictOriginWhenCrossOrigin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ReferrerPolicyValue {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
ReferrerPolicyValue::NoReferrer => "no-referrer",
|
||||||
|
ReferrerPolicyValue::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
|
||||||
|
ReferrerPolicyValue::Origin => "origin",
|
||||||
|
ReferrerPolicyValue::OriginWhenCrossOrigin => "origin-when-cross-origin",
|
||||||
|
ReferrerPolicyValue::SameOrigin => "same-origin",
|
||||||
|
ReferrerPolicyValue::StrictOrigin => "strict-origin",
|
||||||
|
ReferrerPolicyValue::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ReferrerPolicy {
|
||||||
|
fn default() -> Self {
|
||||||
|
ReferrerPolicy(vec![ReferrerPolicyValue::NoReferrer])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for ReferrerPolicy {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
http::header::REFERRER_POLICY
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
let s: Vec<String> = self.0.iter().map(|v| v.to_string()).collect();
|
||||||
|
HeaderValue::from_str(s.join(",").as_str())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// `StrictTransportSecurity` sets the `Strict-Transport-Security` header which tells browsers to prefer HTTPS over insecure HTTP.
|
||||||
|
/// See [the documentation on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) for more.
|
||||||
|
pub struct StrictTransportSecurity {
|
||||||
|
/// `max_age` is the number of seconds browsers should remember to prefer HTTPS. It defaults to `15552000`, which is 180 days.
|
||||||
|
pub max_age: Duration,
|
||||||
|
/// `include_subdomains` dictates whether to include the `includeSubDomains` directive, which makes this policy extend to subdomains. It defaults to `true`.
|
||||||
|
pub include_subdomains: bool,
|
||||||
|
/// If true, it adds the `preload` directive, expressing intent to add your HSTS policy to browsers.
|
||||||
|
/// See [the "Preloading Strict Transport Security" section on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#Preloading_Strict_Transport_Security) for more.
|
||||||
|
pub preload: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StrictTransportSecurity {
|
||||||
|
fn default() -> Self {
|
||||||
|
StrictTransportSecurity {
|
||||||
|
max_age: Duration::from_secs(15552000),
|
||||||
|
include_subdomains: true,
|
||||||
|
preload: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for StrictTransportSecurity {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
http::header::STRICT_TRANSPORT_SECURITY
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
let mut directives = vec![format!("max-age={}", self.max_age.as_secs())];
|
||||||
|
|
||||||
|
if self.include_subdomains {
|
||||||
|
directives.push("includeSubdomains".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.preload {
|
||||||
|
directives.push("preload".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderValue::from_str(directives.join("; ").as_str())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
/// `XContentTypeOptions` sets the `X-Content-Type-Options` header to `nosniff`.
|
||||||
|
/// This mitigates [MIME type sniffing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#MIME_sniffing) which can cause security vulnerabilities.
|
||||||
|
/// See [documentation for this header on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) for more.
|
||||||
|
pub struct XContentTypeOptions;
|
||||||
|
|
||||||
|
impl Default for XContentTypeOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
XContentTypeOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for XContentTypeOptions {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
http::header::X_CONTENT_TYPE_OPTIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str("nosniff")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
/// `XDnsPrefetchControl` sets the `X-DNS-Prefetch-Control` header to help control DNS prefetching, which can improve user privacy at the expense of performance.
|
||||||
|
/// See [documentation on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) for more.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct XDnsPrefetchControl(
|
||||||
|
/// Is indictating whether to enable DNS prefetching.
|
||||||
|
pub bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl IntoHeader for XDnsPrefetchControl {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
http::header::X_DNS_PREFETCH_CONTROL
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str(if self.0 { "on" } else { "off" })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
/// `XDownloadOptions` sets the `X-Download-Options` header, which is specific to Internet Explorer 8.
|
||||||
|
/// It forces potentially-unsafe downloads to be saved, mitigating execution of HTML in your site's context.
|
||||||
|
/// For more, see [this old post on MSDN](https://docs.microsoft.com/en-us/archive/blogs/ie/ie8-security-part-v-comprehensive-protection).
|
||||||
|
pub struct XDownloadOptions;
|
||||||
|
|
||||||
|
impl Default for XDownloadOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
XDownloadOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for XDownloadOptions {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
HeaderName::from_static("x-download-options")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str("noopen")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
/// `XFrameOptions` sets the `X-Frame-Options` header to help you mitigate [clickjacking attacks](https://en.wikipedia.org/wiki/Clickjacking).
|
||||||
|
/// This header is superseded by [the `frame-ancestors` Content Security Policy directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors) but is still useful on old browsers.
|
||||||
|
/// For more, see `helmet.contentSecurityPolicy`, as well as [the documentation on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).
|
||||||
|
///
|
||||||
|
/// `DENY` or `SAMEORIGIN`. (A legacy directive, `ALLOW-FROM`, is not supported by this crate. [Read more here.](https://github.com/helmetjs/helmet/wiki/How-to-use-X%E2%80%93Frame%E2%80%93Options's-%60ALLOW%E2%80%93FROM%60-directive))
|
||||||
|
pub enum XFrameOptions {
|
||||||
|
Deny,
|
||||||
|
SameOrigin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for XFrameOptions {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
XFrameOptions::SameOrigin => "SAMEORIGIN",
|
||||||
|
XFrameOptions::Deny => "DENY",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for XFrameOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
XFrameOptions::SameOrigin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for XFrameOptions {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
http::header::X_FRAME_OPTIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str(self.to_string().as_str())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
/// `XPermittedCrossDomainPolicies` sets the `X-Permitted-Cross-Domain-Policies` header, which tells some clients (mostly Adobe products) your domain's policy for loading cross-domain content.
|
||||||
|
/// See [the description on OWASP](https://owasp.org/www-project-secure-headers/) for more.
|
||||||
|
pub enum XPermittedCrossDomainPolicies {
|
||||||
|
None,
|
||||||
|
MasterOnly,
|
||||||
|
ByContentType,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for XPermittedCrossDomainPolicies {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
XPermittedCrossDomainPolicies::None => "none",
|
||||||
|
XPermittedCrossDomainPolicies::MasterOnly => "master-only",
|
||||||
|
XPermittedCrossDomainPolicies::ByContentType => "by-content-type",
|
||||||
|
XPermittedCrossDomainPolicies::All => "all",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for XPermittedCrossDomainPolicies {
|
||||||
|
fn default() -> Self {
|
||||||
|
XPermittedCrossDomainPolicies::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for XPermittedCrossDomainPolicies {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
HeaderName::from_static("x-permitted-cross-domain-policies")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str(self.to_string().as_str())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::IntoHeader;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::HeaderValue;
|
||||||
|
|
||||||
|
/// `XXSSProtection` disables browsers' buggy cross-site scripting filter by setting the `X-XSS-Protection` header to `0`.
|
||||||
|
/// See [discussion about disabling the header here](https://github.com/helmetjs/helmet/issues/230) and [documentation on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection).
|
||||||
|
pub struct XXSSProtection;
|
||||||
|
|
||||||
|
impl Default for XXSSProtection {
|
||||||
|
fn default() -> Self {
|
||||||
|
XXSSProtection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoHeader for XXSSProtection {
|
||||||
|
fn header_name(&self) -> HeaderName {
|
||||||
|
http::header::X_XSS_PROTECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue> {
|
||||||
|
HeaderValue::from_str("0")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,364 @@
|
||||||
|
//! # Overview
|
||||||
|
//!
|
||||||
|
//! `tower-helmet` helps you secure your tower server by setting various HTTP headers. _It's not a silver bullet_, but it can help!
|
||||||
|
//!
|
||||||
|
//! You can find a list of all available headers under the [header] module. By default (with [HelmetLayer::default]) **all of them** are enabled.
|
||||||
|
//! Please take a good look at [ContentSecurityPolicy]. Most of the time you will need to adapt this one to your needs.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use tower_helmet::header::{ContentSecurityPolicy, ExpectCt, XFrameOptions};
|
||||||
|
//! use tower_helmet::HelmetLayer;
|
||||||
|
//!
|
||||||
|
//! // default layer with all security headers active
|
||||||
|
//! let layer = HelmetLayer::default();
|
||||||
|
//!
|
||||||
|
//! // default layer with customizations applied
|
||||||
|
//! let mut directives = HashMap::new();
|
||||||
|
//! directives.insert("default-src", vec!["'self'", "https://example.com"]);
|
||||||
|
//! directives.insert("img-src", vec!["'self'", "data:", "https://example.com"]);
|
||||||
|
//! directives.insert("script-src", vec!["'self'", "'unsafe-inline'", "https://example.com"]);
|
||||||
|
//! let csp = ContentSecurityPolicy {
|
||||||
|
//! directives,
|
||||||
|
//! ..Default::default()
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! let layer = HelmetLayer::default()
|
||||||
|
//! .disable_strict_transport_security()
|
||||||
|
//! .disable_cross_origin_embedder_policy()
|
||||||
|
//! .content_security_policy(csp);
|
||||||
|
//!
|
||||||
|
//! // completely blank layer, selectively enable and add headers
|
||||||
|
//! let layer = HelmetLayer::new()
|
||||||
|
//! .x_frame_options(XFrameOptions::SameOrigin)
|
||||||
|
//! .expect_ct(ExpectCt::default());
|
||||||
|
//! ```
|
||||||
|
pub mod header;
|
||||||
|
|
||||||
|
use futures::ready;
|
||||||
|
use http::header::{HeaderName, InvalidHeaderValue};
|
||||||
|
use http::{HeaderMap, HeaderValue, Request, Response};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use tower_layer::Layer;
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
use header::{
|
||||||
|
ContentSecurityPolicy, CrossOriginEmbedderPolicy, CrossOriginOpenerPolicy,
|
||||||
|
CrossOriginResourcePolicy, ExpectCt, OriginAgentCluster, ReferrerPolicy,
|
||||||
|
StrictTransportSecurity, XContentTypeOptions, XDnsPrefetchControl, XDownloadOptions,
|
||||||
|
XFrameOptions, XPermittedCrossDomainPolicies, XXSSProtection,
|
||||||
|
};
|
||||||
|
|
||||||
|
trait IntoHeader {
|
||||||
|
fn header_name(&self) -> HeaderName;
|
||||||
|
fn header_value(&self) -> Result<HeaderValue, InvalidHeaderValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HelmetLayer
|
||||||
|
pub struct HelmetLayer<'a> {
|
||||||
|
content_security_policy: Option<ContentSecurityPolicy<'a>>,
|
||||||
|
cross_origin_embedder_policy: Option<CrossOriginEmbedderPolicy>,
|
||||||
|
cross_origin_opener_policy: Option<CrossOriginOpenerPolicy>,
|
||||||
|
cross_origin_resource_policy: Option<CrossOriginResourcePolicy>,
|
||||||
|
expect_ct: Option<ExpectCt>,
|
||||||
|
origin_agent_cluster: Option<OriginAgentCluster>,
|
||||||
|
referrer_policy: Option<ReferrerPolicy>,
|
||||||
|
strict_transport_security: Option<StrictTransportSecurity>,
|
||||||
|
x_content_type_options: Option<XContentTypeOptions>,
|
||||||
|
x_dns_prefetch_control: Option<XDnsPrefetchControl>,
|
||||||
|
x_download_options: Option<XDownloadOptions>,
|
||||||
|
x_frame_options: Option<XFrameOptions>,
|
||||||
|
x_permitted_cross_domain_policies: Option<XPermittedCrossDomainPolicies>,
|
||||||
|
x_xss_protection: Option<XXSSProtection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for HelmetLayer<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
HelmetLayer {
|
||||||
|
content_security_policy: Some(ContentSecurityPolicy::default()),
|
||||||
|
cross_origin_embedder_policy: Some(CrossOriginEmbedderPolicy::default()),
|
||||||
|
cross_origin_opener_policy: Some(CrossOriginOpenerPolicy::default()),
|
||||||
|
cross_origin_resource_policy: Some(CrossOriginResourcePolicy::default()),
|
||||||
|
expect_ct: Some(ExpectCt::default()),
|
||||||
|
origin_agent_cluster: Some(OriginAgentCluster::default()),
|
||||||
|
referrer_policy: Some(ReferrerPolicy::default()),
|
||||||
|
strict_transport_security: Some(StrictTransportSecurity::default()),
|
||||||
|
x_content_type_options: Some(XContentTypeOptions::default()),
|
||||||
|
x_dns_prefetch_control: Some(XDnsPrefetchControl::default()),
|
||||||
|
x_download_options: Some(XDownloadOptions::default()),
|
||||||
|
x_frame_options: Some(XFrameOptions::default()),
|
||||||
|
x_permitted_cross_domain_policies: Some(XPermittedCrossDomainPolicies::default()),
|
||||||
|
x_xss_protection: Some(XXSSProtection::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> HelmetLayer<'a> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
HelmetLayer {
|
||||||
|
content_security_policy: None,
|
||||||
|
cross_origin_embedder_policy: None,
|
||||||
|
cross_origin_opener_policy: None,
|
||||||
|
cross_origin_resource_policy: None,
|
||||||
|
expect_ct: None,
|
||||||
|
origin_agent_cluster: None,
|
||||||
|
referrer_policy: None,
|
||||||
|
strict_transport_security: None,
|
||||||
|
x_content_type_options: None,
|
||||||
|
x_dns_prefetch_control: None,
|
||||||
|
x_download_options: None,
|
||||||
|
x_frame_options: None,
|
||||||
|
x_permitted_cross_domain_policies: None,
|
||||||
|
x_xss_protection: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recommended_defaults() -> Self {
|
||||||
|
HelmetLayer::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content_security_policy(&mut self, v: ContentSecurityPolicy<'a>) -> &mut Self {
|
||||||
|
self.content_security_policy = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_content_security_policy(&mut self) -> &mut Self {
|
||||||
|
self.content_security_policy = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cross_origin_embedder_policy(&mut self, v: CrossOriginEmbedderPolicy) -> &mut Self {
|
||||||
|
self.cross_origin_embedder_policy = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_cross_origin_embedder_policy(&mut self) -> &mut Self {
|
||||||
|
self.cross_origin_embedder_policy = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cross_origin_opener_policy(&mut self, v: CrossOriginOpenerPolicy) -> &mut Self {
|
||||||
|
self.cross_origin_opener_policy = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_cross_origin_opener_policy(&mut self) -> &mut Self {
|
||||||
|
self.cross_origin_opener_policy = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cross_origin_resource_policy(&mut self, v: CrossOriginResourcePolicy) -> &mut Self {
|
||||||
|
self.cross_origin_resource_policy = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_cross_origin_resource_policy(&mut self) -> &mut Self {
|
||||||
|
self.cross_origin_resource_policy = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expect_ct(&mut self, v: ExpectCt) -> &mut Self {
|
||||||
|
self.expect_ct = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_expect_ct(&mut self) -> &mut Self {
|
||||||
|
self.expect_ct = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn origin_agent_cluster(&mut self, v: OriginAgentCluster) -> &mut Self {
|
||||||
|
self.origin_agent_cluster = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_origin_agent_cluster(&mut self) -> &mut Self {
|
||||||
|
self.origin_agent_cluster = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn referrer_policy(&mut self, v: ReferrerPolicy) -> &mut Self {
|
||||||
|
self.referrer_policy = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_referrer_policy(&mut self) -> &mut Self {
|
||||||
|
self.referrer_policy = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strict_transport_security(&mut self, v: StrictTransportSecurity) -> &mut Self {
|
||||||
|
self.strict_transport_security = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_strict_transport_security(&mut self) -> &mut Self {
|
||||||
|
self.strict_transport_security = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x_content_type_options(&mut self, v: XContentTypeOptions) -> &mut Self {
|
||||||
|
self.x_content_type_options = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_x_content_type_options(&mut self) -> &mut Self {
|
||||||
|
self.x_content_type_options = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x_dns_prefetch_control(&mut self, v: XDnsPrefetchControl) -> &mut Self {
|
||||||
|
self.x_dns_prefetch_control = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_x_dns_prefetch_control(&mut self) -> &mut Self {
|
||||||
|
self.x_dns_prefetch_control = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x_download_options(&mut self, v: XDownloadOptions) -> &mut Self {
|
||||||
|
self.x_download_options = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_x_download_options(&mut self) -> &mut Self {
|
||||||
|
self.x_download_options = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x_frame_options(&mut self, v: XFrameOptions) -> &mut Self {
|
||||||
|
self.x_frame_options = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_x_frame_options(&mut self) -> &mut Self {
|
||||||
|
self.x_frame_options = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x_permitted_cross_domain_policies(
|
||||||
|
&mut self,
|
||||||
|
v: XPermittedCrossDomainPolicies,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.x_permitted_cross_domain_policies = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_x_permitted_cross_domain_policies(&mut self) -> &mut Self {
|
||||||
|
self.x_permitted_cross_domain_policies = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x_xss_protection(&mut self, v: XXSSProtection) -> &mut Self {
|
||||||
|
self.x_xss_protection = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn disable_x_xss_protection(&mut self) -> &mut Self {
|
||||||
|
self.x_xss_protection = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S> Layer<S> for HelmetLayer<'a> {
|
||||||
|
type Service = HelmetService<S>;
|
||||||
|
|
||||||
|
fn layer(&self, service: S) -> Self::Service {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
|
||||||
|
if let Some(h) = &self.content_security_policy {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.cross_origin_embedder_policy {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.cross_origin_opener_policy {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.cross_origin_resource_policy {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.expect_ct {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.origin_agent_cluster {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.referrer_policy {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.strict_transport_security {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.x_content_type_options {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.x_dns_prefetch_control {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.x_download_options {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.x_frame_options {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.x_permitted_cross_domain_policies {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(h) = &self.x_xss_protection {
|
||||||
|
headers.insert(h.header_name(), h.header_value().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
HelmetService {
|
||||||
|
inner: service,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HelmetService<S> {
|
||||||
|
inner: S,
|
||||||
|
headers: HeaderMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for HelmetService<S>
|
||||||
|
where
|
||||||
|
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
|
||||||
|
{
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = ResponseFuture<S::Future>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.inner.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, request: Request<ReqBody>) -> Self::Future {
|
||||||
|
ResponseFuture {
|
||||||
|
future: self.inner.call(request),
|
||||||
|
headers: self.headers.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
/// Response future for [`HelmetService`].
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ResponseFuture<F> {
|
||||||
|
#[pin]
|
||||||
|
future: F,
|
||||||
|
|
||||||
|
headers: HeaderMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, ResBody, E> Future for ResponseFuture<F>
|
||||||
|
where
|
||||||
|
F: Future<Output = Result<Response<ResBody>, E>>,
|
||||||
|
{
|
||||||
|
type Output = F::Output;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.project();
|
||||||
|
let mut res: Response<ResBody> = ready!(this.future.poll(cx)?);
|
||||||
|
let headers = res.headers_mut();
|
||||||
|
|
||||||
|
for (name, value) in this.headers.iter() {
|
||||||
|
headers.insert(name, value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Ok(res))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue