>();
+ // Add localhost domain as valid too if listening on loopback interface.
+ endpoints.push(bind_address.replace("127.0.0.1", "localhost").into());
+ endpoints.push(bind_address.into());
+
+ is_host_header_valid(request, &endpoints)
+}
+
+pub fn host_invalid_response() -> Box + Send> {
+ Box::new(ContentHandler::forbidden(
+ r#"
+ Request with disallowed Host
header has been blocked.
+ Check the URL in your browser address bar.
+ "#.into(),
+ "text/html".into()
+ ))
+}
diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs
index 8fbb37cf3..bdd5fd291 100644
--- a/dapps/src/router/mod.rs
+++ b/dapps/src/router/mod.rs
@@ -18,6 +18,7 @@
//! Processes request handling authorization and dispatching it to proper application.
pub mod auth;
+mod host_validation;
use DAPPS_DOMAIN;
use std::sync::Arc;
@@ -44,40 +45,46 @@ pub struct Router {
endpoints: Arc,
special: Arc>>,
authorization: Arc,
+ bind_address: String,
handler: Box + Send>,
}
impl server::Handler for Router {
fn on_request(&mut self, req: server::Request) -> Next {
+ // Validate Host header
+ if !host_validation::is_valid(&req, &self.bind_address, self.endpoints.keys().cloned().collect()) {
+ self.handler = host_validation::host_invalid_response();
+ return self.handler.on_request(req);
+ }
+
// Check authorization
let auth = self.authorization.is_authorized(&req);
+ if let Authorized::No(handler) = auth {
+ self.handler = handler;
+ return self.handler.on_request(req);
+ }
// Choose proper handler depending on path / domain
- self.handler = match auth {
- Authorized::No(handler) => handler,
- Authorized::Yes => {
- let url = extract_url(&req);
- let endpoint = extract_endpoint(&url);
+ let url = extract_url(&req);
+ let endpoint = extract_endpoint(&url);
- match endpoint {
- // First check special endpoints
- (ref path, ref endpoint) if self.special.contains_key(endpoint) => {
- self.special.get(endpoint).unwrap().to_handler(path.clone().unwrap_or_default())
- },
- // Then delegate to dapp
- (Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
- self.endpoints.get(&path.app_id).unwrap().to_handler(path.clone())
- },
- // Redirection to main page
- _ if *req.method() == hyper::method::Method::Get => {
- Redirection::new(self.main_page)
- },
- // RPC by default
- _ => {
- self.special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default())
- }
- }
+ self.handler = match endpoint {
+ // First check special endpoints
+ (ref path, ref endpoint) if self.special.contains_key(endpoint) => {
+ self.special.get(endpoint).unwrap().to_handler(path.clone().unwrap_or_default())
+ },
+ // Then delegate to dapp
+ (Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
+ self.endpoints.get(&path.app_id).unwrap().to_handler(path.clone())
+ },
+ // Redirection to main page
+ _ if *req.method() == hyper::method::Method::Get => {
+ Redirection::new(self.main_page)
+ },
+ // RPC by default
+ _ => {
+ self.special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default())
}
};
@@ -106,7 +113,9 @@ impl Router {
main_page: &'static str,
endpoints: Arc,
special: Arc>>,
- authorization: Arc) -> Self {
+ authorization: Arc,
+ bind_address: String,
+ ) -> Self {
let handler = special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default());
Router {
@@ -114,6 +123,7 @@ impl Router {
endpoints: endpoints,
special: special,
authorization: authorization,
+ bind_address: bind_address,
handler: handler,
}
}
diff --git a/dapps/src/rpc.rs b/dapps/src/rpc.rs
index e282c0440..04470bcc1 100644
--- a/dapps/src/rpc.rs
+++ b/dapps/src/rpc.rs
@@ -23,19 +23,22 @@ pub fn rpc(handler: Arc, panic_handler: Arc
Box::new(RpcEndpoint {
handler: handler,
panic_handler: panic_handler,
- cors_domain: vec![AccessControlAllowOrigin::Null],
+ cors_domain: Some(vec![AccessControlAllowOrigin::Null]),
+ // NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router.
+ allowed_hosts: None,
})
}
struct RpcEndpoint {
handler: Arc,
panic_handler: Arc () + Send>>>>,
- cors_domain: Vec,
+ cors_domain: Option>,
+ allowed_hosts: Option>,
}
impl Endpoint for RpcEndpoint {
fn to_handler(&self, _path: EndpointPath) -> Box {
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
- Box::new(ServerHandler::new(self.handler.clone(), self.cors_domain.clone(), panic_handler))
+ Box::new(ServerHandler::new(self.handler.clone(), self.cors_domain.clone(), self.allowed_hosts.clone(), panic_handler))
}
}
diff --git a/parity/cli.rs b/parity/cli.rs
index 928237d72..60aca8310 100644
--- a/parity/cli.rs
+++ b/parity/cli.rs
@@ -107,6 +107,11 @@ API and Console Options:
name. Possible name are web3, eth, net, personal,
ethcore, ethcore_set, traces.
[default: web3,eth,net,ethcore,personal,traces].
+ --jsonrpc-hosts HOSTS List of allowed Host header values. This option will
+ validate the Host header sent by the browser, it
+ is additional security against some attack
+ vectors. Special options: "all", "none",
+ [default: none].
--no-ipc Disable JSON-RPC over IPC service.
--ipc-path PATH Specify custom path for JSON-RPC over IPC service
@@ -118,8 +123,8 @@ API and Console Options:
--dapps-port PORT Specify the port portion of the Dapps server
[default: 8080].
--dapps-interface IP Specify the hostname portion of the Dapps
- server, IP should be an interface's IP address, or
- all (all interfaces) or local [default: local].
+ server, IP should be an interface's IP address,
+ or local [default: local].
--dapps-user USERNAME Specify username for Dapps server. It will be
used in HTTP Basic Authentication Scheme.
If --dapps-pass is not specified you will be
@@ -311,6 +316,7 @@ pub struct Args {
pub flag_jsonrpc_interface: String,
pub flag_jsonrpc_port: u16,
pub flag_jsonrpc_cors: Option,
+ pub flag_jsonrpc_hosts: String,
pub flag_jsonrpc_apis: String,
pub flag_no_ipc: bool,
pub flag_ipc_path: String,
diff --git a/parity/configuration.rs b/parity/configuration.rs
index 266f400a7..6094ba01b 100644
--- a/parity/configuration.rs
+++ b/parity/configuration.rs
@@ -424,9 +424,19 @@ impl Configuration {
self.args.flag_rpcapi.clone().unwrap_or(self.args.flag_jsonrpc_apis.clone())
}
- pub fn rpc_cors(&self) -> Vec {
+ pub fn rpc_cors(&self) -> Option> {
let cors = self.args.flag_jsonrpc_cors.clone().or(self.args.flag_rpccorsdomain.clone());
- cors.map_or_else(Vec::new, |c| c.split(',').map(|s| s.to_owned()).collect())
+ cors.map(|c| c.split(',').map(|s| s.to_owned()).collect())
+ }
+
+ pub fn rpc_hosts(&self) -> Option> {
+ match self.args.flag_jsonrpc_hosts.as_ref() {
+ "none" => return Some(Vec::new()),
+ "all" => return None,
+ _ => {}
+ }
+ let hosts = self.args.flag_jsonrpc_hosts.split(',').map(|h| h.into()).collect();
+ Some(hosts)
}
fn geth_ipc_path(&self) -> String {
@@ -541,7 +551,6 @@ impl Configuration {
pub fn dapps_interface(&self) -> String {
match self.args.flag_dapps_interface.as_str() {
- "all" => "0.0.0.0",
"local" => "127.0.0.1",
x => x,
}.into()
@@ -597,7 +606,7 @@ mod tests {
assert_eq!(net.rpc_enabled, true);
assert_eq!(net.rpc_interface, "all".to_owned());
assert_eq!(net.rpc_port, 8000);
- assert_eq!(conf.rpc_cors(), vec!["*".to_owned()]);
+ assert_eq!(conf.rpc_cors(), Some(vec!["*".to_owned()]));
assert_eq!(conf.rpc_apis(), "web3,eth".to_owned());
}
@@ -619,5 +628,22 @@ mod tests {
assert(conf1);
assert(conf2);
}
+
+ #[test]
+ fn should_parse_rpc_hosts() {
+ // given
+
+ // when
+ let conf0 = parse(&["parity"]);
+ let conf1 = parse(&["parity", "--jsonrpc-hosts", "none"]);
+ let conf2 = parse(&["parity", "--jsonrpc-hosts", "all"]);
+ let conf3 = parse(&["parity", "--jsonrpc-hosts", "ethcore.io,something.io"]);
+
+ // then
+ assert_eq!(conf0.rpc_hosts(), Some(Vec::new()));
+ assert_eq!(conf1.rpc_hosts(), Some(Vec::new()));
+ assert_eq!(conf2.rpc_hosts(), None);
+ assert_eq!(conf3.rpc_hosts(), Some(vec!["ethcore.io".into(), "something.io".into()]));
+ }
}
diff --git a/parity/main.rs b/parity/main.rs
index a8fd63d2a..fe5107d66 100644
--- a/parity/main.rs
+++ b/parity/main.rs
@@ -280,6 +280,7 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig,
port: network_settings.rpc_port,
apis: conf.rpc_apis(),
cors: conf.rpc_cors(),
+ hosts: conf.rpc_hosts(),
}, &dependencies);
// setup ipc rpc
diff --git a/parity/rpc.rs b/parity/rpc.rs
index 7317aa2e6..2b0599962 100644
--- a/parity/rpc.rs
+++ b/parity/rpc.rs
@@ -32,7 +32,8 @@ pub struct HttpConfiguration {
pub interface: String,
pub port: u16,
pub apis: String,
- pub cors: Vec,
+ pub cors: Option>,
+ pub hosts: Option>,
}
pub struct IpcConfiguration {
@@ -66,7 +67,7 @@ pub fn new_http(conf: HttpConfiguration, deps: &Dependencies) -> Option, deps: &Dependencies) -> Server {
@@ -78,21 +79,17 @@ fn setup_rpc_server(apis: Vec<&str>, deps: &Dependencies) -> Server {
pub fn setup_http_rpc_server(
dependencies: &Dependencies,
url: &SocketAddr,
- cors_domains: Vec,
+ cors_domains: Option>,
+ allowed_hosts: Option>,
apis: Vec<&str>,
) -> RpcServer {
let server = setup_rpc_server(apis, dependencies);
- let start_result = server.start_http(url, cors_domains);
let ph = dependencies.panic_handler.clone();
+ let start_result = server.start_http(url, cors_domains, allowed_hosts, ph);
match start_result {
Err(RpcServerError::IoError(err)) => die_with_io_error("RPC", err),
Err(e) => die!("RPC: {:?}", e),
- Ok(server) => {
- server.set_panic_handler(move || {
- ph.notify_all("Panic in RPC thread.".to_owned());
- });
- server
- },
+ Ok(server) => server,
}
}
diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs
index 73a769b13..5899d0027 100644
--- a/rpc/src/lib.rs
+++ b/rpc/src/lib.rs
@@ -41,9 +41,10 @@ extern crate ethcore_devtools as devtools;
use std::sync::Arc;
use std::net::SocketAddr;
+use util::panics::PanicHandler;
use self::jsonrpc_core::{IoHandler, IoDelegate};
-pub use jsonrpc_http_server::{Server, RpcServerError};
+pub use jsonrpc_http_server::{ServerBuilder, Server, RpcServerError};
pub mod v1;
pub use v1::{SigningQueue, ConfirmationsQueue};
@@ -74,15 +75,31 @@ impl RpcServer {
}
/// Start http server asynchronously and returns result with `Server` handle on success or an error.
- pub fn start_http(&self, addr: &SocketAddr, cors_domains: Vec) -> Result {
- let cors_domains = cors_domains.into_iter()
- .map(|v| match v.as_str() {
- "*" => jsonrpc_http_server::AccessControlAllowOrigin::Any,
- "null" => jsonrpc_http_server::AccessControlAllowOrigin::Null,
- v => jsonrpc_http_server::AccessControlAllowOrigin::Value(v.into()),
+ pub fn start_http(
+ &self,
+ addr: &SocketAddr,
+ cors_domains: Option>,
+ allowed_hosts: Option>,
+ panic_handler: Arc,
+ ) -> Result {
+
+ let cors_domains = cors_domains.map(|domains| {
+ domains.into_iter()
+ .map(|v| match v.as_str() {
+ "*" => jsonrpc_http_server::AccessControlAllowOrigin::Any,
+ "null" => jsonrpc_http_server::AccessControlAllowOrigin::Null,
+ v => jsonrpc_http_server::AccessControlAllowOrigin::Value(v.into()),
+ })
+ .collect()
+ });
+
+ ServerBuilder::new(self.handler.clone())
+ .cors(cors_domains.into())
+ .allowed_hosts(allowed_hosts.into())
+ .panic_handler(move || {
+ panic_handler.notify_all("Panic in RPC thread.".to_owned());
})
- .collect();
- Server::start(addr, self.handler.clone(), cors_domains)
+ .start_http(addr)
}
/// Start ipc server asynchronously and returns result with `Server` handle on success or an error.