Skip to content

Commit

Permalink
Add IP check to config (#562)
Browse files Browse the repository at this point in the history
* Add IP check to config

* Check -> Validate

* Add an 'ip-check' feature to mobilecoind, wraps up the reqwest stuff.

* Continuous deployment without default features

* Only check if in online mode

Co-authored-by: James Cape <[email protected]>
  • Loading branch information
sugargoat and James Cape authored Nov 24, 2020
1 parent 860eae8 commit deed8d9
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions jenkins/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pipeline {
// Can be swapped out for specific build commands
sh 'cargo build --release'

// Build mobilecoind without default features
sh 'cargo build --release -p mc-mobilecoind --no-default-features'

// Staging area for binary packaging
sh 'mkdir -p $WORKSPACE/ops/bin'

Expand Down
6 changes: 6 additions & 0 deletions mobilecoind/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ edition = "2018"
name = "mobilecoind"
path = "src/bin/main.rs"

[features]
default = ["ip-check"]
ip-check = []

[dependencies]
mc-account-keys = { path = "../account-keys" }
mc-api = { path = "../api" }
Expand Down Expand Up @@ -36,6 +40,7 @@ mc-util-uri = { path = "../util/uri" }
mc-watcher = { path = "../watcher" }

crossbeam-channel = "0.4"
displaydoc = { version = "0.1", default-features = false}
failure = "0.1.8"
futures = "0.3"
grpcio = "0.6.0"
Expand All @@ -45,6 +50,7 @@ lru = { version = "0.1" }
num_cpus = "1.12"
prost = { version = "0.6.1", default-features = false, features = ["prost-derive"] }
protobuf = "2.12"
reqwest = { version = "0.10", default-features = false, features = ["rustls-tls", "gzip"] }
rand = "0.7"
rand_core = "0.5"
retry = "0.5.1"
Expand Down
3 changes: 3 additions & 0 deletions mobilecoind/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ use structopt::StructOpt;

fn main() {
let config = Config::from_args();
if !cfg!(debug_assertions) && !config.offline {
config.validate_host().expect("Could not validate host");
}

mc_common::setup_panic_handler();
let _sentry_guard = mc_common::sentry::init();
Expand Down
76 changes: 76 additions & 0 deletions mobilecoind/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

//! Configuration parameters for mobilecoind
use displaydoc::Display;
use mc_attest_core::{MrSignerVerifier, Verifier, DEBUG_ENCLAVE};
use mc_common::{logger::Logger, ResponderId};
use mc_connection::{ConnectionManager, ThickClient};
Expand All @@ -10,6 +11,11 @@ use mc_fog_report_connection::GrpcFogPubkeyResolver;
use mc_mobilecoind_api::MobilecoindUri;
use mc_sgx_css::Signature;
use mc_util_uri::{ConnectionUri, ConsensusClientUri};
#[cfg(feature = "ip-check")]
use reqwest::{
blocking::Client,
header::{HeaderMap, HeaderValue, CONTENT_TYPE},
};
use std::{convert::TryFrom, fs, path::PathBuf, str::FromStr, sync::Arc, time::Duration};
use structopt::StructOpt;

Expand Down Expand Up @@ -95,6 +101,33 @@ fn load_css_file(filename: &str) -> Result<Signature, String> {
Ok(signature)
}

#[derive(Display, Debug)]
pub enum ConfigError {
/// Error parsing json {0}
Json(serde_json::Error),

/// Error handling reqwest {0}
Reqwest(reqwest::Error),

/// Invalid country
InvalidCountry,

/// Data missing in the response {0}
DataMissing(String),
}

impl From<serde_json::Error> for ConfigError {
fn from(e: serde_json::Error) -> Self {
Self::Json(e)
}
}

impl From<reqwest::Error> for ConfigError {
fn from(e: reqwest::Error) -> Self {
Self::Reqwest(e)
}
}

impl Config {
pub fn quorum_set(&self) -> QuorumSet<ResponderId> {
// If we have an explicit quorum set, use that.
Expand Down Expand Up @@ -149,6 +182,49 @@ impl Config {
GrpcFogPubkeyResolver::new(&report_verifier, env, logger)
})
}

/// Ensure local IP address is valid.
///
/// Uses icanhazip.com for getting local IP.
/// Uses ipinfo.io for getting details about IP address.
///
/// Note, both of these services are free tier and rate-limited. A longer term solution
/// would be to filter on the consensus server.
#[cfg(feature = "ip-check")]
pub fn validate_host(&self) -> Result<(), ConfigError> {
let client = Client::builder().gzip(true).use_rustls_tls().build()?;
let mut json_headers = HeaderMap::new();
json_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
let response = client
.get("https://icanhazip.com")
.send()?
.error_for_status()?;
let local_ip_addr = response.text()?;
let response = client
.get(format!("https://ipinfo.io/{}/json/", local_ip_addr).as_str())
.headers(json_headers)
.send()?
.error_for_status()?;
let data = response.text()?;
let data_json: serde_json::Value = serde_json::from_str(&data)?;
if let Some(v) = data_json.get("country") {
if let Some(country) = v.as_str() {
match country {
"US" => Err(ConfigError::InvalidCountry),
_ => Ok(()),
}
} else {
Err(ConfigError::DataMissing(data_json.to_string()))
}
} else {
Err(ConfigError::DataMissing(data_json.to_string()))
}
}

#[cfg(not(feature = "ip-check"))]
pub fn validate_host(&self) -> Result<(), ConfigError> {
Ok(())
}
}

#[derive(Clone, Debug, StructOpt)]
Expand Down

0 comments on commit deed8d9

Please sign in to comment.