Skip to content

Commit d4a546b

Browse files
committed
feat: Add security headers
Issue #19
1 parent 53ba821 commit d4a546b

File tree

4 files changed

+49
-13
lines changed

4 files changed

+49
-13
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

channelserver/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "channelserver"
3-
version = "0.7.0"
3+
version = "0.8.0"
44
authors = ["jr conlin<[email protected]"]
55

66
[dependencies]

channelserver/src/main.rs

+45-9
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ use std::path::Path;
3434
use std::time::{Duration, Instant};
3535

3636
use actix::Arbiter;
37+
use actix_web::dev::HttpResponseBuilder;
3738
use actix_web::server::HttpServer;
38-
use actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse};
39+
use actix_web::{fs, http, ws, App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse};
40+
use futures::Future;
3941

4042
mod channelid;
4143
mod logging;
@@ -78,7 +80,9 @@ fn channel_route(req: &HttpRequest<session::WsChannelSessionState>) -> Result<Ht
7880
warn!(&req.state().log.log,
7981
"Invalid ChannelID specified: {:?}", id;
8082
"remote_ip" => &meta_info.remote);
81-
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());
8286
}
8387
};
8488
channel_id
@@ -94,6 +98,7 @@ fn channel_route(req: &HttpRequest<session::WsChannelSessionState>) -> Result<Ht
9498
"remote_ip" => &meta_info.remote
9599
);
96100

101+
// Cannot apply headers here.
97102
ws::start(
98103
req,
99104
session::WsChannelSession {
@@ -107,24 +112,52 @@ fn channel_route(req: &HttpRequest<session::WsChannelSessionState>) -> Result<Ht
107112
)
108113
}
109114

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+
110127
fn heartbeat(req: &HttpRequest<session::WsChannelSessionState>) -> Result<HttpResponse, Error> {
111128
// if there's more to check, add it here.
112129
let body = json!({"status": "ok", "version": env!("CARGO_PKG_VERSION")});
113-
Ok(HttpResponse::Ok()
114-
.content_type("application/json")
115-
.body(body.to_string()))
130+
Ok(add_headers(HttpResponse::Ok().content_type("application/json")).body(body.to_string()))
116131
}
117132

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

123138
fn show_version(req: &HttpRequest<session::WsChannelSessionState>) -> Result<HttpResponse, Error> {
124139
// Return the contents of the version.json file.
125-
Ok(HttpResponse::Ok()
126-
.content_type("application/json")
127-
.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+
use std::str;
151+
152+
let log = req.state().log.clone();
153+
req.body()
154+
.from_err()
155+
.and_then(move |body| {
156+
let bstr = str::from_utf8(&body).unwrap();
157+
warn!(log.log, "CSP Report"; "report"=> bstr);
158+
Ok(add_headers(&mut HttpResponse::Ok()).finish())
159+
})
160+
.responder()
128161
}
129162

130163
fn build_app(app: App<session::WsChannelSessionState>) -> App<session::WsChannelSessionState> {
@@ -141,6 +174,9 @@ fn build_app(app: App<session::WsChannelSessionState>) -> App<session::WsChannel
141174
})
142175
.resource("/__lbheartbeat__", |r| {
143176
r.method(http::Method::GET).f(lbheartbeat)
177+
})
178+
.resource("/__cspreport__", |r| {
179+
r.method(http::Method::POST).f(cspreport)
144180
});
145181
// Only add a static handler if the static directory exists.
146182
if Path::new("static/").exists() {

channelserver/src/perror.rs

+2-2
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)