소스 검색

allow check ssh interval to be configurable. use constants for arg names.

master
Jonathan Cobb 4 년 전
부모
커밋
e42fdd1188
3개의 변경된 파일70개의 추가작업 그리고 29개의 파일을 삭제
  1. +7
    -3
      src/admin.rs
  2. +57
    -21
      src/main.rs
  3. +6
    -5
      src/ssh.rs

+ 7
- 3
src/admin.rs 파일 보기

@@ -93,7 +93,8 @@ pub async fn start_admin (admin_reg : Arc<Mutex<Option<AdminRegistration>>>,
password_hash: String, password_hash: String,
auth_token : Arc<String>, auth_token : Arc<String>,
ssh_priv_key : Arc<String>, ssh_priv_key : Arc<String>,
ssh_pub_key : Arc<String>) {
ssh_pub_key : Arc<String>,
check_ssh_interval : u64) {
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 ssh_container: Arc<Mutex<SshContainer>> = Arc::new(Mutex::new(SshContainer::new())); let ssh_container: Arc<Mutex<SshContainer>> = Arc::new(Mutex::new(SshContainer::new()));


@@ -110,6 +111,7 @@ pub async fn start_admin (admin_reg : Arc<Mutex<Option<AdminRegistration>>>,
.and(warp::any().map(move || ssh_priv_key.clone())) .and(warp::any().map(move || ssh_priv_key.clone()))
.and(warp::any().map(move || ssh_pub_key.clone())) .and(warp::any().map(move || ssh_pub_key.clone()))
.and(warp::any().map(move || ssh_container_clone.clone())) .and(warp::any().map(move || ssh_container_clone.clone()))
.and(warp::any().map(move || check_ssh_interval))
.and_then(handle_register)); .and_then(handle_register));


let admin_reg_clone = admin_reg.clone(); let admin_reg_clone = admin_reg.clone();
@@ -137,7 +139,8 @@ async fn handle_register(registration : AdminRegistration,
auth_token : Arc<String>, auth_token : Arc<String>,
ssh_priv_key : Arc<String>, ssh_priv_key : Arc<String>,
ssh_pub_key : Arc<String>, ssh_pub_key : Arc<String>,
ssh_container : Arc<Mutex<SshContainer>>) -> Result<impl warp::Reply, warp::Rejection> {
ssh_container : Arc<Mutex<SshContainer>>,
check_ssh_interval : u64) -> Result<impl warp::Reply, warp::Rejection> {
// validate registration // validate registration
let validated = validate_admin_registration(registration.clone()); let validated = validate_admin_registration(registration.clone());
if validated.is_err() { if validated.is_err() {
@@ -221,7 +224,8 @@ async fn handle_register(registration : AdminRegistration,
internal_reg.bubble.clone(), internal_reg.bubble.clone(),
internal_reg.session.clone(), internal_reg.session.clone(),
reg_response.host_key, reg_response.host_key,
ssh_priv_key).await;
ssh_priv_key,
check_ssh_interval).await;
if ssh_result.is_err() { if ssh_result.is_err() {
let err = ssh_result.err(); let err = ssh_result.err();
if err.is_none() { if err.is_none() {


+ 57
- 21
src/main.rs 파일 보기

@@ -14,6 +14,7 @@ extern crate stderrlog;


extern crate rand; extern crate rand;


use std::num::ParseIntError;
use std::path::Path; use std::path::Path;
use std::process::exit; use std::process::exit;
use std::sync::Arc; use std::sync::Arc;
@@ -36,71 +37,92 @@ use bubble_flexrouter::util::read_required_env_var_argument_as_file;
use bubble_flexrouter::util::read_path_to_string; use bubble_flexrouter::util::read_path_to_string;
use bubble_flexrouter::version::VERSION; use bubble_flexrouter::version::VERSION;


const MIN_TOKEN_CHARS: usize = 50;
const MAX_TOKEN_CHARS: usize = 100;
const MIN_TOKEN_CHARS : usize = 50;
const MAX_TOKEN_CHARS : usize = 100;
const DEFAULT_CHECK_SSH_INTERVAL : u64 = 10;
const ARG_DNS1 : &'static str = "dns1";
const ARG_DNS2 : &'static str = "dns2";
const ARG_PROXY_PORT : &'static str = "proxy_port";
const ARG_ADMIN_PORT : &'static str = "admin_port";
const ARG_PASSWORD_FILE : &'static str = "password_file";
const ARG_PASSWORD_ENV_VAR : &'static str = "password_env_var";
const ARG_TOKEN_FILE : &'static str = "token_file";
const ARG_SSH_KEY_FILE : &'static str = "ssh_key_file";
const ARG_CHECK_SSH_INTERVAL : &'static str = "check_ssh_interval";
const ARG_LOG_LEVEL : &'static str = "log_level";


#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let default_check_ssh_interval_string = DEFAULT_CHECK_SSH_INTERVAL.to_string();
let default_check_ssh_interval = default_check_ssh_interval_string.as_str();

let args : ArgMatches = App::new("bubble-flexrouter") let args : ArgMatches = App::new("bubble-flexrouter")
.version(VERSION) .version(VERSION)
.author("Jonathan Cobb <jonathan@getbubblenow.com>") .author("Jonathan Cobb <jonathan@getbubblenow.com>")
.about("Proxy services for Bubble nodes") .about("Proxy services for Bubble nodes")
.arg(Arg::with_name("dns1")
.arg(Arg::with_name(ARG_DNS1)
.short("d") .short("d")
.long("dns1") .long("dns1")
.value_name("IP_ADDRESS") .value_name("IP_ADDRESS")
.help("Primary DNS server") .help("Primary DNS server")
.default_value("1.1.1.1") .default_value("1.1.1.1")
.takes_value(true)) .takes_value(true))
.arg(Arg::with_name("dns2")
.arg(Arg::with_name(ARG_DNS2)
.short("e") .short("e")
.long("dns2") .long("dns2")
.value_name("IP_ADDRESS") .value_name("IP_ADDRESS")
.help("Secondary DNS server") .help("Secondary DNS server")
.default_value("1.0.0.1") .default_value("1.0.0.1")
.takes_value(true)) .takes_value(true))
.arg(Arg::with_name("proxy_port")
.arg(Arg::with_name(ARG_PROXY_PORT)
.short("p") .short("p")
.long("proxy-port") .long("proxy-port")
.value_name("PORT") .value_name("PORT")
.help("port to listen for proxy connections") .help("port to listen for proxy connections")
.default_value("9823") .default_value("9823")
.takes_value(true)) .takes_value(true))
.arg(Arg::with_name("admin_port")
.arg(Arg::with_name(ARG_ADMIN_PORT)
.short("a") .short("a")
.long("admin-port") .long("admin-port")
.value_name("PORT") .value_name("PORT")
.help("port to listen for admin connections") .help("port to listen for admin connections")
.default_value("9833") .default_value("9833")
.takes_value(true)) .takes_value(true))
.arg(Arg::with_name("password_file")
.arg(Arg::with_name(ARG_PASSWORD_FILE)
.short("w") .short("w")
.long("password-file") .long("password-file")
.value_name("ENV_VAR_NAME") .value_name("ENV_VAR_NAME")
.help("environment variable naming the file that contains bcrypt-hashed password required for admin commands") .help("environment variable naming the file that contains bcrypt-hashed password required for admin commands")
.default_value("BUBBLE_FR_PASS") .default_value("BUBBLE_FR_PASS")
.takes_value(true)) .takes_value(true))
.arg(Arg::with_name("password_env_var")
.arg(Arg::with_name(ARG_PASSWORD_ENV_VAR)
.short("W") .short("W")
.long("password-env-var") .long("password-env-var")
.value_name("ENV_VAR_NAME") .value_name("ENV_VAR_NAME")
.help("environment variable containing the admin password. overwrites previous value") .help("environment variable containing the admin password. overwrites previous value")
.takes_value(true)) .takes_value(true))
.arg(Arg::with_name("token_file")
.arg(Arg::with_name(ARG_TOKEN_FILE)
.short("t") .short("t")
.long("token-file") .long("token-file")
.value_name("ENV_VAR_NAME") .value_name("ENV_VAR_NAME")
.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")
.arg(Arg::with_name(ARG_SSH_KEY_FILE)
.short("s") .short("s")
.long("ssh-key-file") .long("ssh-key-file")
.value_name("ENV_VAR_NAME") .value_name("ENV_VAR_NAME")
.help("environment variable naming the file that contains the SSH key") .help("environment variable naming the file that contains the SSH key")
.default_value("BUBBLE_FR_SSH_KEY") .default_value("BUBBLE_FR_SSH_KEY")
.takes_value(true)) .takes_value(true))
.arg(Arg::with_name("log_level")
.arg(Arg::with_name(ARG_CHECK_SSH_INTERVAL)
.short("c")
.long("check-ssh-interval")
.value_name("SECONDS")
.help("how often to verify that the SSH tunnel is OK")
.default_value(default_check_ssh_interval)
.takes_value(true))
.arg(Arg::with_name(ARG_LOG_LEVEL)
.short("v") .short("v")
.long("log-level") .long("log-level")
.value_name("LOG_LEVEL") .value_name("LOG_LEVEL")
@@ -109,7 +131,7 @@ async fn main() {
.takes_value(true)) .takes_value(true))
.get_matches(); .get_matches();


let (verbosity, quiet) = match args.value_of("log_level").unwrap().to_ascii_lowercase().as_str() {
let (verbosity, quiet) = match args.value_of(ARG_LOG_LEVEL).unwrap().to_ascii_lowercase().as_str() {
"off" => (0, true), "off" => (0, true),
"error" => (0, false), "error" => (0, false),
"warn" => (1, false), "warn" => (1, false),
@@ -130,18 +152,18 @@ async fn main() {
// todo: ensure we are running as root (or Administrator on Windows) // todo: ensure we are running as root (or Administrator on Windows)
info!("The current user is {}", whoami::username()); info!("The current user is {}", whoami::username());


let password_file_env_var_opt = args.value_of("password_file");
let password_file_env_var_opt = args.value_of(ARG_PASSWORD_FILE);
let password_file = read_required_env_var_argument("password-file", password_file_env_var_opt); let password_file = read_required_env_var_argument("password-file", password_file_env_var_opt);


let password_opt = args.value_of("password_env_var");
let password_opt = args.value_of(ARG_PASSWORD_ENV_VAR);
let password_hash = init_password(password_file.as_str(), password_opt); let password_hash = init_password(password_file.as_str(), password_opt);


let admin_port = args.value_of("admin_port").unwrap().parse::<u16>().unwrap();
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 admin_port = args.value_of(ARG_ADMIN_PORT).unwrap().parse::<u16>().unwrap();
let dns1_ip = args.value_of(ARG_DNS1).unwrap();
let dns2_ip = args.value_of(ARG_DNS2).unwrap();
let proxy_port = args.value_of(ARG_PROXY_PORT).unwrap().parse::<u16>().unwrap();


let ssh_key_file_env_var_opt = args.value_of("ssh_key_file");
let ssh_key_file_env_var_opt = args.value_of(ARG_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_string = read_required_env_var_argument("ssh-key-file", ssh_key_file_env_var_opt);
let ssh_priv_key = Arc::new(ssh_key_path_path_string); let ssh_priv_key = Arc::new(ssh_key_path_path_string);
let ssh_priv_clone = ssh_priv_key.clone(); let ssh_priv_clone = ssh_priv_key.clone();
@@ -155,7 +177,7 @@ async fn main() {
let ssh_pub_key_path = Path::new(ssh_pub_key_path_name.as_str()); 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 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 token_file_env_var_opt = args.value_of(ARG_TOKEN_FILE);
let auth_token_string = read_required_env_var_argument_as_file("token-file", token_file_env_var_opt); 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 {
@@ -168,6 +190,19 @@ async fn main() {
} }
let auth_token = Arc::new(String::from(auth_token_val)); let auth_token = Arc::new(String::from(auth_token_val));


let check_ssh_interval_opt = args.value_of(ARG_CHECK_SSH_INTERVAL);
if check_ssh_interval_opt.is_none() {
error!("main: check ssh interval was not set");
exit(2);
}
let check_ssh_interval_val = check_ssh_interval_opt.unwrap();
let check_ssh_interval_result: Result<u64, ParseIntError> = check_ssh_interval_val.trim().parse();
if check_ssh_interval_result.is_err() {
error!("main: check ssh interval was not a valid integer: {}", check_ssh_interval_val);
exit(2);
}
let check_ssh_interval = check_ssh_interval_result.unwrap();

let admin_reg: Arc<Mutex<Option<AdminRegistration>>> = Arc::new(Mutex::new(None)); let admin_reg: Arc<Mutex<Option<AdminRegistration>>> = Arc::new(Mutex::new(None));


let admin = start_admin( let admin = start_admin(
@@ -177,7 +212,8 @@ async fn main() {
password_hash, password_hash,
auth_token.clone(), auth_token.clone(),
ssh_priv_key.clone(), ssh_priv_key.clone(),
ssh_pub_key.clone()
ssh_pub_key.clone(),
check_ssh_interval
); );
let proxy = start_proxy( let proxy = start_proxy(
dns1_ip, dns1_ip,


+ 6
- 5
src/ssh.rs 파일 보기

@@ -79,7 +79,8 @@ pub async fn spawn_ssh (ssh_container : Arc<Mutex<SshContainer>>,
bubble : Arc<String>, bubble : Arc<String>,
session : Arc<String>, session : Arc<String>,
host_key : String, host_key : String,
priv_key : Arc<String>) -> Result<bool, Option<Error>> {
priv_key : Arc<String>,
check_ssh_interval : u64) -> Result<bool, Option<Error>> {
let mut guard = ssh_container.lock().await; let mut guard = ssh_container.lock().await;
if (*guard).child.is_some() { if (*guard).child.is_some() {
info!("spawn_ssh: ssh tunnel exists, not respawning"); info!("spawn_ssh: ssh tunnel exists, not respawning");
@@ -120,7 +121,7 @@ pub async fn spawn_ssh (ssh_container : Arc<Mutex<SshContainer>>,
let check_ip = ip.clone(); let check_ip = ip.clone();
let check_session = session.clone(); let check_session = session.clone();
trace!("spawn_ssh: starting abortable checker"); trace!("spawn_ssh: starting abortable checker");
let task = tokio::spawn(check_ssh(ssh_container.clone(), check_host, check_ip, check_session));
let task = tokio::spawn(check_ssh(ssh_container.clone(), check_host, check_ip, check_session, check_ssh_interval));
let (_fut, abort_handle) = abortable(task); let (_fut, abort_handle) = abortable(task);
(*guard).checker_abort_handle = Some(Arc::new(Mutex::new(abort_handle))); (*guard).checker_abort_handle = Some(Arc::new(Mutex::new(abort_handle)));
Ok(true) Ok(true)
@@ -218,15 +219,15 @@ fn build_ssh_command<'a>(command : &mut Command, tunnel: String, target: String,
} }


const CHECK_SSH_START_DELAY : u64 = 10; const CHECK_SSH_START_DELAY : u64 = 10;
const CHECK_SSH_INTERVAL: u64 = 10;
const MAX_CHECK_ERRORS_BEFORE_RESTART : u8 = 3; const MAX_CHECK_ERRORS_BEFORE_RESTART : u8 = 3;
const CHECK_SSH_HTTP_TIMEOUT: u64 = 10; const CHECK_SSH_HTTP_TIMEOUT: u64 = 10;


async fn check_ssh (ssh_container : Arc<Mutex<SshContainer>>, async fn check_ssh (ssh_container : Arc<Mutex<SshContainer>>,
bubble : Arc<String>, bubble : Arc<String>,
ip : Arc<String>, ip : Arc<String>,
session : Arc<String>) -> bool {
let mut checker = interval_at(Instant::now().checked_add(Duration::new(CHECK_SSH_START_DELAY, 0)).unwrap(), Duration::new(CHECK_SSH_INTERVAL, 0));
session : Arc<String>,
check_ssh_interval : u64) -> bool {
let mut checker = interval_at(Instant::now().checked_add(Duration::new(CHECK_SSH_START_DELAY, 0)).unwrap(), Duration::new(check_ssh_interval, 0));
let check_url = format!("https://{}/api/me/flexRouters/{}/status", bubble.clone(), ip.clone()); let check_url = format!("https://{}/api/me/flexRouters/{}/status", bubble.clone(), ip.clone());
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(HEADER_BUBBLE_SESSION, HeaderValue::from_str(session.to_string().as_str()).unwrap()); headers.insert(HEADER_BUBBLE_SESSION, HeaderValue::from_str(session.to_string().as_str()).unwrap());


불러오는 중...
취소
저장