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:
committed by
Wei Tang
parent
c5fa7aab43
commit
4cc274e75f
@@ -37,6 +37,7 @@ extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_service;
|
||||
extern crate url;
|
||||
extern crate jsonrpc_server_utils;
|
||||
|
||||
#[macro_use]
|
||||
extern crate ethabi_derive;
|
||||
@@ -107,7 +108,7 @@ pub fn start(client: Arc<Client>, sync: Arc<SyncProvider>, miner: Arc<Miner>, se
|
||||
|
||||
// prepare HTTP listener
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ use traits::KeyServer;
|
||||
use serialization::{SerializableEncryptedDocumentKeyShadow, SerializableBytes, SerializablePublic};
|
||||
use types::{Error, Public, MessageHash, NodeAddress, RequestSignature, ServerKeyId,
|
||||
EncryptedDocumentKey, EncryptedDocumentKeyShadow, NodeId};
|
||||
use jsonrpc_server_utils::cors::{self, AllowCors, AccessControlAllowOrigin};
|
||||
|
||||
/// Key server http-requests listener. Available requests:
|
||||
/// 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 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 {
|
||||
_executor: Executor,
|
||||
_handler: Arc<KeyServerSharedHttpHandler>,
|
||||
@@ -77,6 +80,7 @@ enum Request {
|
||||
#[derive(Clone)]
|
||||
struct KeyServerHttpHandler {
|
||||
handler: Arc<KeyServerSharedHttpHandler>,
|
||||
cors: CorsDomains,
|
||||
}
|
||||
|
||||
/// Shared http handler
|
||||
@@ -84,13 +88,14 @@ struct KeyServerSharedHttpHandler {
|
||||
key_server: Weak<KeyServer>,
|
||||
}
|
||||
|
||||
|
||||
impl KeyServerHttpListener {
|
||||
/// 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 {
|
||||
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 = TcpListener::bind(&listener_address)?;
|
||||
|
||||
@@ -101,7 +106,7 @@ impl KeyServerHttpListener {
|
||||
.for_each(move |socket| {
|
||||
let http = Http::new();
|
||||
let serve = http.serve_connection(socket,
|
||||
KeyServerHttpHandler { handler: shared_handler2.clone() }
|
||||
KeyServerHttpHandler { handler: shared_handler2.clone(), cors: cors.clone() }
|
||||
).map(|_| ()).map_err(|e| {
|
||||
warn!("Key server handler error: {:?}", e);
|
||||
});
|
||||
@@ -121,10 +126,10 @@ impl KeyServerHttpListener {
|
||||
}
|
||||
|
||||
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) {
|
||||
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))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
@@ -133,7 +138,7 @@ impl KeyServerHttpHandler {
|
||||
}))
|
||||
},
|
||||
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))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
@@ -142,7 +147,7 @@ impl KeyServerHttpHandler {
|
||||
}))
|
||||
},
|
||||
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))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
@@ -151,7 +156,7 @@ impl KeyServerHttpHandler {
|
||||
}))
|
||||
},
|
||||
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()))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
@@ -160,7 +165,7 @@ impl KeyServerHttpHandler {
|
||||
}))
|
||||
},
|
||||
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()))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
@@ -169,7 +174,7 @@ impl KeyServerHttpHandler {
|
||||
}))
|
||||
},
|
||||
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))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
@@ -178,7 +183,7 @@ impl KeyServerHttpHandler {
|
||||
}))
|
||||
},
|
||||
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))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
@@ -187,7 +192,7 @@ impl KeyServerHttpHandler {
|
||||
}))
|
||||
},
|
||||
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))
|
||||
.unwrap_or(Err(Error::Internal("KeyServer is already destroyed".into())))
|
||||
.map_err(|err| {
|
||||
@@ -213,69 +218,80 @@ impl Service for KeyServerHttpHandler {
|
||||
type Future = Box<Future<Item = HttpResponse<Self::ResBody>, Error=Self::Error> + Send>;
|
||||
|
||||
fn call(&mut self, req: HttpRequest<Body>) -> Self::Future {
|
||||
if req.headers().contains_key(header::ORIGIN) {
|
||||
warn!(target: "secretstore", "Ignoring {}-request {} with Origin header", req.method(), req.uri());
|
||||
return Box::new(future::ok(HttpResponse::builder()
|
||||
.status(HttpStatusCode::NOT_FOUND)
|
||||
.body(Body::empty())
|
||||
.expect("Nothing to parse, cannot fail; qed")))
|
||||
}
|
||||
let cors = cors::get_cors_allow_origin(
|
||||
req.headers().get(header::ORIGIN).and_then(|value| value.to_str().ok()),
|
||||
req.headers().get(header::HOST).and_then(|value| value.to_str().ok()),
|
||||
&self.cors
|
||||
);
|
||||
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();
|
||||
let req_uri = req.uri().clone();
|
||||
// We cannot consume Self because of the Service trait requirement.
|
||||
let this = self.clone();
|
||||
|
||||
Box::new(req.into_body().concat2().map(move |body| {
|
||||
let path = req_uri.path().to_string();
|
||||
if path.starts_with("/") {
|
||||
this.process(req_method, req_uri, &path, &body)
|
||||
} else {
|
||||
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")
|
||||
Box::new(req.into_body().concat2().map(move |body| {
|
||||
let path = req_uri.path().to_string();
|
||||
if path.starts_with("/") {
|
||||
this.process(req_method, req_uri, &path, &body, cors)
|
||||
} else {
|
||||
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> {
|
||||
return_bytes::<i32>(req_uri, empty.map(|_| None))
|
||||
fn return_empty(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, empty: Result<(), Error>) -> HttpResponse<Body> {
|
||||
return_bytes::<i32>(req_uri, cors, empty.map(|_| None))
|
||||
}
|
||||
|
||||
fn return_server_public_key(req_uri: &Uri, server_public: Result<Public, Error>) -> HttpResponse<Body> {
|
||||
return_bytes(req_uri, server_public.map(|k| Some(SerializablePublic(k))))
|
||||
fn return_server_public_key(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, server_public: Result<Public, Error>) -> HttpResponse<Body> {
|
||||
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> {
|
||||
return_bytes(req_uri, signature.map(|s| Some(SerializableBytes(s))))
|
||||
fn return_message_signature(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, signature: Result<EncryptedDocumentKey, Error>) -> HttpResponse<Body> {
|
||||
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> {
|
||||
return_bytes(req_uri, document_key.map(|k| Some(SerializableBytes(k))))
|
||||
fn return_document_key(req_uri: &Uri, cors: AllowCors<AccessControlAllowOrigin>, document_key: Result<EncryptedDocumentKey, Error>) -> HttpResponse<Body> {
|
||||
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>
|
||||
{
|
||||
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(),
|
||||
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 {
|
||||
Ok(Some(result)) => match serde_json::to_vec(&result) {
|
||||
Ok(result) => {
|
||||
let body: Body = result.into();
|
||||
HttpResponse::builder()
|
||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("application/json; charset=utf-8"))
|
||||
.body(body)
|
||||
.expect("Error creating http response")
|
||||
let mut builder = HttpResponse::builder();
|
||||
builder.header(header::CONTENT_TYPE, HeaderValue::from_static("application/json; charset=utf-8"));
|
||||
if let AllowCors::Ok(AccessControlAllowOrigin::Value(origin)) = cors {
|
||||
builder.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.to_string());
|
||||
}
|
||||
builder.body(body).expect("Error creating http response")
|
||||
},
|
||||
Err(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) => {
|
||||
HttpResponse::builder()
|
||||
.status(HttpStatusCode::OK)
|
||||
.body(Body::empty())
|
||||
.expect("Nothing to parse, cannot fail; qed")
|
||||
let mut builder = HttpResponse::builder();
|
||||
builder.status(HttpStatusCode::OK);
|
||||
if let AllowCors::Ok(AccessControlAllowOrigin::Value(origin)) = cors {
|
||||
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),
|
||||
}
|
||||
@@ -423,7 +441,7 @@ mod tests {
|
||||
let key_server: Arc<KeyServer> = Arc::new(DummyKeyServer::default());
|
||||
let address = NodeAddress { address: "127.0.0.1".into(), port: 9000 };
|
||||
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();
|
||||
drop(listener);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ pub struct ServiceConfiguration {
|
||||
pub acl_check_contract_address: Option<ContractAddress>,
|
||||
/// Cluster configuration.
|
||||
pub cluster_config: ClusterConfiguration,
|
||||
// Allowed CORS domains
|
||||
pub cors: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Key server cluster configuration
|
||||
|
||||
Reference in New Issue
Block a user