From 5e67c89b4b472154dcb79491f3804192f586c721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 19 Oct 2016 11:02:21 +0200 Subject: [PATCH] Remove obsolete dapps and update security headers (#2694) * Embed allowed only on signer port * Adding security headers to dapps * Adding security headers to signer * Removing old dapps --- Cargo.lock | 20 ------------------- dapps/Cargo.toml | 7 +------ dapps/src/apps/mod.rs | 28 ++++++--------------------- dapps/src/handlers/content.rs | 33 +++++++++++++++++--------------- dapps/src/handlers/mod.rs | 18 +++++++++++++++++ dapps/src/lib.rs | 12 +++++++++++- dapps/src/page/builtin.rs | 14 +++++++------- dapps/src/page/handler.rs | 24 +++++++++++------------ dapps/src/page/local.rs | 8 ++++---- dapps/src/tests/api.rs | 6 +++++- dapps/src/tests/authorization.rs | 3 ++- dapps/src/tests/fetch.rs | 3 ++- dapps/src/tests/helpers.rs | 4 ++++ dapps/src/tests/redirection.rs | 6 +++++- devtools/src/http_client.rs | 15 +++++++++++++++ parity/dapps.rs | 9 +++++++-- signer/src/tests/mod.rs | 4 ++++ signer/src/ws_server/session.rs | 2 ++ 18 files changed, 123 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c63bff471..240b0588b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,8 +339,6 @@ dependencies = [ "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps-glue 1.4.0", "parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", - "parity-dapps-status 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", - "parity-dapps-wallet 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-ui 1.4.0", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1202,22 +1200,6 @@ dependencies = [ "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", ] -[[package]] -name = "parity-dapps-status" -version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" -dependencies = [ - "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", -] - -[[package]] -name = "parity-dapps-wallet" -version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" -dependencies = [ - "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", -] - [[package]] name = "parity-ui" version = "1.4.0" @@ -1999,8 +1981,6 @@ dependencies = [ "checksum odds 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "b28c06e81b0f789122d415d6394b5fe849bde8067469f4c2980d3cdc10c78ec1" "checksum parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "" "checksum parity-dapps-home 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "" -"checksum parity-dapps-status 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "" -"checksum parity-dapps-wallet 1.4.0 (git+https://github.com/ethcore/parity-ui.git)" = "" "checksum parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e0fd1be2c3cf5fef20a6d18fec252c4f3c87c14fc3039002eb7d4ed91e436826" "checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026" "checksum phf_codegen 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "8af7ae7c3f75a502292b491e5cc0a1f69e3407744abe6e57e2a3b712bb82f01d" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 8eb343e68..733bb9b0e 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -31,9 +31,7 @@ parity-ui = { path = "./ui" } parity-dapps-glue = { path = "./js-glue" } ### DEPRECATED parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } -parity-dapps-status = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } parity-dapps-home = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4" } -parity-dapps-wallet = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4", optional = true } ### /DEPRECATED mime_guess = { version = "1.6.1" } @@ -43,14 +41,11 @@ clippy = { version = "0.0.90", optional = true} serde_codegen = { version = "0.8", optional = true } [features] -default = ["serde_codegen", "extra-dapps"] -extra-dapps = ["parity-dapps-wallet"] +default = ["serde_codegen"] nightly = ["serde_macros"] dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"] use-precompiled-js = [ "parity-ui/use-precompiled-js", - "parity-dapps-status/use-precompiled-js", "parity-dapps-home/use-precompiled-js", - "parity-dapps-wallet/use-precompiled-js" ] diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 90f22be12..145c3e820 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -26,7 +26,6 @@ pub mod urlhint; pub mod fetcher; pub mod manifest; -extern crate parity_dapps_status; extern crate parity_dapps_home; extern crate parity_ui; @@ -50,37 +49,22 @@ pub fn utils() -> Box { Box::new(PageEndpoint::with_prefix(parity_dapps_home::App::default(), UTILS_PATH.to_owned())) } -pub fn all_endpoints(dapps_path: String) -> Endpoints { +pub fn all_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { // fetch fs dapps at first to avoid overwriting builtins let mut pages = fs::local_endpoints(dapps_path); - // Home page needs to be safe embed - // because we use Cross-Origin LocalStorage. - // TODO [ToDr] Account naming should be moved to parity. - pages.insert("home".into(), Box::new( - PageEndpoint::new_safe_to_embed(parity_dapps_home::App::default()) - )); + // NOTE [ToDr] Dapps will be currently embeded on 8180 pages.insert("ui".into(), Box::new( - PageEndpoint::new_safe_to_embed(NewUi::default()) + PageEndpoint::new_safe_to_embed(NewUi::default(), signer_port) )); - pages.insert("proxy".into(), ProxyPac::boxed()); - insert::(&mut pages, "status"); - insert::(&mut pages, "parity"); - // Optional dapps - wallet_page(&mut pages); + pages.insert("proxy".into(), ProxyPac::boxed()); + insert::(&mut pages, "home"); + pages } -#[cfg(feature = "parity-dapps-wallet")] -fn wallet_page(pages: &mut Endpoints) { - extern crate parity_dapps_wallet; - insert::(pages, "wallet"); -} -#[cfg(not(feature = "parity-dapps-wallet"))] -fn wallet_page(_pages: &mut Endpoints) {} - fn insert(pages: &mut Endpoints, id: &str) { pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default()))); } diff --git a/dapps/src/handlers/content.rs b/dapps/src/handlers/content.rs index 4dc011475..e1eb1cd1e 100644 --- a/dapps/src/handlers/content.rs +++ b/dapps/src/handlers/content.rs @@ -23,53 +23,55 @@ use hyper::status::StatusCode; use util::version; +use handlers::add_security_headers; + #[derive(Clone)] pub struct ContentHandler { code: StatusCode, content: String, mimetype: String, write_pos: usize, + safe_to_embed_at_port: Option, } impl ContentHandler { pub fn ok(content: String, mimetype: String) -> Self { - ContentHandler { - code: StatusCode::Ok, - content: content, - mimetype: mimetype, - write_pos: 0 - } + Self::new(StatusCode::Ok, content, mimetype) } pub fn not_found(content: String, mimetype: String) -> Self { - ContentHandler { - code: StatusCode::NotFound, - content: content, - mimetype: mimetype, - write_pos: 0 - } + Self::new(StatusCode::NotFound, content, mimetype) } - pub fn html(code: StatusCode, content: String) -> Self { - Self::new(code, content, "text/html".into()) + pub fn html(code: StatusCode, content: String, embeddable_at: Option) -> Self { + Self::new_embeddable(code, content, "text/html".into(), embeddable_at) } pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>) -> Self { + Self::error_embeddable(code, title, message, details, None) + } + + pub fn error_embeddable(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_at: Option) -> Self { Self::html(code, format!( include_str!("../error_tpl.html"), title=title, message=message, details=details.unwrap_or_else(|| ""), version=version(), - )) + ), embeddable_at) } pub fn new(code: StatusCode, content: String, mimetype: String) -> Self { + Self::new_embeddable(code, content, mimetype, None) + } + + pub fn new_embeddable(code: StatusCode, content: String, mimetype: String, embeddable_at: Option) -> Self { ContentHandler { code: code, content: content, mimetype: mimetype, write_pos: 0, + safe_to_embed_at_port: embeddable_at, } } } @@ -86,6 +88,7 @@ impl server::Handler for ContentHandler { fn on_response(&mut self, res: &mut server::Response) -> Next { res.set_status(self.code); res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap())); + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port.clone()); Next::write() } diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index 54644fe8d..c4f98fc26 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -31,6 +31,24 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl}; use url::Url; use hyper::{server, header, net, uri}; +/// Adds security-related headers to the Response. +pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option) { + headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]); + headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]); + + // Embedding header: + if let Some(port) = embeddable_at { + headers.set_raw( + "X-Frame-Options", + vec![format!("ALLOW-FROM http://127.0.0.1:{}", port).into_bytes()] + ); + } else { + // TODO [ToDr] Should we be more strict here (DENY?)? + headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); + } +} + +/// Extracts URL part from the Request. pub fn extract_url(req: &server::Request) -> Option { match *req.uri() { uri::RequestUri::AbsoluteUri(ref url) => { diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index d863de18a..95fbbb191 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -108,6 +108,7 @@ pub struct ServerBuilder { handler: Arc, registrar: Arc, sync_status: Arc, + signer_port: Option, } impl Extendable for ServerBuilder { @@ -124,6 +125,7 @@ impl ServerBuilder { handler: Arc::new(IoHandler::new()), registrar: registrar, sync_status: Arc::new(|| false), + signer_port: None, } } @@ -132,6 +134,11 @@ impl ServerBuilder { self.sync_status = status; } + /// Change default signer port. + pub fn with_signer_port(&mut self, signer_port: Option) { + self.signer_port = signer_port; + } + /// Asynchronously start server with no authentication, /// returns result with `Server` handle on success or an error. pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option>) -> Result { @@ -141,6 +148,7 @@ impl ServerBuilder { NoAuth, self.handler.clone(), self.dapps_path.clone(), + self.signer_port.clone(), self.registrar.clone(), self.sync_status.clone(), ) @@ -155,6 +163,7 @@ impl ServerBuilder { HttpBasicAuth::single_user(username, password), self.handler.clone(), self.dapps_path.clone(), + self.signer_port.clone(), self.registrar.clone(), self.sync_status.clone(), ) @@ -189,13 +198,14 @@ impl Server { authorization: A, handler: Arc, dapps_path: String, + signer_port: Option, registrar: Arc, sync_status: Arc, ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status)); - let endpoints = Arc::new(apps::all_endpoints(dapps_path)); + let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port)); let special = Arc::new({ let mut special = HashMap::new(); special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone())); diff --git a/dapps/src/page/builtin.rs b/dapps/src/page/builtin.rs index 3c8d66686..c4f56556e 100644 --- a/dapps/src/page/builtin.rs +++ b/dapps/src/page/builtin.rs @@ -25,7 +25,7 @@ pub struct PageEndpoint { /// Prefix to strip from the path (when `None` deducted from `app_id`) pub prefix: Option, /// Safe to be loaded in frame by other origin. (use wisely!) - safe_to_embed: bool, + safe_to_embed_at_port: Option, info: EndpointInfo, } @@ -36,7 +36,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: None, - safe_to_embed: false, + safe_to_embed_at_port: None, info: EndpointInfo::from(info), } } @@ -49,7 +49,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: Some(prefix), - safe_to_embed: false, + safe_to_embed_at_port: None, info: EndpointInfo::from(info), } } @@ -57,12 +57,12 @@ impl PageEndpoint { /// Creates new `PageEndpoint` which can be safely used in iframe /// even from different origin. It might be dangerous (clickjacking). /// Use wisely! - pub fn new_safe_to_embed(app: T) -> Self { + pub fn new_safe_to_embed(app: T, port: Option) -> Self { let info = app.info(); PageEndpoint { app: Arc::new(app), prefix: None, - safe_to_embed: true, + safe_to_embed_at_port: port, info: EndpointInfo::from(info), } } @@ -79,8 +79,8 @@ impl Endpoint for PageEndpoint { app: BuiltinDapp::new(self.app.clone()), prefix: self.prefix.clone(), path: path, - file: Default::default(), - safe_to_embed: self.safe_to_embed, + file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()), + safe_to_embed_at_port: self.safe_to_embed_at_port.clone(), }) } } diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs index 70487443e..f975a98c8 100644 --- a/dapps/src/page/handler.rs +++ b/dapps/src/page/handler.rs @@ -22,7 +22,7 @@ use hyper::net::HttpStream; use hyper::status::StatusCode; use hyper::{Decoder, Encoder, Next}; use endpoint::EndpointPath; -use handlers::ContentHandler; +use handlers::{ContentHandler, add_security_headers}; /// Represents a file that can be sent to client. /// Implementation should keep track of bytes already sent internally. @@ -57,13 +57,14 @@ pub enum ServedFile { Error(ContentHandler), } -impl Default for ServedFile { - fn default() -> Self { - ServedFile::Error(ContentHandler::error( +impl ServedFile { + pub fn new(embeddable_at: Option) -> Self { + ServedFile::Error(ContentHandler::error_embeddable( StatusCode::NotFound, "404 Not Found", "Requested dapp resource was not found.", - None + None, + embeddable_at, )) } } @@ -81,7 +82,7 @@ pub struct PageHandler { /// Requested path. pub path: EndpointPath, /// Flag indicating if the file can be safely embeded (put in iframe). - pub safe_to_embed: bool, + pub safe_to_embed_at_port: Option, } impl PageHandler { @@ -115,7 +116,7 @@ impl server::Handler for PageHandler { self.app.file(&self.extract_path(url.path())) }, _ => None, - }.map_or_else(|| ServedFile::default(), |f| ServedFile::File(f)); + }.map_or_else(|| ServedFile::new(self.safe_to_embed_at_port.clone()), |f| ServedFile::File(f)); Next::write() } @@ -128,9 +129,8 @@ impl server::Handler for PageHandler { ServedFile::File(ref f) => { res.set_status(StatusCode::Ok); res.headers_mut().set(header::ContentType(f.content_type().parse().unwrap())); - if !self.safe_to_embed { - res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); - } + // Security headers: + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port); Next::write() }, ServedFile::Error(ref mut handler) => { @@ -212,8 +212,8 @@ fn should_extract_path_with_appid() { port: 8080, using_dapps_domains: true, }, - file: Default::default(), - safe_to_embed: true, + file: ServedFile::new(None), + safe_to_embed_at_port: None, }; // when diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs index e34cc6434..e702fe4d5 100644 --- a/dapps/src/page/local.rs +++ b/dapps/src/page/local.rs @@ -61,16 +61,16 @@ impl Endpoint for LocalPageEndpoint { app: LocalSingleFile { path: self.path.clone(), mime: mime.clone() }, prefix: None, path: path, - file: Default::default(), - safe_to_embed: false, + file: handler::ServedFile::new(None), + safe_to_embed_at_port: None, }) } else { Box::new(handler::PageHandler { app: LocalDapp { path: self.path.clone() }, prefix: None, path: path, - file: Default::default(), - safe_to_embed: false, + file: handler::ServedFile::new(None), + safe_to_embed_at_port: None, }) } } diff --git a/dapps/src/tests/api.rs b/dapps/src/tests/api.rs index fc255ec20..c0e3bb93b 100644 --- a/dapps/src/tests/api.rs +++ b/dapps/src/tests/api.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve, serve_with_registrar, request}; +use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers}; #[test] fn should_return_error() { @@ -36,6 +36,7 @@ fn should_return_error() { assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); assert_eq!(response.body, format!("58\n{}\n0\n\n", r#"{"code":"404","title":"Not Found","detail":"Resource you requested has not been found."}"#)); + assert_security_headers(&response.headers); } #[test] @@ -58,6 +59,7 @@ fn should_serve_apps() { assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); assert!(response.body.contains("Parity Home Screen"), response.body); + assert_security_headers(&response.headers); } #[test] @@ -80,6 +82,7 @@ fn should_handle_ping() { assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.headers.get(0).unwrap(), "Content-Type: application/json"); assert_eq!(response.body, "0\n\n".to_owned()); + assert_security_headers(&response.headers); } @@ -101,5 +104,6 @@ fn should_try_to_resolve_dapp() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert_eq!(registrar.calls.lock().len(), 2); + assert_security_headers(&response.headers); } diff --git a/dapps/src/tests/authorization.rs b/dapps/src/tests/authorization.rs index 9d5a46af4..808214a55 100644 --- a/dapps/src/tests/authorization.rs +++ b/dapps/src/tests/authorization.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve_with_auth, request}; +use tests::helpers::{serve_with_auth, request, assert_security_headers}; #[test] fn should_require_authorization() { @@ -76,4 +76,5 @@ fn should_allow_on_valid_auth() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + assert_security_headers(&response.headers); } diff --git a/dapps/src/tests/fetch.rs b/dapps/src/tests/fetch.rs index 6fd65c00f..9f02e3385 100644 --- a/dapps/src/tests/fetch.rs +++ b/dapps/src/tests/fetch.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve_with_registrar, request}; +use tests::helpers::{serve_with_registrar, request, assert_security_headers}; #[test] fn should_resolve_dapp() { @@ -34,5 +34,6 @@ fn should_resolve_dapp() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert_eq!(registrar.calls.lock().len(), 2); + assert_security_headers(&response.headers); } diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index efbd24a8d..c1837d5a2 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -92,3 +92,7 @@ pub fn serve() -> Server { pub fn request(server: Server, request: &str) -> http_client::Response { http_client::request(server.addr(), request) } + +pub fn assert_security_headers(headers: &[String]) { + http_client::assert_security_headers_present(headers) +} diff --git a/dapps/src/tests/redirection.rs b/dapps/src/tests/redirection.rs index 1a360cd08..92fc6ce80 100644 --- a/dapps/src/tests/redirection.rs +++ b/dapps/src/tests/redirection.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use tests::helpers::{serve, request}; +use tests::helpers::{serve, request, assert_security_headers}; #[test] fn should_redirect_to_home() { @@ -74,6 +74,7 @@ fn should_display_404_on_invalid_dapp() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert!(response.body.contains("href=\"/home/")); + assert_security_headers(&response.headers); } #[test] @@ -94,6 +95,7 @@ fn should_display_404_on_invalid_dapp_with_domain() { // then assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned()); assert!(response.body.contains("href=\"http://home.parity/")); + assert_security_headers(&response.headers); } #[test] @@ -160,6 +162,7 @@ fn should_serve_proxy_pac() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.body, "86\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned()); + assert_security_headers(&response.headers); } #[test] @@ -181,5 +184,6 @@ fn should_serve_utils() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); assert_eq!(response.body.contains("function(){"), true); + assert_security_headers(&response.headers); } diff --git a/devtools/src/http_client.rs b/devtools/src/http_client.rs index f194c4004..3f0bd463e 100644 --- a/devtools/src/http_client.rs +++ b/devtools/src/http_client.rs @@ -64,3 +64,18 @@ pub fn request(address: &SocketAddr, request: &str) -> Response { } } +/// Check if all required security headers are present +pub fn assert_security_headers_present(headers: &[String]) { + assert!( + headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(), + "X-Frame-Options missing: {:?}", headers + ); + assert!( + headers.iter().find(|header| header.as_str() == "X-XSS-Protection: 1; mode=block").is_some(), + "X-XSS-Protection missing: {:?}", headers + ); + assert!( + headers.iter().find(|header| header.as_str() == "X-Content-Type-Options: nosniff").is_some(), + "X-Content-Type-Options missing: {:?}", headers + ); +} diff --git a/parity/dapps.rs b/parity/dapps.rs index fae395b5a..ede57ae5d 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -58,6 +58,7 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result Result>, _auth: Option<(String, String)>, + _signer_port: Option, ) -> Result { Err("Your Parity version has been compiled without WebApps support.".into()) } @@ -115,7 +117,8 @@ mod server { dapps_path: String, url: &SocketAddr, allowed_hosts: Option>, - auth: Option<(String, String)> + auth: Option<(String, String)>, + signer_port: Option, ) -> Result { use ethcore_dapps as dapps; @@ -125,6 +128,8 @@ mod server { ); let sync = deps.sync.clone(); server.with_sync_status(Arc::new(move || sync.status().is_major_syncing())); + server.with_signer_port(signer_port); + let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext); let start_result = match auth { None => { diff --git a/signer/src/tests/mod.rs b/signer/src/tests/mod.rs index 61b2ff1d3..e6933382f 100644 --- a/signer/src/tests/mod.rs +++ b/signer/src/tests/mod.rs @@ -81,6 +81,7 @@ fn should_reject_invalid_host() { // then assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); assert!(response.body.contains("URL Blocked")); + http_client::assert_security_headers_present(&response.headers); } #[test] @@ -101,6 +102,7 @@ fn should_serve_styles_even_on_disallowed_domain() { // then assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned()); + http_client::assert_security_headers_present(&response.headers); } #[test] @@ -124,6 +126,7 @@ fn should_block_if_authorization_is_incorrect() { // then assert_eq!(response.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); + http_client::assert_security_headers_present(&response.headers); } #[test] @@ -202,4 +205,5 @@ fn should_allow_initial_connection_but_only_once() { // then assert_eq!(response1.status, "HTTP/1.1 101 Switching Protocols".to_owned()); assert_eq!(response2.status, "HTTP/1.1 403 FORBIDDEN".to_owned()); + http_client::assert_security_headers_present(&response2.headers); } diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index 45518850e..255f2cd8f 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -112,6 +112,8 @@ fn add_headers(mut response: ws::Response, mime: &str) -> ws::Response { { let mut headers = response.headers_mut(); headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec())); + headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec())); + headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec())); headers.push(("Server".into(), b"Parity/SignerUI".to_vec())); headers.push(("Content-Length".into(), content_len.as_bytes().to_vec())); headers.push(("Content-Type".into(), mime.as_bytes().to_vec()));