Skip to content

Commit 8a70fff

Browse files
authored
feat: Add security headers (#58)
* feat: Add security headers Issue #19
1 parent cd44e85 commit 8a70fff

File tree

4 files changed

+48
-13
lines changed

4 files changed

+48
-13
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

channelserver/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "channelserver"
3-
version = "0.7.1"
3+
version = "0.8.0"
44
authors = ["jr conlin<[email protected]"]
55

66
[dependencies]

channelserver/src/main.rs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@ extern crate maxminddb;
3131
extern crate reqwest;
3232

3333
use std::path::Path;
34+
use std::str;
3435
use std::time::{Duration, Instant};
3536

3637
use actix::Arbiter;
38+
use actix_web::dev::HttpResponseBuilder;
3739
use actix_web::server::HttpServer;
38-
use actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse};
40+
use actix_web::{fs, http, ws, App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse};
41+
use futures::Future;
3942

4043
mod channelid;
4144
mod logging;
@@ -77,7 +80,9 @@ fn channel_route(req: &HttpRequest<session::WsChannelSessionState>) -> Result<Ht
7780
warn!(&req.state().log.log,
7881
"Invalid ChannelID specified: {:?}", id;
7982
"remote_ip" => &meta_info.remote);
80-
return Ok(HttpResponse::new(http::StatusCode::NOT_FOUND));
83+
let mut resp =
84+
HttpResponse::new(http::StatusCode::NOT_FOUND).into_builder();
85+
return Ok(add_headers(&mut resp).finish());
8186
}
8287
};
8388
channel_id
@@ -93,6 +98,7 @@ fn channel_route(req: &HttpRequest<session::WsChannelSessionState>) -> Result<Ht
9398
"remote_ip" => &meta_info.remote
9499
);
95100

101+
// Cannot apply headers here.
96102
ws::start(
97103
req,
98104
session::WsChannelSession {
@@ -106,24 +112,50 @@ fn channel_route(req: &HttpRequest<session::WsChannelSessionState>) -> Result<Ht
106112
)
107113
}
108114

115+
/// Inject the FoxSec headers into all HTTP responses
116+
fn add_headers<'a>(resp: &'a mut HttpResponseBuilder) -> &'a mut HttpResponseBuilder {
117+
resp.header(
118+
"Content-Security-Policy",
119+
"default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; report-uri /__cspreport__",
120+
)
121+
.header("X-Content-Type-Options", "nosniff")
122+
.header("X-Frame-Options", "deny")
123+
.header("X-XSS-Protection", "1; mode=block")
124+
.header("Strict-Transport-Security", "max-age=63072000")
125+
}
126+
109127
fn heartbeat(req: &HttpRequest<session::WsChannelSessionState>) -> Result<HttpResponse, Error> {
110128
// if there's more to check, add it here.
111129
let body = json!({"status": "ok", "version": env!("CARGO_PKG_VERSION")});
112-
Ok(HttpResponse::Ok()
113-
.content_type("application/json")
114-
.body(body.to_string()))
130+
Ok(add_headers(HttpResponse::Ok().content_type("application/json")).body(body.to_string()))
115131
}
116132

117133
fn lbheartbeat(req: &HttpRequest<session::WsChannelSessionState>) -> Result<HttpResponse, Error> {
118134
// load balance heartbeat. Doesn't matter what's returned, aside from a 200
119-
Ok(HttpResponse::Ok().into())
135+
Ok(add_headers(&mut HttpResponse::Ok()).finish())
120136
}
121137

122138
fn show_version(req: &HttpRequest<session::WsChannelSessionState>) -> Result<HttpResponse, Error> {
123139
// Return the contents of the version.json file.
124-
Ok(HttpResponse::Ok()
125-
.content_type("application/json")
126-
.body(include_str!("../version.json")))
140+
Ok(
141+
add_headers(&mut HttpResponse::Ok().content_type("application/json"))
142+
.body(include_str!("../version.json")),
143+
)
144+
}
145+
146+
/// Dump the "CSP report" as a warning message.
147+
fn cspreport(
148+
req: &HttpRequest<session::WsChannelSessionState>,
149+
) -> Box<Future<Item = HttpResponse, Error = Error>> {
150+
let log = req.state().log.clone();
151+
req.body()
152+
.from_err()
153+
.and_then(move |body| {
154+
let bstr = str::from_utf8(&body).unwrap();
155+
warn!(log.log, "CSP Report"; "report"=> bstr);
156+
Ok(add_headers(&mut HttpResponse::Ok()).finish())
157+
})
158+
.responder()
127159
}
128160

129161
fn build_app(app: App<session::WsChannelSessionState>) -> App<session::WsChannelSessionState> {
@@ -140,6 +172,9 @@ fn build_app(app: App<session::WsChannelSessionState>) -> App<session::WsChannel
140172
})
141173
.resource("/__lbheartbeat__", |r| {
142174
r.method(http::Method::GET).f(lbheartbeat)
175+
})
176+
.resource("/__cspreport__", |r| {
177+
r.method(http::Method::POST).f(cspreport)
143178
});
144179
// Only add a static handler if the static directory exists.
145180
if Path::new("static/").exists() {

channelserver/src/perror.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ pub struct HandlerError {
1111

1212
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
1313
pub enum HandlerErrorKind {
14-
#[fail(display = "Excess Data Exchanged: _0")]
14+
#[fail(display = "Excess Data Exchanged: {:?}", _0)]
1515
XSDataErr(String),
16-
#[fail(display = "Excess Messages: _0")]
16+
#[fail(display = "Excess Messages: {:?}", _0)]
1717
XSMessageErr(String),
1818
#[fail(display = "IO Error: {:?}", _0)]
1919
IOError(String),

0 commit comments

Comments
 (0)