@@ -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<u16> | |||
} | |||
pub async fn start_admin (admin_port : u16, | |||
proxy_ip : String, | |||
proxy_port : u16, | |||
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 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<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); | |||
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, | |||
@@ -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/ | |||
@@ -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::<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); | |||
} | |||
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() | |||
); | |||
@@ -42,7 +42,6 @@ type HttpClient = Client<hyper_tls::HttpsConnector<HttpConnector<CacheResolver>> | |||
pub async fn start_proxy (dns1_ip : &str, | |||
dns2_ip: &str, | |||
proxy_ip: IpAddr, | |||
proxy_port: u16, | |||
auth_token : Arc<String>) { | |||
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(); | |||
@@ -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() | |||
} |