Allow CORS requests in Secret Store API (#10584)

* allow CORS requests for Secret Store API (#10582)

* secretstore CORS: fix error with unit tests

* secretstore CORS: removed debug log

* secretstore CORS: add missing response's header

* secretstore CORS: switched to jsonrpc-server-utils for CORS validation
This commit is contained in:
Antoine Detante 2019-04-20 07:31:37 +02:00 committed by Wei Tang
parent c5fa7aab43
commit 4cc274e75f
9 changed files with 143 additions and 58 deletions

33
Cargo.lock generated
View File

@ -1074,6 +1074,7 @@ dependencies = [
"ethkey 0.3.0", "ethkey 0.3.0",
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-server-utils 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"keccak-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "keccak-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"kvdb 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "kvdb 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"kvdb-rocksdb 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "kvdb-rocksdb 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1853,6 +1854,18 @@ dependencies = [
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "jsonrpc-core"
version = "11.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "jsonrpc-derive" name = "jsonrpc-derive"
version = "10.0.2" version = "10.0.2"
@ -1916,6 +1929,22 @@ dependencies = [
"unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "jsonrpc-server-utils"
version = "11.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytes 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "jsonrpc-tcp-server" name = "jsonrpc-tcp-server"
version = "10.0.1" version = "10.0.1"
@ -2349,7 +2378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -4593,11 +4622,13 @@ dependencies = [
"checksum jni 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "294eca097d1dc0bf59de5ab9f7eafa5f77129e9f6464c957ed3ddeb705fb4292" "checksum jni 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "294eca097d1dc0bf59de5ab9f7eafa5f77129e9f6464c957ed3ddeb705fb4292"
"checksum jni-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" "checksum jni-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
"checksum jsonrpc-core 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a5152c3fda235dfd68341b3edf4121bc4428642c93acbd6de88c26bf95fc5d7" "checksum jsonrpc-core 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a5152c3fda235dfd68341b3edf4121bc4428642c93acbd6de88c26bf95fc5d7"
"checksum jsonrpc-core 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97b83fdc5e0218128d0d270f2f2e7a5ea716f3240c8518a58bc89e6716ba8581"
"checksum jsonrpc-derive 10.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c14be84e86c75935be83a34c6765bf31f97ed6c9163bb0b83007190e9703940a" "checksum jsonrpc-derive 10.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c14be84e86c75935be83a34c6765bf31f97ed6c9163bb0b83007190e9703940a"
"checksum jsonrpc-http-server 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99e1ce36c7cc9dcab398024d76849ab2cb917ee812653bce6f74fc9eb7c82d16" "checksum jsonrpc-http-server 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99e1ce36c7cc9dcab398024d76849ab2cb917ee812653bce6f74fc9eb7c82d16"
"checksum jsonrpc-ipc-server 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fac6b8682243740a32bfb288880c71cc06eca29616cdf551e4136a190b11b96d" "checksum jsonrpc-ipc-server 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fac6b8682243740a32bfb288880c71cc06eca29616cdf551e4136a190b11b96d"
"checksum jsonrpc-pubsub 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "56608ed54b1b2a69f4357cb8bdfbcbd99fe1179383c03a09bb428931bd35f592" "checksum jsonrpc-pubsub 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "56608ed54b1b2a69f4357cb8bdfbcbd99fe1179383c03a09bb428931bd35f592"
"checksum jsonrpc-server-utils 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5521613b31ea22d36d9f95ad642058dccec846a94ed8690957652d479f620707" "checksum jsonrpc-server-utils 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5521613b31ea22d36d9f95ad642058dccec846a94ed8690957652d479f620707"
"checksum jsonrpc-server-utils 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3372b3248a53abcca8f61924f188052bb0c4cd80b482b2b4eaf9f8667efb9f4"
"checksum jsonrpc-tcp-server 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c873dac37a601fb88d40ba49eeac3f1aa60953c06b2e99ddbf0569b6f8028478" "checksum jsonrpc-tcp-server 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c873dac37a601fb88d40ba49eeac3f1aa60953c06b2e99ddbf0569b6f8028478"
"checksum jsonrpc-ws-server 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20b8333a5a6e6ccbcf5c90f90919de557cba4929efa164e9bd0e8e497eb20e46" "checksum jsonrpc-ws-server 10.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20b8333a5a6e6ccbcf5c90f90919de557cba4929efa164e9bd0e8e497eb20e46"
"checksum keccak-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "253bbe643c32c816bf58fa5a88248fafedeebb139705ad17a62add3517854a86" "checksum keccak-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "253bbe643c32c816bf58fa5a88248fafedeebb139705ad17a62add3517854a86"

View File

@ -623,6 +623,10 @@ usage! {
"--no-secretstore-auto-migrate", "--no-secretstore-auto-migrate",
"Do not run servers set change session automatically when servers set changes. This option has no effect when servers set is read from configuration file.", "Do not run servers set change session automatically when servers set changes. This option has no effect when servers set is read from configuration file.",
ARG arg_secretstore_http_cors: (String) = "none", or |c: &Config| c.secretstore.as_ref()?.cors.as_ref().map(|vec| vec.join(",")),
"--secretstore-http-cors=[URL]",
"Specify CORS header for Secret Store HTTP API responses. Special options: \"all\", \"none\".",
ARG arg_secretstore_acl_contract: (Option<String>) = Some("registry".into()), or |c: &Config| c.secretstore.as_ref()?.acl_contract.clone(), ARG arg_secretstore_acl_contract: (Option<String>) = Some("registry".into()), or |c: &Config| c.secretstore.as_ref()?.acl_contract.clone(),
"--secretstore-acl-contract=[SOURCE]", "--secretstore-acl-contract=[SOURCE]",
"Secret Store permissioning contract address source: none, registry (contract address is read from 'secretstore_acl_checker' entry in registry) or address.", "Secret Store permissioning contract address source: none, registry (contract address is read from 'secretstore_acl_checker' entry in registry) or address.",
@ -1328,6 +1332,7 @@ struct SecretStore {
http_interface: Option<String>, http_interface: Option<String>,
http_port: Option<u16>, http_port: Option<u16>,
path: Option<String>, path: Option<String>,
cors: Option<Vec<String>>
} }
#[derive(Default, Debug, PartialEq, Deserialize)] #[derive(Default, Debug, PartialEq, Deserialize)]
@ -1854,6 +1859,7 @@ mod tests {
arg_secretstore_http_interface: "local".into(), arg_secretstore_http_interface: "local".into(),
arg_secretstore_http_port: 8082u16, arg_secretstore_http_port: 8082u16,
arg_secretstore_path: "$HOME/.parity/secretstore".into(), arg_secretstore_path: "$HOME/.parity/secretstore".into(),
arg_secretstore_http_cors: "null".into(),
// IPFS // IPFS
flag_ipfs_api: false, flag_ipfs_api: false,
@ -2132,6 +2138,7 @@ mod tests {
http_interface: None, http_interface: None,
http_port: Some(8082), http_port: Some(8082),
path: None, path: None,
cors: None,
}), }),
private_tx: None, private_tx: None,
ipfs: Some(Ipfs { ipfs: Some(Ipfs {

View File

@ -105,6 +105,7 @@ http_port = 8082
interface = "local" interface = "local"
port = 8083 port = 8083
path = "$HOME/.parity/secretstore" path = "$HOME/.parity/secretstore"
cors = ["null"]
[ipfs] [ipfs]
enable = false enable = false

View File

@ -638,6 +638,7 @@ impl Configuration {
http_port: self.args.arg_ports_shift + self.args.arg_secretstore_http_port, http_port: self.args.arg_ports_shift + self.args.arg_secretstore_http_port,
data_path: self.directories().secretstore, data_path: self.directories().secretstore,
admin_public: self.secretstore_admin_public()?, admin_public: self.secretstore_admin_public()?,
cors: self.secretstore_cors()
}) })
} }
@ -1058,6 +1059,10 @@ impl Configuration {
self.interface(&self.args.arg_secretstore_http_interface) self.interface(&self.args.arg_secretstore_http_interface)
} }
fn secretstore_cors(&self) -> Option<Vec<String>> {
Self::cors(self.args.arg_secretstore_http_cors.as_ref())
}
fn secretstore_self_secret(&self) -> Result<Option<NodeSecretKey>, String> { fn secretstore_self_secret(&self) -> Result<Option<NodeSecretKey>, String> {
match self.args.arg_secretstore_secret { match self.args.arg_secretstore_secret {
Some(ref s) if s.len() == 64 => Ok(Some(NodeSecretKey::Plain(s.parse() Some(ref s) if s.len() == 64 => Ok(Some(NodeSecretKey::Plain(s.parse()
@ -1969,4 +1974,19 @@ mod tests {
_ => panic!("Should be Cmd::Run"), _ => panic!("Should be Cmd::Run"),
} }
} }
#[test]
fn should_parse_secretstore_cors() {
// given
// when
let conf0 = parse(&["parity"]);
let conf1 = parse(&["parity", "--secretstore-http-cors", "*"]);
let conf2 = parse(&["parity", "--secretstore-http-cors", "http://parity.io,http://something.io"]);
// then
assert_eq!(conf0.secretstore_cors(), Some(vec![]));
assert_eq!(conf1.secretstore_cors(), None);
assert_eq!(conf2.secretstore_cors(), Some(vec!["http://parity.io".into(),"http://something.io".into()]));
}
} }

View File

@ -84,6 +84,8 @@ pub struct Configuration {
pub data_path: String, pub data_path: String,
/// Administrator public key. /// Administrator public key.
pub admin_public: Option<Public>, pub admin_public: Option<Public>,
// Allowed CORS domains
pub cors: Option<Vec<String>>,
} }
/// Secret store dependencies /// Secret store dependencies
@ -195,6 +197,7 @@ mod server {
admin_public: conf.admin_public, admin_public: conf.admin_public,
auto_migrate_enabled: conf.auto_migrate_enabled, auto_migrate_enabled: conf.auto_migrate_enabled,
}, },
cors: conf.cors
}; };
cconf.cluster_config.nodes.insert(self_secret.public().clone(), cconf.cluster_config.listener_address.clone()); cconf.cluster_config.nodes.insert(self_secret.public().clone(), cconf.cluster_config.listener_address.clone());
@ -234,6 +237,7 @@ impl Default for Configuration {
http_interface: "127.0.0.1".to_owned(), http_interface: "127.0.0.1".to_owned(),
http_port: 8082, http_port: 8082,
data_path: replace_home(&data_dir, "$BASE/secretstore"), data_path: replace_home(&data_dir, "$BASE/secretstore"),
cors: Some(vec![]),
} }
} }
} }

View File

@ -36,6 +36,7 @@ tokio = "~0.1.11"
tokio-io = "0.1" tokio-io = "0.1"
tokio-service = "0.1" tokio-service = "0.1"
url = "1.0" url = "1.0"
jsonrpc-server-utils = "11.0"
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.5"

View File

@ -37,6 +37,7 @@ extern crate tokio;
extern crate tokio_io; extern crate tokio_io;
extern crate tokio_service; extern crate tokio_service;
extern crate url; extern crate url;
extern crate jsonrpc_server_utils;
#[macro_use] #[macro_use]
extern crate ethabi_derive; extern crate ethabi_derive;
@ -107,7 +108,7 @@ pub fn start(client: Arc<Client>, sync: Arc<SyncProvider>, miner: Arc<Miner>, se
// prepare HTTP listener // prepare HTTP listener
let http_listener = match config.listener_address { let http_listener = match config.listener_address {
Some(listener_address) => Some(listener::http_listener::KeyServerHttpListener::start(listener_address, Arc::downgrade(&key_server), executor)?), Some(listener_address) => Some(listener::http_listener::KeyServerHttpListener::start(listener_address, config.cors, Arc::downgrade(&key_server), executor)?),
None => None, None => None,
}; };

View File

@ -34,6 +34,7 @@ use traits::KeyServer;
use serialization::{SerializableEncryptedDocumentKeyShadow, SerializableBytes, SerializablePublic}; use serialization::{SerializableEncryptedDocumentKeyShadow, SerializableBytes, SerializablePublic};
use types::{Error, Public, MessageHash, NodeAddress, RequestSignature, ServerKeyId, use types::{Error, Public, MessageHash, NodeAddress, RequestSignature, ServerKeyId,
EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId}; EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId};
use jsonrpc_server_utils::cors::{self, AllowCors, AccessControlAllowOrigin};
/// Key server http-requests listener. Available requests: /// Key server http-requests listener. Available requests:
/// To generate server key: POST /shadow/{server_key_id}/{signature}/{threshold} /// To generate server key: POST /shadow/{server_key_id}/{signature}/{threshold}
@ -45,6 +46,8 @@ use types::{Error, Public, MessageHash, NodeAddress, RequestSignature, ServerKey
/// To generate ECDSA signature with server key: GET /ecdsa/{server_key_id}/{signature}/{message_hash} /// To generate ECDSA signature with server key: GET /ecdsa/{server_key_id}/{signature}/{message_hash}
/// To change servers set: POST /admin/servers_set_change/{old_signature}/{new_signature} + BODY: json array of hex-encoded nodes ids /// To change servers set: POST /admin/servers_set_change/{old_signature}/{new_signature} + BODY: json array of hex-encoded nodes ids
type CorsDomains = Option<Vec<AccessControlAllowOrigin>>;
pub struct KeyServerHttpListener { pub struct KeyServerHttpListener {
_executor: Executor, _executor: Executor,
_handler: Arc<KeyServerSharedHttpHandler>, _handler: Arc<KeyServerSharedHttpHandler>,
@ -77,6 +80,7 @@ enum Request {
#[derive(Clone)] #[derive(Clone)]
struct KeyServerHttpHandler { struct KeyServerHttpHandler {
handler: Arc<KeyServerSharedHttpHandler>, handler: Arc<KeyServerSharedHttpHandler>,
cors: CorsDomains,
} }
/// Shared http handler /// Shared http handler
@ -84,13 +88,14 @@ struct KeyServerSharedHttpHandler {
key_server: Weak<KeyServer>, key_server: Weak<KeyServer>,
} }
impl KeyServerHttpListener { impl KeyServerHttpListener {
/// Start KeyServer http listener /// Start KeyServer http listener
pub fn start(listener_address: NodeAddress, key_server: Weak<KeyServer>, executor: Executor) -> Result<Self, Error> { pub fn start(listener_address: NodeAddress, cors_domains: Option<Vec<String>>, key_server: Weak<KeyServer>, executor: Executor) -> Result<Self, Error> {
let shared_handler = Arc::new(KeyServerSharedHttpHandler { let shared_handler = Arc::new(KeyServerSharedHttpHandler {
key_server: key_server, key_server: key_server,
}); });
let cors: CorsDomains = cors_domains.map(|domains| domains.into_iter().map(AccessControlAllowOrigin::from).collect());
let listener_address = format!("{}:{}", listener_address.address, listener_address.port).parse()?; let listener_address = format!("{}:{}", listener_address.address, listener_address.port).parse()?;
let listener = TcpListener::bind(&listener_address)?; let listener = TcpListener::bind(&listener_address)?;
@ -101,7 +106,7 @@ impl KeyServerHttpListener {
.for_each(move |socket| { .for_each(move |socket| {
let http = Http::new(); let http = Http::new();
let serve = http.serve_connection(socket, let serve = http.serve_connection(socket,
KeyServerHttpHandler { handler: shared_handler2.clone() } KeyServerHttpHandler { handler: shared_handler2.clone(), cors: cors.clone() }
).map(|_| ()).map_err(|e| { ).map(|_| ()).map_err(|e| {
warn!("Key server handler error: {:?}", e); warn!("Key server handler error: {:?}", e);
}); });
@ -121,10 +126,10 @@ impl KeyServerHttpListener {
} }
impl KeyServerHttpHandler { impl KeyServerHttpHandler {
fn process(self, req_method: HttpMethod, req_uri: Uri, path: &str, req_body: &[u8]) -> HttpResponse<Body> { fn process(self, req_method: HttpMethod, req_uri: Uri, path: &str, req_body: &[u8], cors: AllowCors<AccessControlAllowOrigin>) -> HttpResponse<Body> {
match parse_request(&req_method, &path, &req_body) { match parse_request(&req_method, &path, &req_body) {
Request::GenerateServerKey(document, signature, threshold) => { Request::GenerateServerKey(document, signature, threshold) => {
return_server_public_key(&req_uri, self.handler.key_server.upgrade() return_server_public_key(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.generate_key(&document, &signature.into(), threshold)) .map(|key_server| key_server.generate_key(&document, &signature.into(), threshold))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| { .map_err(|err| {
@ -133,7 +138,7 @@ impl KeyServerHttpHandler {
})) }))
}, },
Request::StoreDocumentKey(document, signature, common_point, encrypted_document_key) => { Request::StoreDocumentKey(document, signature, common_point, encrypted_document_key) => {
return_empty(&req_uri, self.handler.key_server.upgrade() return_empty(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.store_document_key(&document, &signature.into(), common_point, encrypted_document_key)) .map(|key_server| key_server.store_document_key(&document, &signature.into(), common_point, encrypted_document_key))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| { .map_err(|err| {
@ -142,7 +147,7 @@ impl KeyServerHttpHandler {
})) }))
}, },
Request::GenerateDocumentKey(document, signature, threshold) => { Request::GenerateDocumentKey(document, signature, threshold) => {
return_document_key(&req_uri, self.handler.key_server.upgrade() return_document_key(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.generate_document_key(&document, &signature.into(), threshold)) .map(|key_server| key_server.generate_document_key(&document, &signature.into(), threshold))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| { .map_err(|err| {
@ -151,7 +156,7 @@ impl KeyServerHttpHandler {
})) }))
}, },
Request::GetDocumentKey(document, signature) => { Request::GetDocumentKey(document, signature) => {
return_document_key(&req_uri, self.handler.key_server.upgrade() return_document_key(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.restore_document_key(&document, &signature.into())) .map(|key_server| key_server.restore_document_key(&document, &signature.into()))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| { .map_err(|err| {
@ -160,7 +165,7 @@ impl KeyServerHttpHandler {
})) }))
}, },
Request::GetDocumentKeyShadow(document, signature) => { Request::GetDocumentKeyShadow(document, signature) => {
return_document_key_shadow(&req_uri, self.handler.key_server.upgrade() return_document_key_shadow(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.restore_document_key_shadow(&document, &signature.into())) .map(|key_server| key_server.restore_document_key_shadow(&document, &signature.into()))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| { .map_err(|err| {
@ -169,7 +174,7 @@ impl KeyServerHttpHandler {
})) }))
}, },
Request::SchnorrSignMessage(document, signature, message_hash) => { Request::SchnorrSignMessage(document, signature, message_hash) => {
return_message_signature(&req_uri, self.handler.key_server.upgrade() return_message_signature(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.sign_message_schnorr(&document, &signature.into(), message_hash)) .map(|key_server| key_server.sign_message_schnorr(&document, &signature.into(), message_hash))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| { .map_err(|err| {
@ -178,7 +183,7 @@ impl KeyServerHttpHandler {
})) }))
}, },
Request::EcdsaSignMessage(document, signature, message_hash) => { Request::EcdsaSignMessage(document, signature, message_hash) => {
return_message_signature(&req_uri, self.handler.key_server.upgrade() return_message_signature(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.sign_message_ecdsa(&document, &signature.into(), message_hash)) .map(|key_server| key_server.sign_message_ecdsa(&document, &signature.into(), message_hash))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| { .map_err(|err| {
@ -187,7 +192,7 @@ impl KeyServerHttpHandler {
})) }))
}, },
Request::ChangeServersSet(old_set_signature, new_set_signature, new_servers_set) => { Request::ChangeServersSet(old_set_signature, new_set_signature, new_servers_set) => {
return_empty(&req_uri, self.handler.key_server.upgrade() return_empty(&req_uri, cors, self.handler.key_server.upgrade()
.map(|key_server| key_server.change_servers_set(old_set_signature, new_set_signature, new_servers_set)) .map(|key_server| key_server.change_servers_set(old_set_signature, new_set_signature, new_servers_set))
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into()))) .unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
.map_err(|err| { .map_err(|err| {
@ -213,69 +218,80 @@ impl Service for KeyServerHttpHandler {
type Future = Box<Future<Item = HttpResponse<Self::ResBody>, Error=Self::Error> + Send>; type Future = Box<Future<Item = HttpResponse<Self::ResBody>, Error=Self::Error> + Send>;
fn call(&mut self, req: HttpRequest<Body>) -> Self::Future { fn call(&mut self, req: HttpRequest<Body>) -> Self::Future {
if req.headers().contains_key(header::ORIGIN) { let cors = cors::get_cors_allow_origin(
warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method(), req.uri()); req.headers().get(header::ORIGIN).and_then(|value| value.to_str().ok()),
return Box::new(future::ok(HttpResponse::builder() req.headers().get(header::HOST).and_then(|value| value.to_str().ok()),
.status(HttpStatusCode::NOT_FOUND) &self.cors
.body(Body::empty()) );
.expect("Nothing to parse, cannot fail; qed"))) match cors {
} AllowCors::Invalid => {
warn!(target: "secretstore", "Ignoring {}-request {} with unauthorized Origin header", req.method(), req.uri());
Box::new(future::ok(HttpResponse::builder()
.status(HttpStatusCode::NOT_FOUND)
.body(Body::empty())
.expect("Nothing to parse, cannot fail; qed")
))
},
_ => {
let req_method = req.method().clone();
let req_uri = req.uri().clone();
// We cannot consume Self because of the Service trait requirement.
let this = self.clone();
let req_method = req.method().clone(); Box::new(req.into_body().concat2().map(move |body| {
let req_uri = req.uri().clone(); let path = req_uri.path().to_string();
// We cannot consume Self because of the Service trait requirement. if path.starts_with("/") {
let this = self.clone(); this.process(req_method, req_uri, &path, &body, cors)
} else {
Box::new(req.into_body().concat2().map(move |body| { warn!(target: "secretstore", "Ignoring invalid {}-request {}", req_method, req_uri);
let path = req_uri.path().to_string(); HttpResponse::builder()
if path.starts_with("/") { .status(HttpStatusCode::NOT_FOUND)
this.process(req_method, req_uri, &path, &body) .body(Body::empty())
} else { .expect("Nothing to parse, cannot fail; qed")
warn!(target: "secretstore", "Ignoring invalid {}-request {}", req_method, req_uri); }
HttpResponse::builder() }))
.status(HttpStatusCode::NOT_FOUND)
.body(Body::empty())
.expect("Nothing to parse, cannot fail; qed")
} }
})) }
} }
} }
fn return_empty(req_uri: &Uri, empty: Result<(), Error>) -> HttpResponse<Body> { fn return_empty(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, empty: Result<(), Error>) -> HttpResponse<Body> {
return_bytes::<i32>(req_uri, empty.map(|_| None)) return_bytes::<i32>(req_uri, cors, empty.map(|_| None))
} }
fn return_server_public_key(req_uri: &Uri, server_public: Result<Public, Error>) -> HttpResponse<Body> { fn return_server_public_key(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, server_public: Result<Public, Error>) -> HttpResponse<Body> {
return_bytes(req_uri, server_public.map(|k| Some(SerializablePublic(k)))) return_bytes(req_uri, cors, server_public.map(|k| Some(SerializablePublic(k))))
} }
fn return_message_signature(req_uri: &Uri, signature: Result<EncryptedDocumentKey, Error>) -> HttpResponse<Body> { fn return_message_signature(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, signature: Result<EncryptedDocumentKey, Error>) -> HttpResponse<Body> {
return_bytes(req_uri, signature.map(|s| Some(SerializableBytes(s)))) return_bytes(req_uri, cors, signature.map(|s| Some(SerializableBytes(s))))
} }
fn return_document_key(req_uri: &Uri, document_key: Result<EncryptedDocumentKey, Error>) -> HttpResponse<Body> { fn return_document_key(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, document_key: Result<EncryptedDocumentKey, Error>) -> HttpResponse<Body> {
return_bytes(req_uri, document_key.map(|k| Some(SerializableBytes(k)))) return_bytes(req_uri, cors, document_key.map(|k| Some(SerializableBytes(k))))
} }
fn return_document_key_shadow(req_uri: &Uri, document_key_shadow: Result<EncryptedDocumentKeyShadow, Error>) fn return_document_key_shadow(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, document_key_shadow: Result<EncryptedDocumentKeyShadow, Error>)
-> HttpResponse<Body> -> HttpResponse<Body>
{ {
return_bytes(req_uri, document_key_shadow.map(|k| Some(SerializableEncryptedDocumentKeyShadow { return_bytes(req_uri, cors, document_key_shadow.map(|k| Some(SerializableEncryptedDocumentKeyShadow {
decrypted_secret: k.decrypted_secret.into(), decrypted_secret: k.decrypted_secret.into(),
common_point: k.common_point.expect("always filled when requesting document_key_shadow; qed").into(), common_point: k.common_point.expect("always filled when requesting document_key_shadow; qed").into(),
decrypt_shadows: k.decrypt_shadows.expect("always filled when requesting document_key_shadow; qed").into_iter().map(Into::into).collect(), decrypt_shadows: k.decrypt_shadows.expect("always filled when requesting document_key_shadow; qed").into_iter().map(Into::into).collect()
}))) })))
} }
fn return_bytes<T: Serialize>(req_uri: &Uri, result: Result<Option<T>, Error>) -> HttpResponse<Body> { fn return_bytes<T: Serialize>(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, result: Result<Option<T>, Error>) -> HttpResponse<Body> {
match result { match result {
Ok(Some(result)) => match serde_json::to_vec(&result) { Ok(Some(result)) => match serde_json::to_vec(&result) {
Ok(result) => { Ok(result) => {
let body: Body = result.into(); let body: Body = result.into();
HttpResponse::builder() let mut builder = HttpResponse::builder();
.header(header::CONTENT_TYPE, HeaderValue::from_static("application/json; charset=utf-8")) builder.header(header::CONTENT_TYPE, HeaderValue::from_static("application/json; charset=utf-8"));
.body(body) if let AllowCors::Ok(AccessControlAllowOrigin::Value(origin)) = cors {
.expect("Error creating http response") builder.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.to_string());
}
builder.body(body).expect("Error creating http response")
}, },
Err(err) => { Err(err) => {
warn!(target: "secretstore", "response to request {} has failed with: {}", req_uri, err); warn!(target: "secretstore", "response to request {} has failed with: {}", req_uri, err);
@ -286,10 +302,12 @@ fn return_bytes<T: Serialize>(req_uri: &Uri, result: Result<Option<T>, Error>) -
} }
}, },
Ok(None) => { Ok(None) => {
HttpResponse::builder() let mut builder = HttpResponse::builder();
.status(HttpStatusCode::OK) builder.status(HttpStatusCode::OK);
.body(Body::empty()) if let AllowCors::Ok(AccessControlAllowOrigin::Value(origin)) = cors {
.expect("Nothing to parse, cannot fail; qed") builder.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.to_string());
}
builder.body(Body::empty()).expect("Nothing to parse, cannot fail; qed")
}, },
Err(err) => return_error(err), Err(err) => return_error(err),
} }
@ -423,7 +441,7 @@ mod tests {
let key_server: Arc<KeyServer> = Arc::new(DummyKeyServer::default()); let key_server: Arc<KeyServer> = Arc::new(DummyKeyServer::default());
let address = NodeAddress { address: "127.0.0.1".into(), port: 9000 }; let address = NodeAddress { address: "127.0.0.1".into(), port: 9000 };
let runtime = Runtime::with_thread_count(1); let runtime = Runtime::with_thread_count(1);
let listener = KeyServerHttpListener::start(address, Arc::downgrade(&key_server), let listener = KeyServerHttpListener::start(address, None, Arc::downgrade(&key_server),
runtime.executor()).unwrap(); runtime.executor()).unwrap();
drop(listener); drop(listener);
} }

View File

@ -70,6 +70,8 @@ pub struct ServiceConfiguration {
pub acl_check_contract_address: Option<ContractAddress>, pub acl_check_contract_address: Option<ContractAddress>,
/// Cluster configuration. /// Cluster configuration.
pub cluster_config: ClusterConfiguration, pub cluster_config: ClusterConfiguration,
// Allowed CORS domains
pub cors: Option<Vec<String>>,
} }
/// Key server cluster configuration /// Key server cluster configuration