Support pre-flight CORS check (#1772)
## Issue Addressed - Resolves #1766 ## Proposed Changes - Use the `warp::filters::cors` filter instead of our work-around. ## Additional Info It's not trivial to enable/disable `cors` using `warp`, since using `routes.with(cors)` changes the type of `routes`. This makes it difficult to apply/not apply cors at runtime. My solution has been to *always* use the `warp::filters::cors` wrapper but when cors should be disabled, just pass the HTTP server listen address as the only permissible origin.
This commit is contained in:
parent
a3552a4b70
commit
a3704b971e
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -6534,8 +6534,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "warp"
|
name = "warp"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/paulhauner/warp?branch=cors-wildcard#a7685b76d70c3e5628e31d60aee510acec3c5c30"
|
||||||
checksum = "f41be6df54c97904af01aa23e613d4521eed7ab23537cede692d4058f6449407"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.5.6",
|
"bytes 0.5.6",
|
||||||
"futures 0.3.6",
|
"futures 0.3.6",
|
||||||
@ -6565,6 +6564,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"beacon_chain",
|
"beacon_chain",
|
||||||
"eth2",
|
"eth2",
|
||||||
|
"headers",
|
||||||
"safe_arith",
|
"safe_arith",
|
||||||
"serde",
|
"serde",
|
||||||
"state_processing",
|
"state_processing",
|
||||||
|
@ -5,7 +5,7 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
warp = "0.2.5"
|
warp = { git = "https://github.com/paulhauner/warp", branch = "cors-wildcard" }
|
||||||
serde = { version = "1.0.116", features = ["derive"] }
|
serde = { version = "1.0.116", features = ["derive"] }
|
||||||
tokio = { version = "0.2.22", features = ["macros"] }
|
tokio = { version = "0.2.22", features = ["macros"] }
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
|
@ -211,7 +211,19 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
|
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
|
||||||
let config = ctx.config.clone();
|
let config = ctx.config.clone();
|
||||||
let log = ctx.log.clone();
|
let log = ctx.log.clone();
|
||||||
let allow_origin = config.allow_origin.clone();
|
|
||||||
|
// Configure CORS.
|
||||||
|
let cors_builder = {
|
||||||
|
let builder = warp::cors()
|
||||||
|
.allow_methods(vec!["GET", "POST"])
|
||||||
|
.allow_headers(vec!["Content-Type"]);
|
||||||
|
|
||||||
|
warp_utils::cors::set_builder_origins(
|
||||||
|
builder,
|
||||||
|
config.allow_origin.as_deref(),
|
||||||
|
(config.listen_addr, config.listen_port),
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
// Sanity check.
|
// Sanity check.
|
||||||
if !config.enabled {
|
if !config.enabled {
|
||||||
@ -1827,8 +1839,7 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
.with(prometheus_metrics())
|
.with(prometheus_metrics())
|
||||||
// Add a `Server` header.
|
// Add a `Server` header.
|
||||||
.map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform()))
|
.map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform()))
|
||||||
// Maybe add some CORS headers.
|
.with(cors_builder.build());
|
||||||
.map(move |reply| warp_utils::reply::maybe_cors(reply, allow_origin.as_ref()));
|
|
||||||
|
|
||||||
let (listening_socket, server) = warp::serve(routes).try_bind_with_graceful_shutdown(
|
let (listening_socket, server) = warp::serve(routes).try_bind_with_graceful_shutdown(
|
||||||
SocketAddrV4::new(config.listen_addr, config.listen_port),
|
SocketAddrV4::new(config.listen_addr, config.listen_port),
|
||||||
|
@ -8,7 +8,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
prometheus = "0.10.0"
|
prometheus = "0.10.0"
|
||||||
warp = "0.2.5"
|
warp = { git = "https://github.com/paulhauner/warp", branch = "cors-wildcard" }
|
||||||
serde = { version = "1.0.116", features = ["derive"] }
|
serde = { version = "1.0.116", features = ["derive"] }
|
||||||
slog = "2.5.2"
|
slog = "2.5.2"
|
||||||
beacon_chain = { path = "../beacon_chain" }
|
beacon_chain = { path = "../beacon_chain" }
|
||||||
|
@ -87,7 +87,19 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
|
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
|
||||||
let config = &ctx.config;
|
let config = &ctx.config;
|
||||||
let log = ctx.log.clone();
|
let log = ctx.log.clone();
|
||||||
let allow_origin = config.allow_origin.clone();
|
|
||||||
|
// Configure CORS.
|
||||||
|
let cors_builder = {
|
||||||
|
let builder = warp::cors()
|
||||||
|
.allow_method("GET")
|
||||||
|
.allow_headers(vec!["Content-Type"]);
|
||||||
|
|
||||||
|
warp_utils::cors::set_builder_origins(
|
||||||
|
builder,
|
||||||
|
config.allow_origin.as_deref(),
|
||||||
|
(config.listen_addr, config.listen_port),
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
// Sanity check.
|
// Sanity check.
|
||||||
if !config.enabled {
|
if !config.enabled {
|
||||||
@ -115,8 +127,7 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
})
|
})
|
||||||
// Add a `Server` header.
|
// Add a `Server` header.
|
||||||
.map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform()))
|
.map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform()))
|
||||||
// Maybe add some CORS headers.
|
.with(cors_builder.build());
|
||||||
.map(move |reply| warp_utils::reply::maybe_cors(reply, allow_origin.as_ref()));
|
|
||||||
|
|
||||||
let (listening_socket, server) = warp::serve(routes).try_bind_with_graceful_shutdown(
|
let (listening_socket, server) = warp::serve(routes).try_bind_with_graceful_shutdown(
|
||||||
SocketAddrV4::new(config.listen_addr, config.listen_port),
|
SocketAddrV4::new(config.listen_addr, config.listen_port),
|
||||||
|
@ -171,8 +171,10 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
Arg::with_name("http-allow-origin")
|
Arg::with_name("http-allow-origin")
|
||||||
.long("http-allow-origin")
|
.long("http-allow-origin")
|
||||||
.value_name("ORIGIN")
|
.value_name("ORIGIN")
|
||||||
.help("Set the value of the Access-Control-Allow-Origin response HTTP header. Use * to allow any origin (not recommended in production)")
|
.help("Set the value of the Access-Control-Allow-Origin response HTTP header. \
|
||||||
.default_value("")
|
Use * to allow any origin (not recommended in production). \
|
||||||
|
If no value is supplied, the CORS allowed origin is set to the listen \
|
||||||
|
address of this server (e.g., http://localhost:5052).")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
/* Prometheus metrics HTTP server related arguments */
|
/* Prometheus metrics HTTP server related arguments */
|
||||||
@ -202,9 +204,10 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
Arg::with_name("metrics-allow-origin")
|
Arg::with_name("metrics-allow-origin")
|
||||||
.long("metrics-allow-origin")
|
.long("metrics-allow-origin")
|
||||||
.value_name("ORIGIN")
|
.value_name("ORIGIN")
|
||||||
.help("Set the value of the Access-Control-Allow-Origin response HTTP header for the Prometheus metrics HTTP server. \
|
.help("Set the value of the Access-Control-Allow-Origin response HTTP header. \
|
||||||
Use * to allow any origin (not recommended in production)")
|
Use * to allow any origin (not recommended in production). \
|
||||||
.default_value("")
|
If no value is supplied, the CORS allowed origin is set to the listen \
|
||||||
|
address of this server (e.g., http://localhost:5054).")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
/* Websocket related arguments */
|
/* Websocket related arguments */
|
||||||
|
@ -7,7 +7,7 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
warp = "0.2.5"
|
warp = { git = "https://github.com/paulhauner/warp", branch = "cors-wildcard" }
|
||||||
eth2 = { path = "../eth2" }
|
eth2 = { path = "../eth2" }
|
||||||
types = { path = "../../consensus/types" }
|
types = { path = "../../consensus/types" }
|
||||||
beacon_chain = { path = "../../beacon_node/beacon_chain" }
|
beacon_chain = { path = "../../beacon_node/beacon_chain" }
|
||||||
@ -15,3 +15,4 @@ state_processing = { path = "../../consensus/state_processing" }
|
|||||||
safe_arith = { path = "../../consensus/safe_arith" }
|
safe_arith = { path = "../../consensus/safe_arith" }
|
||||||
serde = { version = "1.0.116", features = ["derive"] }
|
serde = { version = "1.0.116", features = ["derive"] }
|
||||||
tokio = { version = "0.2.22", features = ["sync"] }
|
tokio = { version = "0.2.22", features = ["sync"] }
|
||||||
|
headers = "0.3.2"
|
||||||
|
76
common/warp_utils/src/cors.rs
Normal file
76
common/warp_utils/src/cors.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use std::net::Ipv4Addr;
|
||||||
|
use warp::filters::cors::Builder;
|
||||||
|
|
||||||
|
/// Configure a `cors::Builder`.
|
||||||
|
///
|
||||||
|
/// If `allow_origin.is_none()` the `default_origin` is used.
|
||||||
|
pub fn set_builder_origins(
|
||||||
|
builder: Builder,
|
||||||
|
allow_origin: Option<&str>,
|
||||||
|
default_origin: (Ipv4Addr, u16),
|
||||||
|
) -> Result<Builder, String> {
|
||||||
|
if let Some(allow_origin) = allow_origin {
|
||||||
|
let origins = allow_origin
|
||||||
|
.split(',')
|
||||||
|
.map(|s| verify_cors_origin_str(s).map(|_| s))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(builder.allow_origins(origins))
|
||||||
|
} else {
|
||||||
|
let origin = format!("http://{}:{}", default_origin.0, default_origin.1);
|
||||||
|
verify_cors_origin_str(&origin)?;
|
||||||
|
|
||||||
|
Ok(builder.allow_origin(origin.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify that `s` can be used as a CORS origin.
|
||||||
|
///
|
||||||
|
/// ## Notes
|
||||||
|
///
|
||||||
|
/// We need this function since `warp` will panic if provided an invalid origin. The verification
|
||||||
|
/// code is taken from here:
|
||||||
|
///
|
||||||
|
/// https://github.com/seanmonstar/warp/blob/3d1760c6ca35ce2d03dee0562259d0320e9face3/src/filters/cors.rs#L616
|
||||||
|
///
|
||||||
|
/// Ideally we should make a PR to `warp` to expose this behaviour, however we defer this for a
|
||||||
|
/// later time. The impact of a false-positive on this function is fairly limited, since only
|
||||||
|
/// trusted users should be setting CORS origins.
|
||||||
|
fn verify_cors_origin_str(s: &str) -> Result<(), String> {
|
||||||
|
// Always the wildcard origin.
|
||||||
|
if s == "*" {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts = s.splitn(2, "://");
|
||||||
|
let scheme = parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| format!("{} is missing a scheme", s))?;
|
||||||
|
let rest = parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| format!("{} is missing the part following the scheme", s))?;
|
||||||
|
|
||||||
|
headers::Origin::try_from_parts(scheme, rest, None)
|
||||||
|
.map_err(|e| format!("Unable to parse {}: {}", s, e))
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn valid_origins() {
|
||||||
|
verify_cors_origin_str("*").unwrap();
|
||||||
|
verify_cors_origin_str("http://127.0.0.1").unwrap();
|
||||||
|
verify_cors_origin_str("http://localhost").unwrap();
|
||||||
|
verify_cors_origin_str("http://127.0.0.1:8000").unwrap();
|
||||||
|
verify_cors_origin_str("http://localhost:8000").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_origins() {
|
||||||
|
verify_cors_origin_str(".*").unwrap_err();
|
||||||
|
verify_cors_origin_str("127.0.0.1").unwrap_err();
|
||||||
|
verify_cors_origin_str("localhost").unwrap_err();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
//! This crate contains functions that are common across multiple `warp` HTTP servers in the
|
//! This crate contains functions that are common across multiple `warp` HTTP servers in the
|
||||||
//! Lighthouse project. E.g., the `http_api` and `http_metrics` crates.
|
//! Lighthouse project. E.g., the `http_api` and `http_metrics` crates.
|
||||||
|
|
||||||
|
pub mod cors;
|
||||||
pub mod reject;
|
pub mod reject;
|
||||||
pub mod reply;
|
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
/// Add CORS headers to `reply` only if `allow_origin.is_some()`.
|
|
||||||
pub fn maybe_cors<T: warp::Reply + 'static>(
|
|
||||||
reply: T,
|
|
||||||
allow_origin: Option<&String>,
|
|
||||||
) -> Box<dyn warp::Reply> {
|
|
||||||
if let Some(allow_origin) = allow_origin {
|
|
||||||
Box::new(warp::reply::with_header(
|
|
||||||
reply,
|
|
||||||
"Access-Control-Allow-Origin",
|
|
||||||
allow_origin,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Box::new(reply)
|
|
||||||
}
|
|
||||||
}
|
|
@ -52,7 +52,7 @@ eth2_keystore = { path = "../crypto/eth2_keystore" }
|
|||||||
account_utils = { path = "../common/account_utils" }
|
account_utils = { path = "../common/account_utils" }
|
||||||
lighthouse_version = { path = "../common/lighthouse_version" }
|
lighthouse_version = { path = "../common/lighthouse_version" }
|
||||||
warp_utils = { path = "../common/warp_utils" }
|
warp_utils = { path = "../common/warp_utils" }
|
||||||
warp = "0.2.5"
|
warp = { git = "https://github.com/paulhauner/warp", branch = "cors-wildcard" }
|
||||||
hyper = "0.13.8"
|
hyper = "0.13.8"
|
||||||
serde_utils = { path = "../consensus/serde_utils" }
|
serde_utils = { path = "../consensus/serde_utils" }
|
||||||
libsecp256k1 = "0.3.5"
|
libsecp256k1 = "0.3.5"
|
||||||
|
@ -131,8 +131,10 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
Arg::with_name("http-allow-origin")
|
Arg::with_name("http-allow-origin")
|
||||||
.long("http-allow-origin")
|
.long("http-allow-origin")
|
||||||
.value_name("ORIGIN")
|
.value_name("ORIGIN")
|
||||||
.help("Set the value of the Access-Control-Allow-Origin response HTTP header. Use * to allow any origin (not recommended in production)")
|
.help("Set the value of the Access-Control-Allow-Origin response HTTP header. \
|
||||||
.default_value("")
|
Use * to allow any origin (not recommended in production). \
|
||||||
|
If no value is supplied, the CORS allowed origin is set to the listen \
|
||||||
|
address of this server (e.g., http://localhost:5062).")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,19 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
|||||||
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
|
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
|
||||||
let config = &ctx.config;
|
let config = &ctx.config;
|
||||||
let log = ctx.log.clone();
|
let log = ctx.log.clone();
|
||||||
let allow_origin = config.allow_origin.clone();
|
|
||||||
|
// Configure CORS.
|
||||||
|
let cors_builder = {
|
||||||
|
let builder = warp::cors()
|
||||||
|
.allow_methods(vec!["GET", "POST", "PATCH"])
|
||||||
|
.allow_headers(vec!["Content-Type", "Authorization"]);
|
||||||
|
|
||||||
|
warp_utils::cors::set_builder_origins(
|
||||||
|
builder,
|
||||||
|
config.allow_origin.as_deref(),
|
||||||
|
(config.listen_addr, config.listen_port),
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
// Sanity check.
|
// Sanity check.
|
||||||
if !config.enabled {
|
if !config.enabled {
|
||||||
@ -428,8 +440,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
|||||||
.recover(warp_utils::reject::handle_rejection)
|
.recover(warp_utils::reject::handle_rejection)
|
||||||
// Add a `Server` header.
|
// Add a `Server` header.
|
||||||
.map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform()))
|
.map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform()))
|
||||||
// Maybe add some CORS headers.
|
.with(cors_builder.build());
|
||||||
.map(move |reply| warp_utils::reply::maybe_cors(reply, allow_origin.as_ref()));
|
|
||||||
|
|
||||||
let (listening_socket, server) = warp::serve(routes).try_bind_with_graceful_shutdown(
|
let (listening_socket, server) = warp::serve(routes).try_bind_with_graceful_shutdown(
|
||||||
SocketAddrV4::new(config.listen_addr, config.listen_port),
|
SocketAddrV4::new(config.listen_addr, config.listen_port),
|
||||||
|
Loading…
Reference in New Issue
Block a user