Problem: insecure RPCs are subject to MITM attacks
Solution: by default, disallow use of non-TLS RPC endpoints For testing, there's an escape hatch of a command line argument `--allow-insecure-rpc-endpoints` (purposefully long) that will reduce the severity of using a non-TLS RPC endpoint to a warning in a log file. It was not made to be a configuration file option to reduce the risk of this option slipping into a production configuration file by mistake. Closes #79
This commit is contained in:
parent
036e83ece2
commit
18802df14c
|
@ -54,6 +54,11 @@ bridge --config config.toml --database db.toml
|
||||||
- `--config` - location of the configuration file. configuration file must exist
|
- `--config` - location of the configuration file. configuration file must exist
|
||||||
- `--database` - location of the database file.
|
- `--database` - location of the database file.
|
||||||
|
|
||||||
|
Bridge forces TLS for RPC connections by default. However, in some limited scenarios (like local testing),
|
||||||
|
this might be undesirable. In this case, you can use `--allow-insecure-rpc-endpoints` option to allow non-TLS
|
||||||
|
endpoints to be used. Ensure, however, that this option is not going to be used in production.
|
||||||
|
|
||||||
|
|
||||||
#### Exit Status Codes
|
#### Exit Status Codes
|
||||||
|
|
||||||
| Code | Meaning |
|
| Code | Meaning |
|
||||||
|
|
|
@ -8,7 +8,7 @@ use rustc_hex::FromHex;
|
||||||
use web3::types::Address;
|
use web3::types::Address;
|
||||||
#[cfg(feature = "deploy")]
|
#[cfg(feature = "deploy")]
|
||||||
use web3::types::Bytes;
|
use web3::types::Bytes;
|
||||||
use error::{ResultExt, Error};
|
use error::{ResultExt, Error, ErrorKind};
|
||||||
use {toml};
|
use {toml};
|
||||||
|
|
||||||
const DEFAULT_POLL_INTERVAL: u64 = 1;
|
const DEFAULT_POLL_INTERVAL: u64 = 1;
|
||||||
|
@ -33,22 +33,22 @@ pub struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Config, Error> {
|
pub fn load<P: AsRef<Path>>(path: P, allow_insecure_rpc_endpoints: bool) -> Result<Config, Error> {
|
||||||
let mut file = fs::File::open(path).chain_err(|| "Cannot open config")?;
|
let mut file = fs::File::open(path).chain_err(|| "Cannot open config")?;
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
file.read_to_string(&mut buffer).expect("TODO");
|
file.read_to_string(&mut buffer).expect("TODO");
|
||||||
Self::load_from_str(&buffer)
|
Self::load_from_str(&buffer, allow_insecure_rpc_endpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_from_str(s: &str) -> Result<Config, Error> {
|
fn load_from_str(s: &str, allow_insecure_rpc_endpoints: bool) -> Result<Config, Error> {
|
||||||
let config: load::Config = toml::from_str(s).chain_err(|| "Cannot parse config")?;
|
let config: load::Config = toml::from_str(s).chain_err(|| "Cannot parse config")?;
|
||||||
Config::from_load_struct(config)
|
Config::from_load_struct(config, allow_insecure_rpc_endpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_load_struct(config: load::Config) -> Result<Config, Error> {
|
fn from_load_struct(config: load::Config, allow_insecure_rpc_endpoints: bool) -> Result<Config, Error> {
|
||||||
let result = Config {
|
let result = Config {
|
||||||
home: Node::from_load_struct(config.home)?,
|
home: Node::from_load_struct(config.home, allow_insecure_rpc_endpoints)?,
|
||||||
foreign: Node::from_load_struct(config.foreign)?,
|
foreign: Node::from_load_struct(config.foreign, allow_insecure_rpc_endpoints)?,
|
||||||
authorities: Authorities {
|
authorities: Authorities {
|
||||||
#[cfg(feature = "deploy")]
|
#[cfg(feature = "deploy")]
|
||||||
accounts: config.authorities.accounts,
|
accounts: config.authorities.accounts,
|
||||||
|
@ -106,7 +106,7 @@ impl PartialEq for NodeInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
fn from_load_struct(node: load::Node) -> Result<Node, Error> {
|
fn from_load_struct(node: load::Node, allow_insecure_rpc_endpoints: bool) -> Result<Node, Error> {
|
||||||
let gas_price_oracle_url = node.gas_price_oracle_url.clone();
|
let gas_price_oracle_url = node.gas_price_oracle_url.clone();
|
||||||
|
|
||||||
let gas_price_speed = match node.gas_price_speed {
|
let gas_price_speed = match node.gas_price_speed {
|
||||||
|
@ -122,6 +122,16 @@ impl Node {
|
||||||
let default_gas_price = node.default_gas_price.unwrap_or(DEFAULT_GAS_PRICE_WEI);
|
let default_gas_price = node.default_gas_price.unwrap_or(DEFAULT_GAS_PRICE_WEI);
|
||||||
let concurrent_http_requests = node.concurrent_http_requests.unwrap_or(DEFAULT_CONCURRENCY);
|
let concurrent_http_requests = node.concurrent_http_requests.unwrap_or(DEFAULT_CONCURRENCY);
|
||||||
|
|
||||||
|
let rpc_host = node.rpc_host.unwrap();
|
||||||
|
|
||||||
|
if !rpc_host.starts_with("https://") {
|
||||||
|
if !allow_insecure_rpc_endpoints {
|
||||||
|
return Err(ErrorKind::ConfigError(format!("RPC endpoints must use TLS, {} doesn't", rpc_host)).into());
|
||||||
|
} else {
|
||||||
|
warn!("RPC endpoints must use TLS, {} doesn't", rpc_host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let result = Node {
|
let result = Node {
|
||||||
account: node.account,
|
account: node.account,
|
||||||
#[cfg(feature = "deploy")]
|
#[cfg(feature = "deploy")]
|
||||||
|
@ -136,7 +146,7 @@ impl Node {
|
||||||
request_timeout: Duration::from_secs(node.request_timeout.unwrap_or(DEFAULT_TIMEOUT)),
|
request_timeout: Duration::from_secs(node.request_timeout.unwrap_or(DEFAULT_TIMEOUT)),
|
||||||
poll_interval: Duration::from_secs(node.poll_interval.unwrap_or(DEFAULT_POLL_INTERVAL)),
|
poll_interval: Duration::from_secs(node.poll_interval.unwrap_or(DEFAULT_POLL_INTERVAL)),
|
||||||
required_confirmations: node.required_confirmations.unwrap_or(DEFAULT_CONFIRMATIONS),
|
required_confirmations: node.required_confirmations.unwrap_or(DEFAULT_CONFIRMATIONS),
|
||||||
rpc_host: node.rpc_host.unwrap(),
|
rpc_host,
|
||||||
rpc_port: node.rpc_port.unwrap_or(DEFAULT_RPC_PORT),
|
rpc_port: node.rpc_port.unwrap_or(DEFAULT_RPC_PORT),
|
||||||
password: node.password,
|
password: node.password,
|
||||||
info: Default::default(),
|
info: Default::default(),
|
||||||
|
@ -398,7 +408,7 @@ required_signatures = 2
|
||||||
keystore: "/keys/".into(),
|
keystore: "/keys/".into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = Config::load_from_str(toml).unwrap();
|
let config = Config::load_from_str(toml, true).unwrap();
|
||||||
assert_eq!(expected, config);
|
assert_eq!(expected, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,7 +471,7 @@ required_signatures = 2
|
||||||
keystore: "/keys/".into(),
|
keystore: "/keys/".into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = Config::load_from_str(toml).unwrap();
|
let config = Config::load_from_str(toml, true).unwrap();
|
||||||
assert_eq!(expected, config);
|
assert_eq!(expected, config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,10 @@ error_chain! {
|
||||||
OtherError(error: String) {
|
OtherError(error: String) {
|
||||||
description("other error")
|
description("other error")
|
||||||
display("{}", error)
|
display("{}", error)
|
||||||
|
}
|
||||||
|
ConfigError(err: String) {
|
||||||
|
description("config error")
|
||||||
|
display("{}", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,13 +73,14 @@ POA-Ethereum bridge.
|
||||||
Copyright 2018 POA Networks Ltd.
|
Copyright 2018 POA Networks Ltd.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
bridge --config <config> --database <database>
|
bridge [options] --config <config> --database <database>
|
||||||
bridge -h | --help
|
bridge -h | --help
|
||||||
bridge -v | --version
|
bridge -v | --version
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Display help message and exit.
|
-h, --help Display help message and exit.
|
||||||
-v, --version Print version and exit.
|
-v, --version Print version and exit.
|
||||||
|
--allow-insecure-rpc-endpoints Allow non-HTTPS endpoints
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -87,6 +88,7 @@ pub struct Args {
|
||||||
arg_config: PathBuf,
|
arg_config: PathBuf,
|
||||||
arg_database: PathBuf,
|
arg_database: PathBuf,
|
||||||
flag_version: bool,
|
flag_version: bool,
|
||||||
|
flag_allow_insecure_rpc_endpoints: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -128,7 +130,7 @@ fn execute<S, I>(command: I, running: Arc<AtomicBool>) -> Result<String, UserFac
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(target: "bridge", "Loading config");
|
info!(target: "bridge", "Loading config");
|
||||||
let config = Config::load(args.arg_config)?;
|
let config = Config::load(args.arg_config, args.flag_allow_insecure_rpc_endpoints)?;
|
||||||
|
|
||||||
info!(target: "bridge", "Starting event loop");
|
info!(target: "bridge", "Starting event loop");
|
||||||
let mut event_loop = Core::new().unwrap();
|
let mut event_loop = Core::new().unwrap();
|
||||||
|
|
|
@ -163,6 +163,7 @@ fn test_basic_deposit_then_withdraw() {
|
||||||
.env("RUST_LOG", "info")
|
.env("RUST_LOG", "info")
|
||||||
.arg("--config").arg("bridge_config.toml")
|
.arg("--config").arg("bridge_config.toml")
|
||||||
.arg("--database").arg("tmp/bridge1_db.txt")
|
.arg("--database").arg("tmp/bridge1_db.txt")
|
||||||
|
.arg("--allow-insecure-rpc-endpoints")
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("failed to spawn bridge process");
|
.expect("failed to spawn bridge process");
|
||||||
|
|
||||||
|
|
|
@ -194,6 +194,7 @@ fn test_insufficient_funds() {
|
||||||
.env("RUST_LOG", "info")
|
.env("RUST_LOG", "info")
|
||||||
.arg("--config").arg("bridge_config_gas_price.toml")
|
.arg("--config").arg("bridge_config_gas_price.toml")
|
||||||
.arg("--database").arg("tmp/bridge1_db.txt")
|
.arg("--database").arg("tmp/bridge1_db.txt")
|
||||||
|
.arg("--allow-insecure-rpc-endpoints")
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("failed to spawn bridge process");
|
.expect("failed to spawn bridge process");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue