Add ability to configure CORS header (#1345)
## Issue Addressed https://github.com/sigp/lighthouse/issues/1177 ## Proposed Changes Add a command line option (`--http-allow-origin`) and a config item for configuring the `Access-Control-Allow-Origin` response header. This should unblock making XMLHttpRequests.
This commit is contained in:
parent
4a01f44206
commit
fc5e6cbbb0
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -396,6 +396,7 @@ dependencies = [
|
||||
"exit-future",
|
||||
"futures 0.3.5",
|
||||
"genesis",
|
||||
"hyper 0.13.6",
|
||||
"logging",
|
||||
"node_test_rig",
|
||||
"rand 0.7.3",
|
||||
|
@ -39,3 +39,4 @@ eth2_libp2p = { path = "./eth2_libp2p" }
|
||||
eth2_ssz = "0.1.2"
|
||||
serde = "1.0.110"
|
||||
clap_utils = { path = "../common/clap_utils" }
|
||||
hyper = "0.13.5"
|
||||
|
@ -38,6 +38,9 @@ pub struct Config {
|
||||
pub listen_address: Ipv4Addr,
|
||||
/// The port the REST API HTTP server will listen on.
|
||||
pub port: u16,
|
||||
/// If something else than "", a 'Access-Control-Allow-Origin' header will be present in
|
||||
/// responses. Put *, to allow any origin.
|
||||
pub allow_origin: String,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -46,6 +49,7 @@ impl Default for Config {
|
||||
enabled: false,
|
||||
listen_address: Ipv4Addr::new(127, 0, 0, 1),
|
||||
port: 5052,
|
||||
allow_origin: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ pub enum ApiError {
|
||||
UnsupportedType(String),
|
||||
ImATeapot(String), // Just in case.
|
||||
ProcessingError(String), // A 202 error, for when a block/attestation cannot be processed, but still transmitted.
|
||||
InvalidHeaderValue(String),
|
||||
}
|
||||
|
||||
pub type ApiResult = Result<Response<Body>, ApiError>;
|
||||
@ -26,6 +27,7 @@ impl ApiError {
|
||||
ApiError::UnsupportedType(desc) => (StatusCode::UNSUPPORTED_MEDIA_TYPE, desc),
|
||||
ApiError::ImATeapot(desc) => (StatusCode::IM_A_TEAPOT, desc),
|
||||
ApiError::ProcessingError(desc) => (StatusCode::ACCEPTED, desc),
|
||||
ApiError::InvalidHeaderValue(desc) => (StatusCode::INTERNAL_SERVER_ERROR, desc),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,6 +79,12 @@ impl From<std::io::Error> for ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hyper::header::InvalidHeaderValue> for ApiError {
|
||||
fn from(e: hyper::header::InvalidHeaderValue) -> ApiError {
|
||||
ApiError::InvalidHeaderValue(format!("Invalid CORS header value: {:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for ApiError {
|
||||
fn cause(&self) -> Option<&dyn StdError> {
|
||||
None
|
||||
|
@ -64,12 +64,14 @@ pub fn start_server<T: BeaconChainTypes>(
|
||||
) -> Result<SocketAddr, hyper::Error> {
|
||||
let log = executor.log();
|
||||
let inner_log = log.clone();
|
||||
let rest_api_config = Arc::new(config.clone());
|
||||
let eth2_config = Arc::new(eth2_config);
|
||||
|
||||
// Define the function that will build the request handler.
|
||||
let make_service = make_service_fn(move |_socket: &AddrStream| {
|
||||
let beacon_chain = beacon_chain.clone();
|
||||
let log = inner_log.clone();
|
||||
let rest_api_config = rest_api_config.clone();
|
||||
let eth2_config = eth2_config.clone();
|
||||
let network_globals = network_info.network_globals.clone();
|
||||
let network_channel = network_info.network_chan.clone();
|
||||
@ -84,6 +86,7 @@ pub fn start_server<T: BeaconChainTypes>(
|
||||
beacon_chain.clone(),
|
||||
network_globals.clone(),
|
||||
network_channel.clone(),
|
||||
rest_api_config.clone(),
|
||||
eth2_config.clone(),
|
||||
log.clone(),
|
||||
db_path.clone(),
|
||||
|
@ -1,12 +1,13 @@
|
||||
use crate::{
|
||||
advanced, beacon, consensus, error::ApiError, helpers, lighthouse, metrics, network, node,
|
||||
spec, validator, NetworkChannel,
|
||||
advanced, beacon, config::Config, consensus, error::ApiError, helpers, lighthouse, metrics,
|
||||
network, node, spec, validator, NetworkChannel,
|
||||
};
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use bus::Bus;
|
||||
use eth2_config::Eth2Config;
|
||||
use eth2_libp2p::NetworkGlobals;
|
||||
use hyper::{Body, Error, Method, Request, Response};
|
||||
use hyper::header::HeaderValue;
|
||||
use hyper::{Body, Method, Request, Response};
|
||||
use parking_lot::Mutex;
|
||||
use slog::debug;
|
||||
use std::path::PathBuf;
|
||||
@ -21,20 +22,23 @@ pub async fn route<T: BeaconChainTypes>(
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
||||
network_channel: NetworkChannel<T::EthSpec>,
|
||||
rest_api_config: Arc<Config>,
|
||||
eth2_config: Arc<Eth2Config>,
|
||||
local_log: slog::Logger,
|
||||
db_path: PathBuf,
|
||||
freezer_db_path: PathBuf,
|
||||
events: Arc<Mutex<Bus<SignedBeaconBlockHash>>>,
|
||||
) -> Result<Response<Body>, Error> {
|
||||
) -> Result<Response<Body>, ApiError> {
|
||||
metrics::inc_counter(&metrics::REQUEST_COUNT);
|
||||
let timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME);
|
||||
let received_instant = Instant::now();
|
||||
|
||||
let path = req.uri().path().to_string();
|
||||
|
||||
let log = local_log.clone();
|
||||
let request_result = match (req.method(), path.as_ref()) {
|
||||
let result = {
|
||||
let _timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME);
|
||||
|
||||
match (req.method(), path.as_ref()) {
|
||||
// Methods for Client
|
||||
(&Method::GET, "/node/health") => node::get_health(req),
|
||||
(&Method::GET, "/node/version") => node::get_version(req),
|
||||
@ -50,7 +54,9 @@ pub async fn route<T: BeaconChainTypes>(
|
||||
|
||||
// Methods for Network
|
||||
(&Method::GET, "/network/enr") => network::get_enr::<T>(req, network_globals),
|
||||
(&Method::GET, "/network/peer_count") => network::get_peer_count::<T>(req, network_globals),
|
||||
(&Method::GET, "/network/peer_count") => {
|
||||
network::get_peer_count::<T>(req, network_globals)
|
||||
}
|
||||
(&Method::GET, "/network/peer_id") => network::get_peer_id::<T>(req, network_globals),
|
||||
(&Method::GET, "/network/peers") => network::get_peer_list::<T>(req, network_globals),
|
||||
(&Method::GET, "/network/listen_port") => {
|
||||
@ -70,7 +76,9 @@ pub async fn route<T: BeaconChainTypes>(
|
||||
let reader = events.lock().add_rx();
|
||||
beacon::stream_forks::<T>(log, reader)
|
||||
}
|
||||
(&Method::GET, "/beacon/genesis_time") => beacon::get_genesis_time::<T>(req, beacon_chain),
|
||||
(&Method::GET, "/beacon/genesis_time") => {
|
||||
beacon::get_genesis_time::<T>(req, beacon_chain)
|
||||
}
|
||||
(&Method::GET, "/beacon/genesis_validators_root") => {
|
||||
beacon::get_genesis_validators_root::<T>(req, beacon_chain)
|
||||
}
|
||||
@ -99,7 +107,8 @@ pub async fn route<T: BeaconChainTypes>(
|
||||
|
||||
// Methods for Validator
|
||||
(&Method::POST, "/validator/duties") => {
|
||||
let timer = metrics::start_timer(&metrics::VALIDATOR_GET_DUTIES_REQUEST_RESPONSE_TIME);
|
||||
let timer =
|
||||
metrics::start_timer(&metrics::VALIDATOR_GET_DUTIES_REQUEST_RESPONSE_TIME);
|
||||
let response = validator::post_validator_duties::<T>(req, beacon_chain);
|
||||
drop(timer);
|
||||
response.await
|
||||
@ -114,7 +123,8 @@ pub async fn route<T: BeaconChainTypes>(
|
||||
validator::get_active_validator_duties::<T>(req, beacon_chain)
|
||||
}
|
||||
(&Method::GET, "/validator/block") => {
|
||||
let timer = metrics::start_timer(&metrics::VALIDATOR_GET_BLOCK_REQUEST_RESPONSE_TIME);
|
||||
let timer =
|
||||
metrics::start_timer(&metrics::VALIDATOR_GET_BLOCK_REQUEST_RESPONSE_TIME);
|
||||
let response = validator::get_new_beacon_block::<T>(req, beacon_chain, log);
|
||||
drop(timer);
|
||||
response
|
||||
@ -136,7 +146,12 @@ pub async fn route<T: BeaconChainTypes>(
|
||||
validator::publish_attestations::<T>(req, beacon_chain, network_channel, log).await
|
||||
}
|
||||
(&Method::POST, "/validator/aggregate_and_proofs") => {
|
||||
validator::publish_aggregate_and_proofs::<T>(req, beacon_chain, network_channel, log)
|
||||
validator::publish_aggregate_and_proofs::<T>(
|
||||
req,
|
||||
beacon_chain,
|
||||
network_channel,
|
||||
log,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@ -151,7 +166,9 @@ pub async fn route<T: BeaconChainTypes>(
|
||||
// Methods for bootstrap and checking configuration
|
||||
(&Method::GET, "/spec") => spec::get_spec::<T>(req, beacon_chain),
|
||||
(&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::<T>(req),
|
||||
(&Method::GET, "/spec/deposit_contract") => helpers::implementation_pending_response(req),
|
||||
(&Method::GET, "/spec/deposit_contract") => {
|
||||
helpers::implementation_pending_response(req)
|
||||
}
|
||||
(&Method::GET, "/spec/eth2_config") => spec::get_eth2_config::<T>(req, eth2_config),
|
||||
|
||||
// Methods for advanced parameters
|
||||
@ -181,37 +198,44 @@ pub async fn route<T: BeaconChainTypes>(
|
||||
_ => Err(ApiError::NotFound(
|
||||
"Request path and/or method not found.".to_owned(),
|
||||
)),
|
||||
}
|
||||
};
|
||||
|
||||
let request_processing_duration = Instant::now().duration_since(received_instant);
|
||||
|
||||
// Map the Rust-friendly `Result` in to a http-friendly response. In effect, this ensures that
|
||||
// any `Err` returned from our response handlers becomes a valid http response to the client
|
||||
// (e.g., a response with a 404 or 500 status).
|
||||
let duration = Instant::now().duration_since(received_instant);
|
||||
match request_result {
|
||||
Ok(response) => {
|
||||
|
||||
match result {
|
||||
Ok(mut response) => {
|
||||
if rest_api_config.allow_origin != "" {
|
||||
let headers = response.headers_mut();
|
||||
headers.insert(
|
||||
hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
HeaderValue::from_str(&rest_api_config.allow_origin)?,
|
||||
);
|
||||
headers.insert(hyper::header::VARY, HeaderValue::from_static("Origin"));
|
||||
}
|
||||
|
||||
debug!(
|
||||
local_log,
|
||||
"HTTP API request successful";
|
||||
"path" => path,
|
||||
"duration_ms" => duration.as_millis()
|
||||
"duration_ms" => request_processing_duration.as_millis()
|
||||
);
|
||||
metrics::inc_counter(&metrics::SUCCESS_COUNT);
|
||||
metrics::stop_timer(timer);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
Err(e) => {
|
||||
let error_response = e.into();
|
||||
|
||||
Err(error) => {
|
||||
debug!(
|
||||
local_log,
|
||||
"HTTP API request failure";
|
||||
"path" => path,
|
||||
"duration_ms" => duration.as_millis()
|
||||
"duration_ms" => request_processing_duration.as_millis()
|
||||
);
|
||||
metrics::stop_timer(timer);
|
||||
|
||||
Ok(error_response)
|
||||
Ok(error.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,6 +161,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.default_value("5052")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("http-allow-origin")
|
||||
.long("http-allow-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)")
|
||||
.default_value("")
|
||||
.takes_value(true),
|
||||
)
|
||||
/* Websocket related arguments */
|
||||
.arg(
|
||||
Arg::with_name("ws")
|
||||
|
@ -4,6 +4,7 @@ use clap_utils::BAD_TESTNET_DIR_MESSAGE;
|
||||
use client::{config::DEFAULT_DATADIR, ClientConfig, ClientGenesis};
|
||||
use eth2_libp2p::{Enr, Multiaddr};
|
||||
use eth2_testnet_config::Eth2TestnetConfig;
|
||||
use hyper;
|
||||
use slog::{crit, info, Logger};
|
||||
use ssz::Encode;
|
||||
use std::fs;
|
||||
@ -220,6 +221,15 @@ pub fn get_config<E: EthSpec>(
|
||||
.map_err(|_| "http-port is not a valid u16.")?;
|
||||
}
|
||||
|
||||
if let Some(allow_origin) = cli_args.value_of("http-allow-origin") {
|
||||
// Pre-validate the config value to give feedback to the user on node startup, instead of
|
||||
// as late as when the first API response is produced.
|
||||
hyper::header::HeaderValue::from_str(allow_origin)
|
||||
.map_err(|_| "Invalid allow-origin value")?;
|
||||
|
||||
client_config.rest_api.allow_origin = allow_origin.to_string();
|
||||
}
|
||||
|
||||
/*
|
||||
* Websocket server
|
||||
*/
|
||||
|
@ -20,7 +20,6 @@ pub const VERSION: &str = git_version!(
|
||||
fallback = crate_version!()
|
||||
);
|
||||
pub const DEFAULT_DATA_DIR: &str = ".lighthouse";
|
||||
pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml";
|
||||
pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml";
|
||||
|
||||
fn main() {
|
||||
|
Loading…
Reference in New Issue
Block a user