@@ -1,4 +1,4 @@ | |||||
#![deny(warnings)] | |||||
//#![deny(warnings)] | |||||
/** | /** | ||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | * Copyright (c) 2020 Bubble, Inc. All rights reserved. | ||||
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | * For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | ||||
@@ -28,16 +28,23 @@ struct AdminRegistration { | |||||
#[derive(Debug, Deserialize, Serialize, Clone)] | #[derive(Debug, Deserialize, Serialize, Clone)] | ||||
struct BubbleRegistration { | struct BubbleRegistration { | ||||
key: String, | |||||
ip: String, | ip: String, | ||||
proxy_port: u16, | |||||
auth_token: String | auth_token: String | ||||
} | } | ||||
#[derive(Debug, Deserialize, Serialize, Clone)] | |||||
struct BubbleRegistrationResponse { | |||||
port: Option<u16> | |||||
} | |||||
pub async fn start_admin (admin_port : u16, | pub async fn start_admin (admin_port : u16, | ||||
proxy_ip : String, | proxy_ip : String, | ||||
proxy_port : u16, | proxy_port : u16, | ||||
password_hash: String, | password_hash: String, | ||||
auth_token : Arc<String>) { | |||||
auth_token : Arc<String>, | |||||
ssh_priv_key : Arc<String>, | |||||
ssh_pub_key : Arc<String>) { | |||||
let admin_sock: SocketAddr = format!("127.0.0.1:{}", admin_port).parse().unwrap(); | let admin_sock: SocketAddr = format!("127.0.0.1:{}", admin_port).parse().unwrap(); | ||||
let register = warp::path!("register") | let register = warp::path!("register") | ||||
@@ -47,6 +54,8 @@ pub async fn start_admin (admin_port : u16, | |||||
.and(warp::any().map(move || proxy_port)) | .and(warp::any().map(move || proxy_port)) | ||||
.and(warp::any().map(move || password_hash.clone())) | .and(warp::any().map(move || password_hash.clone())) | ||||
.and(warp::any().map(move || auth_token.clone())) | .and(warp::any().map(move || auth_token.clone())) | ||||
.and(warp::any().map(move || ssh_priv_key.clone())) | |||||
.and(warp::any().map(move || ssh_pub_key.clone())) | |||||
.and_then(handle_register); | .and_then(handle_register); | ||||
let routes = warp::post().and(register); | let routes = warp::post().and(register); | ||||
@@ -62,7 +71,9 @@ async fn handle_register(registration : AdminRegistration, | |||||
proxy_ip: String, | proxy_ip: String, | ||||
proxy_port : u16, | proxy_port : u16, | ||||
hashed_password : String, | hashed_password : String, | ||||
auth_token : Arc<String>) -> Result<impl warp::Reply, warp::Rejection> { | |||||
auth_token : Arc<String>, | |||||
ssh_priv_key : Arc<String>, | |||||
ssh_pub_key : Arc<String>) -> Result<impl warp::Reply, warp::Rejection> { | |||||
let pass_result = is_correct_password(registration.password, hashed_password); | let pass_result = is_correct_password(registration.password, hashed_password); | ||||
if pass_result.is_err() { | if pass_result.is_err() { | ||||
error!("handle_register: error verifying password: {:?}", pass_result.err()); | error!("handle_register: error verifying password: {:?}", pass_result.err()); | ||||
@@ -80,8 +91,8 @@ async fn handle_register(registration : AdminRegistration, | |||||
// create the registration object | // create the registration object | ||||
let bubble_registration = BubbleRegistration { | let bubble_registration = BubbleRegistration { | ||||
key: ssh_pub_key.to_string(), | |||||
ip: proxy_ip, | ip: proxy_ip, | ||||
proxy_port, | |||||
auth_token: auth_token.to_string() | auth_token: auth_token.to_string() | ||||
}; | }; | ||||
@@ -97,13 +108,41 @@ async fn handle_register(registration : AdminRegistration, | |||||
match response.status() { | match response.status() { | ||||
ReqwestStatusCode::OK => { | ReqwestStatusCode::OK => { | ||||
info!("handle_register: successfully registered with bubble"); | info!("handle_register: successfully registered with bubble"); | ||||
Ok(warp::reply::with_status( | |||||
"successfully registered with bubble", | |||||
http::StatusCode::OK, | |||||
)) | |||||
let body_bytes = &response.bytes().await.unwrap(); | |||||
let body = String::from_utf8(body_bytes.to_vec()).unwrap(); | |||||
let reg_opt = serde_json::from_str(body.as_str()); | |||||
if reg_opt.is_err() { | |||||
error!("handle_register: error registering with bubble, error parsing response: {}", body); | |||||
Ok(warp::reply::with_status( | |||||
"error registering with bubble, error parsing response", | |||||
http::StatusCode::PRECONDITION_FAILED, | |||||
)) | |||||
} else { | |||||
let reg_response: BubbleRegistrationResponse = reg_opt.unwrap(); | |||||
info!("handle_register: parsed response object: {:?}", reg_response); | |||||
let port_opt = reg_response.port; | |||||
if port_opt.is_none() { | |||||
error!("handle_register: error registering with bubble, response did not include a port"); | |||||
Ok(warp::reply::with_status( | |||||
"error registering with bubble, response did not include a port", | |||||
http::StatusCode::PRECONDITION_FAILED, | |||||
)) | |||||
} else { | |||||
let port = port_opt.unwrap(); | |||||
info!("handle_register: received port: {}", port); | |||||
// todo: start or restart ssh service | |||||
Ok(warp::reply::with_status( | |||||
"successfully registered with bubble", | |||||
http::StatusCode::OK, | |||||
)) | |||||
} | |||||
} | |||||
}, | }, | ||||
_ => { | _ => { | ||||
error!("handle_register: error registering with bubble: {:?}", response.status()); | |||||
let status_code = &response.status(); | |||||
let body_bytes = &response.bytes().await.unwrap(); | |||||
let body = String::from_utf8(body_bytes.to_vec()).unwrap(); | |||||
error!("handle_register: error registering with bubble: {:?}: {}", status_code, body); | |||||
Ok(warp::reply::with_status( | Ok(warp::reply::with_status( | ||||
"error registering with bubble", | "error registering with bubble", | ||||
http::StatusCode::PRECONDITION_FAILED, | http::StatusCode::PRECONDITION_FAILED, | ||||
@@ -1,4 +1,4 @@ | |||||
#![deny(warnings)] | |||||
//#![deny(warnings)] | |||||
/** | /** | ||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | * Copyright (c) 2020 Bubble, Inc. All rights reserved. | ||||
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | * For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | ||||
@@ -1,4 +1,4 @@ | |||||
#![deny(warnings)] | |||||
//#![deny(warnings)] | |||||
/** | /** | ||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | * Copyright (c) 2020 Bubble, Inc. All rights reserved. | ||||
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | * For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | ||||
@@ -14,7 +14,6 @@ extern crate stderrlog; | |||||
extern crate rand; | extern crate rand; | ||||
use std::fs; | |||||
use std::path::Path; | use std::path::Path; | ||||
use std::process::exit; | use std::process::exit; | ||||
use std::sync::Arc; | use std::sync::Arc; | ||||
@@ -34,6 +33,8 @@ use bubble_flexrouter::net::is_private_ip; | |||||
use bubble_flexrouter::pass::init_password; | use bubble_flexrouter::pass::init_password; | ||||
use bubble_flexrouter::proxy::start_proxy; | use bubble_flexrouter::proxy::start_proxy; | ||||
use bubble_flexrouter::util::read_required_env_var_argument; | use bubble_flexrouter::util::read_required_env_var_argument; | ||||
use bubble_flexrouter::util::read_required_env_var_argument_as_file; | |||||
use bubble_flexrouter::util::read_path_to_string; | |||||
const MIN_TOKEN_CHARS: usize = 50; | const MIN_TOKEN_CHARS: usize = 50; | ||||
const MAX_TOKEN_CHARS: usize = 100; | const MAX_TOKEN_CHARS: usize = 100; | ||||
@@ -98,6 +99,13 @@ async fn main() { | |||||
.help("environment variable naming the file that contains the bubble token") | .help("environment variable naming the file that contains the bubble token") | ||||
.default_value("BUBBLE_FR_TOKEN") | .default_value("BUBBLE_FR_TOKEN") | ||||
.takes_value(true)) | .takes_value(true)) | ||||
.arg(Arg::with_name("ssh_key_file") | |||||
.short("s") | |||||
.long("ssh-key-file") | |||||
.value_name("ENV_VAR_NAME") | |||||
.help("environment variable naming the file that contains the SSH key") | |||||
.default_value("BUBBLE_FR_SSH_KEY") | |||||
.takes_value(true)) | |||||
.arg(Arg::with_name("log_level") | .arg(Arg::with_name("log_level") | ||||
.short("v") | .short("v") | ||||
.long("log-level") | .long("log-level") | ||||
@@ -163,35 +171,29 @@ async fn main() { | |||||
let dns1_ip = args.value_of("dns1").unwrap(); | let dns1_ip = args.value_of("dns1").unwrap(); | ||||
let dns2_ip = args.value_of("dns2").unwrap(); | let dns2_ip = args.value_of("dns2").unwrap(); | ||||
let proxy_port = args.value_of("proxy_port").unwrap().parse::<u16>().unwrap(); | let proxy_port = args.value_of("proxy_port").unwrap().parse::<u16>().unwrap(); | ||||
let proxy_ip = proxy_bind_addr.unwrap().ip(); | |||||
let token_file_env_var_opt = args.value_of("token_file"); | |||||
let token_path_string = read_required_env_var_argument("token-file", token_file_env_var_opt); | |||||
let token_path = token_path_string.as_str(); | |||||
let token_file_path = Path::new(token_path); | |||||
if !token_file_path.exists() { | |||||
error!("main: token file does not exist: {}", token_path); | |||||
let ssh_key_file_env_var_opt = args.value_of("ssh_key_file"); | |||||
let ssh_key_path_path_string = read_required_env_var_argument("ssh-key-file", ssh_key_file_env_var_opt); | |||||
let ssh_key_path = Path::new(ssh_key_path_path_string.as_str()); | |||||
if !ssh_key_path.exists() { | |||||
error!("read_required_env_var_argument_as_path: file does not exist: {}", ssh_key_path.to_str().unwrap()); | |||||
exit(2); | exit(2); | ||||
} | } | ||||
let auth_token_result = fs::read_to_string(&token_file_path); | |||||
if auth_token_result.is_err() { | |||||
let err = auth_token_result.err(); | |||||
if err.is_none() { | |||||
error!("main: error reading token file {}", token_path); | |||||
} else { | |||||
error!("main: error reading token file {}: {:?}", token_path, err.unwrap()); | |||||
} | |||||
exit(2); | |||||
} | |||||
let auth_token_string = auth_token_result.unwrap(); | |||||
let ssh_priv_key = Arc::new(read_path_to_string(ssh_key_path)); | |||||
let ssh_pub_key_path_name = format!("{}.pub", ssh_key_path.to_str().unwrap()); | |||||
let ssh_pub_key_path = Path::new(ssh_pub_key_path_name.as_str()); | |||||
let ssh_pub_key = Arc::new(read_path_to_string(ssh_pub_key_path)); | |||||
let token_file_env_var_opt = args.value_of("token_file"); | |||||
let auth_token_string = read_required_env_var_argument_as_file("token-file", token_file_env_var_opt); | |||||
let auth_token_val = auth_token_string.trim(); | let auth_token_val = auth_token_string.trim(); | ||||
if auth_token_val.len() < MIN_TOKEN_CHARS { | if auth_token_val.len() < MIN_TOKEN_CHARS { | ||||
error!("main: auth token in token file {} is too short, must be at least {} chars", token_path, MIN_TOKEN_CHARS); | |||||
error!("main: auth token in token file is too short, must be at least {} chars", MIN_TOKEN_CHARS); | |||||
exit(2); | exit(2); | ||||
} | } | ||||
if auth_token_val.len() > MAX_TOKEN_CHARS { | if auth_token_val.len() > MAX_TOKEN_CHARS { | ||||
error!("main: auth token in token file {} is too long, must be at most {} chars", token_path, MAX_TOKEN_CHARS); | |||||
error!("main: auth token in token file is too long, must be at most {} chars", MAX_TOKEN_CHARS); | |||||
exit(2); | exit(2); | ||||
} | } | ||||
let auth_token = Arc::new(String::from(auth_token_val)); | let auth_token = Arc::new(String::from(auth_token_val)); | ||||
@@ -201,12 +203,13 @@ async fn main() { | |||||
proxy_ip.to_string(), | proxy_ip.to_string(), | ||||
proxy_port, | proxy_port, | ||||
password_hash, | password_hash, | ||||
auth_token.clone() | |||||
auth_token.clone(), | |||||
ssh_priv_key.clone(), | |||||
ssh_pub_key.clone() | |||||
); | ); | ||||
let proxy = start_proxy( | let proxy = start_proxy( | ||||
dns1_ip, | dns1_ip, | ||||
dns2_ip, | dns2_ip, | ||||
proxy_ip, | |||||
proxy_port, | proxy_port, | ||||
auth_token.clone() | auth_token.clone() | ||||
); | ); | ||||
@@ -42,7 +42,6 @@ type HttpClient = Client<hyper_tls::HttpsConnector<HttpConnector<CacheResolver>> | |||||
pub async fn start_proxy (dns1_ip : &str, | pub async fn start_proxy (dns1_ip : &str, | ||||
dns2_ip: &str, | dns2_ip: &str, | ||||
proxy_ip: IpAddr, | |||||
proxy_port: u16, | proxy_port: u16, | ||||
auth_token : Arc<String>) { | auth_token : Arc<String>) { | ||||
let dns1_sock : SocketAddr = format!("{}:53", dns1_ip).parse().unwrap(); | let dns1_sock : SocketAddr = format!("{}:53", dns1_ip).parse().unwrap(); | ||||
@@ -57,7 +56,8 @@ pub async fn start_proxy (dns1_ip : &str, | |||||
let client: HttpClient = Client::builder().build(https); | let client: HttpClient = Client::builder().build(https); | ||||
let gateway = Arc::new(ip_gateway()); | let gateway = Arc::new(ip_gateway()); | ||||
let addr = SocketAddr::from((proxy_ip, proxy_port)); | |||||
let proxy_local_ip : IpAddr = "127.0.0.1".parse().unwrap(); | |||||
let addr = SocketAddr::from((proxy_local_ip, proxy_port)); | |||||
let make_service = make_service_fn(move |_| { | let make_service = make_service_fn(move |_| { | ||||
let client = client.clone(); | let client = client.clone(); | ||||
@@ -5,13 +5,15 @@ | |||||
*/ | */ | ||||
use std::env; | use std::env; | ||||
use std::fs; | |||||
use std::path::Path; | |||||
use std::process::exit; | use std::process::exit; | ||||
use log::error; | use log::error; | ||||
pub fn read_required_env_var_argument(arg_name : &str, opt : Option<&str>) -> String { | pub fn read_required_env_var_argument(arg_name : &str, opt : Option<&str>) -> String { | ||||
if opt.is_none() { | if opt.is_none() { | ||||
error!("main: {} argument is required", arg_name); | |||||
error!("read_required_env_var_argument: {} argument is required", arg_name); | |||||
exit(2); | exit(2); | ||||
} | } | ||||
let opt_value = opt.unwrap(); | let opt_value = opt.unwrap(); | ||||
@@ -19,11 +21,36 @@ pub fn read_required_env_var_argument(arg_name : &str, opt : Option<&str>) -> St | |||||
if opt_opt.is_err() { | if opt_opt.is_err() { | ||||
let err = opt_opt.err(); | let err = opt_opt.err(); | ||||
if err.is_none() { | if err.is_none() { | ||||
error!("main: {} argument was invalid: {}", arg_name, opt_value); | |||||
error!("read_required_env_var_argument: {} argument was invalid: {}", arg_name, opt_value); | |||||
} else { | } else { | ||||
error!("main: {} argument was invalid: {}: {:?}", arg_name, opt_value, err); | |||||
error!("read_required_env_var_argument: {} argument was invalid: {}: {:?}", arg_name, opt_value, err); | |||||
} | } | ||||
exit(2); | exit(2); | ||||
} | } | ||||
opt_opt.unwrap() | opt_opt.unwrap() | ||||
} | } | ||||
pub fn read_required_env_var_argument_as_file(arg_name : &str, opt : Option<&str>) -> String { | |||||
let path_string = read_required_env_var_argument(arg_name, opt); | |||||
let file_path = Path::new(path_string.as_str()); | |||||
if !file_path.exists() { | |||||
error!("read_required_env_var_argument_as_path: file does not exist: {}", file_path.to_str().unwrap()); | |||||
exit(2); | |||||
} | |||||
read_path_to_string(file_path) | |||||
} | |||||
pub fn read_path_to_string(path: &Path) -> String { | |||||
let read_result = fs::read_to_string(path); | |||||
if read_result.is_err() { | |||||
let err = read_result.err(); | |||||
let path_string = path.to_str().unwrap(); | |||||
if err.is_none() { | |||||
error!("read_required_env_var_argument_as_file: error reading file {}", path_string); | |||||
} else { | |||||
error!("read_required_env_var_argument_as_file: error reading file {}: {:?}", path_string, err.unwrap()); | |||||
} | |||||
exit(2); | |||||
} | |||||
read_result.unwrap() | |||||
} |