feat: implement HTTPS mTLS tunnel with auth handshake
Add HTTPS default tunnel endpoint and outbound connector using Rustls with mTLS and an additional auth/session token. - Add rustls, tokio-rustls, hyper, and related dependencies - Implement TLS config builders (server/client) with cert validation - Implement HTTPS tunnel listener with mTLS accept and auth handshake - Implement HTTPS tunnel connector with server cert validation - Add auth token loading from CLI value or file - Add security gate state machine for tracking auth progression - Fail closed for missing/invalid TLS config, cert mismatch, auth failures - Port conflicts fail without fallback - Reconnect revalidates all security gates - 67 tests covering all validation assertions
This commit is contained in:
+89
-16
@@ -1,13 +1,20 @@
|
||||
mod cli;
|
||||
mod config;
|
||||
mod errors;
|
||||
mod generate;
|
||||
mod redact;
|
||||
mod tls;
|
||||
mod tunnel;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::cli::Cli;
|
||||
use crate::tunnel::{ConnectorConfig, ListenerConfig};
|
||||
|
||||
fn main() {
|
||||
// Initialize tracing/logger — secrets are never printed by design
|
||||
@@ -28,7 +35,8 @@ fn main() {
|
||||
key,
|
||||
ca_cert,
|
||||
auth_token,
|
||||
} => run_listen(listen, cert, key, ca_cert, auth_token),
|
||||
auth_token_file,
|
||||
} => run_listen(listen, cert, key, ca_cert, auth_token, auth_token_file),
|
||||
crate::cli::Commands::Connect {
|
||||
target,
|
||||
socks,
|
||||
@@ -36,7 +44,16 @@ fn main() {
|
||||
key,
|
||||
ca_cert,
|
||||
auth_token,
|
||||
} => run_connect(target, socks, cert, key, ca_cert, auth_token),
|
||||
auth_token_file,
|
||||
} => run_connect(
|
||||
target,
|
||||
socks,
|
||||
cert,
|
||||
key,
|
||||
ca_cert,
|
||||
auth_token,
|
||||
auth_token_file,
|
||||
),
|
||||
crate::cli::Commands::Generate {
|
||||
out,
|
||||
ca_name,
|
||||
@@ -55,6 +72,7 @@ fn run_listen(
|
||||
key: String,
|
||||
ca_cert: String,
|
||||
auth_token: String,
|
||||
auth_token_file: Option<String>,
|
||||
) -> i32 {
|
||||
// Validate all inputs before opening any sockets
|
||||
if cert.is_empty() {
|
||||
@@ -69,10 +87,6 @@ fn run_listen(
|
||||
eprintln!("Error: --ca-cert is required for listen command");
|
||||
return 1;
|
||||
}
|
||||
if auth_token.is_empty() {
|
||||
eprintln!("Error: --auth-token is required for listen command");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let (host, port) = match cli::parse_host_port(&listen) {
|
||||
Ok(v) => v,
|
||||
@@ -96,6 +110,21 @@ fn run_listen(
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Load auth token from file or value
|
||||
let token_path: Option<&Path> = auth_token_file.as_deref().map(Path::new);
|
||||
let token_value: Option<&str> = if auth_token.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&auth_token)
|
||||
};
|
||||
let auth_token = match tunnel::load_auth_token(token_value, token_path) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
"Starting HTTPS tunnel listener on {}:{} (HTTPS default transport)",
|
||||
host,
|
||||
@@ -109,8 +138,24 @@ fn run_listen(
|
||||
redact::Redacted::new(&auth_token)
|
||||
);
|
||||
|
||||
// TODO: Implement HTTPS mTLS listener
|
||||
tracing::info!("Listener implementation pending (Milestone 2)");
|
||||
let bind_addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap();
|
||||
let config = ListenerConfig {
|
||||
bind_addr,
|
||||
server_cert_path: Arc::from(Path::new(&cert)),
|
||||
server_key_path: Arc::from(Path::new(&key)),
|
||||
ca_cert_path: Arc::from(Path::new(&ca_cert)),
|
||||
auth_token: Arc::new(auth_token),
|
||||
};
|
||||
|
||||
// Run the async listener
|
||||
if let Err(e) = tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(tunnel::run_listener(config))
|
||||
{
|
||||
eprintln!("Error: {}", e);
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
@@ -121,6 +166,7 @@ fn run_connect(
|
||||
key: String,
|
||||
ca_cert: String,
|
||||
auth_token: String,
|
||||
auth_token_file: Option<String>,
|
||||
) -> i32 {
|
||||
// Validate all inputs before opening any sockets
|
||||
if cert.is_empty() {
|
||||
@@ -135,12 +181,8 @@ fn run_connect(
|
||||
eprintln!("Error: --ca-cert is required for connect command");
|
||||
return 1;
|
||||
}
|
||||
if auth_token.is_empty() {
|
||||
eprintln!("Error: --auth-token is required for connect command");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let (_target_host, target_port) = match cli::parse_host_port(&target) {
|
||||
let (target_host, target_port) = match cli::parse_host_port(&target) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
@@ -170,9 +212,24 @@ fn run_connect(
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Load auth token from file or value
|
||||
let token_path: Option<&Path> = auth_token_file.as_deref().map(Path::new);
|
||||
let token_value: Option<&str> = if auth_token.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&auth_token)
|
||||
};
|
||||
let auth_token = match tunnel::load_auth_token(token_value, token_path) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
"Connecting to HTTPS tunnel at {}:{} (HTTPS default transport)",
|
||||
_target_host,
|
||||
target_host,
|
||||
target_port
|
||||
);
|
||||
tracing::info!("SOCKS5 proxy will listen on {}:{} ", socks_host, socks_port);
|
||||
@@ -184,8 +241,24 @@ fn run_connect(
|
||||
redact::Redacted::new(&auth_token)
|
||||
);
|
||||
|
||||
// TODO: Implement HTTPS mTLS connector and SOCKS5 proxy
|
||||
tracing::info!("Connector and SOCKS5 implementation pending (Milestone 2-3)");
|
||||
let target_addr: SocketAddr = format!("{}:{}", target_host, target_port).parse().unwrap();
|
||||
let config = ConnectorConfig {
|
||||
target_addr,
|
||||
client_cert_path: Arc::from(Path::new(&cert)),
|
||||
client_key_path: Arc::from(Path::new(&key)),
|
||||
ca_cert_path: Arc::from(Path::new(&ca_cert)),
|
||||
auth_token: Arc::new(auth_token),
|
||||
};
|
||||
|
||||
// Run the async connector
|
||||
if let Err(e) = tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(tunnel::connect_tunnel(config))
|
||||
{
|
||||
eprintln!("Error: {}", e);
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user