diff --git a/src/admin.rs b/src/admin.rs index db4cfd0..1f269e0 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,4 +1,4 @@ -#![deny(warnings)] +//#![deny(warnings)] /** * Copyright (c) 2020 Bubble, Inc. All rights reserved. * For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ @@ -28,16 +28,23 @@ struct AdminRegistration { #[derive(Debug, Deserialize, Serialize, Clone)] struct BubbleRegistration { + key: String, ip: String, - proxy_port: u16, auth_token: String } +#[derive(Debug, Deserialize, Serialize, Clone)] +struct BubbleRegistrationResponse { + port: Option +} + pub async fn start_admin (admin_port : u16, proxy_ip : String, proxy_port : u16, password_hash: String, - auth_token : Arc) { + auth_token : Arc, + ssh_priv_key : Arc, + ssh_pub_key : Arc) { let admin_sock: SocketAddr = format!("127.0.0.1:{}", admin_port).parse().unwrap(); 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 || password_hash.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); let routes = warp::post().and(register); @@ -62,7 +71,9 @@ async fn handle_register(registration : AdminRegistration, proxy_ip: String, proxy_port : u16, hashed_password : String, - auth_token : Arc) -> Result { + auth_token : Arc, + ssh_priv_key : Arc, + ssh_pub_key : Arc) -> Result { let pass_result = is_correct_password(registration.password, hashed_password); if pass_result.is_err() { error!("handle_register: error verifying password: {:?}", pass_result.err()); @@ -80,8 +91,8 @@ async fn handle_register(registration : AdminRegistration, // create the registration object let bubble_registration = BubbleRegistration { + key: ssh_pub_key.to_string(), ip: proxy_ip, - proxy_port, auth_token: auth_token.to_string() }; @@ -97,13 +108,41 @@ async fn handle_register(registration : AdminRegistration, match response.status() { ReqwestStatusCode::OK => { 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( "error registering with bubble", http::StatusCode::PRECONDITION_FAILED, diff --git a/src/lib.rs b/src/lib.rs index d5f1122..e042e48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![deny(warnings)] +//#![deny(warnings)] /** * Copyright (c) 2020 Bubble, Inc. All rights reserved. * For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ diff --git a/src/main.rs b/src/main.rs index 2cf0c45..2c3e960 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#![deny(warnings)] +//#![deny(warnings)] /** * Copyright (c) 2020 Bubble, Inc. All rights reserved. * For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ @@ -14,7 +14,6 @@ extern crate stderrlog; extern crate rand; -use std::fs; use std::path::Path; use std::process::exit; use std::sync::Arc; @@ -34,6 +33,8 @@ use bubble_flexrouter::net::is_private_ip; use bubble_flexrouter::pass::init_password; use bubble_flexrouter::proxy::start_proxy; 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 MAX_TOKEN_CHARS: usize = 100; @@ -98,6 +99,13 @@ async fn main() { .help("environment variable naming the file that contains the bubble token") .default_value("BUBBLE_FR_TOKEN") .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") .short("v") .long("log-level") @@ -163,35 +171,29 @@ async fn main() { let dns1_ip = args.value_of("dns1").unwrap(); let dns2_ip = args.value_of("dns2").unwrap(); let proxy_port = args.value_of("proxy_port").unwrap().parse::().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); } - 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(); 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); } 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); } let auth_token = Arc::new(String::from(auth_token_val)); @@ -201,12 +203,13 @@ async fn main() { proxy_ip.to_string(), proxy_port, password_hash, - auth_token.clone() + auth_token.clone(), + ssh_priv_key.clone(), + ssh_pub_key.clone() ); let proxy = start_proxy( dns1_ip, dns2_ip, - proxy_ip, proxy_port, auth_token.clone() ); diff --git a/src/proxy.rs b/src/proxy.rs index 6c093ba..4db8fa6 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -42,7 +42,6 @@ type HttpClient = Client> pub async fn start_proxy (dns1_ip : &str, dns2_ip: &str, - proxy_ip: IpAddr, proxy_port: u16, auth_token : Arc) { 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 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 client = client.clone(); diff --git a/src/util.rs b/src/util.rs index 9e3524b..5474bd9 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,13 +5,15 @@ */ use std::env; +use std::fs; +use std::path::Path; use std::process::exit; use log::error; pub fn read_required_env_var_argument(arg_name : &str, opt : Option<&str>) -> String { if opt.is_none() { - error!("main: {} argument is required", arg_name); + error!("read_required_env_var_argument: {} argument is required", arg_name); exit(2); } 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() { let err = opt_opt.err(); 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 { - 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); } 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() +}