static_web_server/
security_headers.rs1use http::header::{
10 CONTENT_SECURITY_POLICY, STRICT_TRANSPORT_SECURITY, X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS,
11};
12use hyper::{Request, Response, header::HeaderValue};
13
14use crate::body::Body;
15use crate::{Error, handler::RequestHandlerOpts};
16
17static HSTS_VALUE: HeaderValue =
19 HeaderValue::from_static("max-age=63072000; includeSubDomains; preload");
20static XFO_VALUE: HeaderValue = HeaderValue::from_static("DENY");
21static XCTO_VALUE: HeaderValue = HeaderValue::from_static("nosniff");
22static CSP_VALUE: HeaderValue = HeaderValue::from_static("frame-ancestors 'self'");
23static RP_VALUE: HeaderValue = HeaderValue::from_static("strict-origin-when-cross-origin");
24
25pub(crate) fn init(enabled: bool, handler_opts: &mut RequestHandlerOpts) {
26 handler_opts.security_headers = enabled;
27 tracing::info!(enabled, "security headers");
28}
29
30pub(crate) fn post_process<T>(
32 opts: &RequestHandlerOpts,
33 _req: &Request<T>,
34 mut resp: Response<Body>,
35) -> Result<Response<Body>, Error> {
36 if opts.security_headers {
37 append_headers(&mut resp);
38 }
39 Ok(resp)
40}
41
42pub fn append_headers(resp: &mut Response<Body>) {
45 resp.headers_mut()
47 .insert(STRICT_TRANSPORT_SECURITY, HSTS_VALUE.clone());
48
49 resp.headers_mut()
51 .insert(X_FRAME_OPTIONS, XFO_VALUE.clone());
52
53 resp.headers_mut()
55 .insert(X_CONTENT_TYPE_OPTIONS, XCTO_VALUE.clone());
56
57 resp.headers_mut()
59 .insert(CONTENT_SECURITY_POLICY, CSP_VALUE.clone());
60
61 resp.headers_mut()
63 .insert("referrer-policy", RP_VALUE.clone());
64}
65
66#[cfg(test)]
67mod tests {
68 use http::header::{
69 CONTENT_SECURITY_POLICY, STRICT_TRANSPORT_SECURITY, X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS,
70 };
71 use hyper::{Response, StatusCode};
72
73 use super::append_headers;
74
75 #[test]
76 fn append_headers_sets_hsts() {
77 let mut resp = Response::new(crate::body::empty());
78 *resp.status_mut() = StatusCode::OK;
79 append_headers(&mut resp);
80 let hsts = resp.headers().get(STRICT_TRANSPORT_SECURITY).unwrap();
81 assert_eq!(
82 hsts.to_str().unwrap(),
83 "max-age=63072000; includeSubDomains; preload"
84 );
85 }
86
87 #[test]
88 fn append_headers_sets_x_frame_options() {
89 let mut resp = Response::new(crate::body::empty());
90 *resp.status_mut() = StatusCode::OK;
91 append_headers(&mut resp);
92 let xfo = resp.headers().get(X_FRAME_OPTIONS).unwrap();
93 assert_eq!(xfo.to_str().unwrap(), "DENY");
94 }
95
96 #[test]
97 fn append_headers_sets_x_content_type_options() {
98 let mut resp = Response::new(crate::body::empty());
99 *resp.status_mut() = StatusCode::OK;
100 append_headers(&mut resp);
101 let xcto = resp.headers().get(X_CONTENT_TYPE_OPTIONS).unwrap();
102 assert_eq!(xcto.to_str().unwrap(), "nosniff");
103 }
104
105 #[test]
106 fn append_headers_sets_csp() {
107 let mut resp = Response::new(crate::body::empty());
108 *resp.status_mut() = StatusCode::OK;
109 append_headers(&mut resp);
110 let csp = resp.headers().get(CONTENT_SECURITY_POLICY).unwrap();
111 assert_eq!(csp.to_str().unwrap(), "frame-ancestors 'self'");
112 }
113
114 #[test]
115 fn append_headers_sets_all_four_headers() {
116 let mut resp = Response::new(crate::body::empty());
117 append_headers(&mut resp);
118 assert!(resp.headers().contains_key(STRICT_TRANSPORT_SECURITY));
119 assert!(resp.headers().contains_key(X_FRAME_OPTIONS));
120 assert!(resp.headers().contains_key(X_CONTENT_TYPE_OPTIONS));
121 assert!(resp.headers().contains_key(CONTENT_SECURITY_POLICY));
122 }
123
124 #[test]
125 fn append_headers_sets_referrer_policy() {
126 let mut resp = Response::new(crate::body::empty());
127 *resp.status_mut() = StatusCode::OK;
128 append_headers(&mut resp);
129 let rp = resp.headers().get("referrer-policy").unwrap();
130 assert_eq!(rp.to_str().unwrap(), "strict-origin-when-cross-origin");
131 }
132}