Add embedded TLS server
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,4 @@ | ||||
| target | ||||
| .idea | ||||
| *.crt | ||||
| *.key | ||||
|   | ||||
							
								
								
									
										111
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										111
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -52,6 +52,7 @@ dependencies = [ | ||||
|  "actix-codec", | ||||
|  "actix-rt", | ||||
|  "actix-service", | ||||
|  "actix-tls", | ||||
|  "actix-utils", | ||||
|  "ahash", | ||||
|  "base64", | ||||
| @@ -143,6 +144,24 @@ dependencies = [ | ||||
|  "pin-project-lite", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "actix-tls" | ||||
| version = "3.0.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" | ||||
| dependencies = [ | ||||
|  "actix-codec", | ||||
|  "actix-rt", | ||||
|  "actix-service", | ||||
|  "actix-utils", | ||||
|  "futures-core", | ||||
|  "log", | ||||
|  "pin-project-lite", | ||||
|  "tokio-rustls", | ||||
|  "tokio-util", | ||||
|  "webpki-roots", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "actix-utils" | ||||
| version = "3.0.0" | ||||
| @@ -166,6 +185,7 @@ dependencies = [ | ||||
|  "actix-rt", | ||||
|  "actix-server", | ||||
|  "actix-service", | ||||
|  "actix-tls", | ||||
|  "actix-utils", | ||||
|  "actix-web-codegen", | ||||
|  "ahash", | ||||
| @@ -1281,6 +1301,21 @@ dependencies = [ | ||||
|  "winreg", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "ring" | ||||
| version = "0.16.20" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" | ||||
| dependencies = [ | ||||
|  "cc", | ||||
|  "libc", | ||||
|  "once_cell", | ||||
|  "spin", | ||||
|  "untrusted", | ||||
|  "web-sys", | ||||
|  "winapi", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rustc_version" | ||||
| version = "0.4.0" | ||||
| @@ -1290,6 +1325,27 @@ dependencies = [ | ||||
|  "semver", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rustls" | ||||
| version = "0.20.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" | ||||
| dependencies = [ | ||||
|  "log", | ||||
|  "ring", | ||||
|  "sct", | ||||
|  "webpki", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rustls-pemfile" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" | ||||
| dependencies = [ | ||||
|  "base64", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "ryu" | ||||
| version = "1.0.11" | ||||
| @@ -1312,6 +1368,16 @@ version = "1.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" | ||||
|  | ||||
| [[package]] | ||||
| name = "sct" | ||||
| version = "0.7.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" | ||||
| dependencies = [ | ||||
|  "ring", | ||||
|  "untrusted", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "security-framework" | ||||
| version = "2.7.0" | ||||
| @@ -1440,6 +1506,12 @@ dependencies = [ | ||||
|  "winapi", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "spin" | ||||
| version = "0.5.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" | ||||
|  | ||||
| [[package]] | ||||
| name = "strsim" | ||||
| version = "0.10.0" | ||||
| @@ -1477,6 +1549,7 @@ name = "tcp_relay_server" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "actix", | ||||
|  "actix-tls", | ||||
|  "actix-web", | ||||
|  "actix-web-actors", | ||||
|  "base", | ||||
| @@ -1484,6 +1557,8 @@ dependencies = [ | ||||
|  "env_logger", | ||||
|  "futures", | ||||
|  "log", | ||||
|  "rustls", | ||||
|  "rustls-pemfile", | ||||
|  "serde", | ||||
|  "tokio", | ||||
| ] | ||||
| @@ -1612,6 +1687,17 @@ dependencies = [ | ||||
|  "tokio", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "tokio-rustls" | ||||
| version = "0.23.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" | ||||
| dependencies = [ | ||||
|  "rustls", | ||||
|  "tokio", | ||||
|  "webpki", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "tokio-tungstenite" | ||||
| version = "0.17.2" | ||||
| @@ -1717,6 +1803,12 @@ dependencies = [ | ||||
|  "tinyvec", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "untrusted" | ||||
| version = "0.7.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" | ||||
|  | ||||
| [[package]] | ||||
| name = "url" | ||||
| version = "2.2.2" | ||||
| @@ -1845,6 +1937,25 @@ dependencies = [ | ||||
|  "wasm-bindgen", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "webpki" | ||||
| version = "0.22.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" | ||||
| dependencies = [ | ||||
|  "ring", | ||||
|  "untrusted", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "webpki-roots" | ||||
| version = "0.22.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" | ||||
| dependencies = [ | ||||
|  "webpki", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "winapi" | ||||
| version = "0.3.9" | ||||
|   | ||||
| @@ -9,8 +9,11 @@ clap = { version = "3.2.18", features = ["derive", "env"] } | ||||
| log = "0.4.17" | ||||
| env_logger = "0.9.0" | ||||
| actix = "0.13.0" | ||||
| actix-web = "4" | ||||
| actix-web = { version = "4", features = ["rustls"] } | ||||
| actix-web-actors = "4.1.0" | ||||
| actix-tls = "3.0.3" | ||||
| serde = { version = "1.0.144", features = ["derive"] } | ||||
| tokio = { version = "1", features = ["full"] } | ||||
| futures = "0.3.24" | ||||
| futures = "0.3.24" | ||||
| rustls = "0.20.6" | ||||
| rustls-pemfile = "1.0.1" | ||||
| @@ -2,8 +2,9 @@ use clap::Parser; | ||||
|  | ||||
| /// TCP relay server | ||||
| #[derive(Parser, Debug, Clone)] | ||||
| #[clap(author, version, about, long_about = None)] | ||||
| pub struct Args { | ||||
| #[clap(author, version, about, | ||||
|     long_about = "TCP-over-HTTP server. This program might be configured behind a reverse-proxy.")] | ||||
| pub struct ProgramArgs { | ||||
|     /// Access tokens | ||||
|     #[clap(short, long)] | ||||
|     pub tokens: Vec<String>, | ||||
| @@ -28,4 +29,12 @@ pub struct Args { | ||||
|     /// on the same machine | ||||
|     #[clap(short, long, default_value_t = 0)] | ||||
|     pub increment_ports: u16, | ||||
|  | ||||
|     /// TLS certificate. Specify also private key to use HTTPS/TLS instead of HTTP | ||||
|     #[clap(long)] | ||||
|     pub tls_cert: Option<String>, | ||||
|  | ||||
|     /// TLS private key. Specify also certificate to use HTTPS/TLS instead of HTTP | ||||
|     #[clap(long)] | ||||
|     pub tls_key: Option<String>, | ||||
| } | ||||
| @@ -1,18 +1,22 @@ | ||||
| use std::fs::File; | ||||
| use std::io::BufReader; | ||||
| use std::sync::Arc; | ||||
|  | ||||
| use actix_web::{App, HttpRequest, HttpResponse, HttpServer, middleware, Responder, web}; | ||||
| use actix_web::web::Data; | ||||
| use clap::Parser; | ||||
| use rustls::{Certificate, PrivateKey, ServerConfig}; | ||||
| use rustls_pemfile::{certs, Item, read_one}; | ||||
|  | ||||
| use base::RelayedPort; | ||||
| use tcp_relay_server::args::Args; | ||||
| use tcp_relay_server::args::ProgramArgs; | ||||
| use tcp_relay_server::relay_ws::relay_ws; | ||||
|  | ||||
| pub async fn hello_route() -> &'static str { | ||||
|     "Hello world!" | ||||
| } | ||||
|  | ||||
| pub async fn config_route(req: HttpRequest, data: Data<Arc<Args>>) -> impl Responder { | ||||
| pub async fn config_route(req: HttpRequest, data: Data<Arc<ProgramArgs>>) -> impl Responder { | ||||
|     let token = req.headers().get("Authorization") | ||||
|         .map(|t| t.to_str().unwrap_or_default()) | ||||
|         .unwrap_or_default() | ||||
| @@ -35,13 +39,49 @@ pub async fn config_route(req: HttpRequest, data: Data<Arc<Args>>) -> impl Respo | ||||
| async fn main() -> std::io::Result<()> { | ||||
|     env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); | ||||
|  | ||||
|     let mut args: Args = Args::parse(); | ||||
|     let mut args: ProgramArgs = ProgramArgs::parse(); | ||||
|  | ||||
|     if args.ports.is_empty() { | ||||
|         log::error!("No port to forward!"); | ||||
|         std::process::exit(2); | ||||
|     } | ||||
|  | ||||
|     // Load TLS configuration, if any | ||||
|     let tls_config = if let (Some(cert), Some(key)) = (&args.tls_cert, &args.tls_key) { | ||||
|  | ||||
|         // Load TLS certificate & private key | ||||
|         let cert_file = &mut BufReader::new(File::open(cert).unwrap()); | ||||
|         let key_file = &mut BufReader::new(File::open(key).unwrap()); | ||||
|  | ||||
|         // Get certificates chain | ||||
|         let cert_chain = certs(cert_file).unwrap() | ||||
|             .into_iter() | ||||
|             .map(Certificate) | ||||
|             .collect(); | ||||
|  | ||||
|         // Get private key | ||||
|         let key = match read_one(key_file).expect("Failed to read private key!") { | ||||
|             None => { | ||||
|                 log::error!("Failed to extract private key!"); | ||||
|                 panic!(); | ||||
|             } | ||||
|             Some(Item::PKCS8Key(key)) => key, | ||||
|             Some(Item::RSAKey(key)) => key, | ||||
|             _ => { | ||||
|                 log::error!("Unsupported private key type!"); | ||||
|                 panic!(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let config = ServerConfig::builder() | ||||
|             .with_safe_defaults() | ||||
|             .with_no_client_auth() | ||||
|             .with_single_cert(cert_chain, PrivateKey(key)) | ||||
|             .expect("Failed to load TLS certificate!"); | ||||
|  | ||||
|         Some(config) | ||||
|     } else { None }; | ||||
|  | ||||
|     // Read tokens from file, if any | ||||
|     if let Some(file) = &args.tokens_file { | ||||
|         std::fs::read_to_string(file) | ||||
| @@ -60,15 +100,19 @@ async fn main() -> std::io::Result<()> { | ||||
|  | ||||
|     let args = Arc::new(args); | ||||
|     let args_clone = args.clone(); | ||||
|     HttpServer::new(move || { | ||||
|     let server = HttpServer::new(move || { | ||||
|         App::new() | ||||
|             .wrap(middleware::Logger::default()) | ||||
|             .app_data(Data::new(args_clone.clone())) | ||||
|             .route("/", web::get().to(hello_route)) | ||||
|             .route("/config", web::get().to(config_route)) | ||||
|             .route("/ws", web::get().to(relay_ws)) | ||||
|     }) | ||||
|         .bind(&args.listen_address)? | ||||
|         .run() | ||||
|     }); | ||||
|  | ||||
|     if let Some(tls_conf) = tls_config { | ||||
|         server.bind_rustls(&args.listen_address, tls_conf)? | ||||
|     } else { | ||||
|         server.bind(&args.listen_address)? | ||||
|     }.run() | ||||
|         .await | ||||
| } | ||||
| @@ -9,7 +9,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
| use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; | ||||
| use tokio::net::TcpStream; | ||||
|  | ||||
| use crate::args::Args; | ||||
| use crate::args::ProgramArgs; | ||||
|  | ||||
| /// How often heartbeat pings are sent | ||||
| const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); | ||||
| @@ -153,7 +153,7 @@ pub struct WebSocketQuery { | ||||
|  | ||||
| pub async fn relay_ws(req: HttpRequest, stream: web::Payload, | ||||
|                       query: web::Query<WebSocketQuery>, | ||||
|                       conf: web::Data<Arc<Args>>) -> Result<HttpResponse, Error> { | ||||
|                       conf: web::Data<Arc<ProgramArgs>>) -> Result<HttpResponse, Error> { | ||||
|     if !conf.tokens.contains(&query.token) { | ||||
|         log::error!("Rejected WS request from {:?} due to invalid token!", req.peer_addr()); | ||||
|         return Ok(HttpResponse::Unauthorized().json("Invalid / missing token!")); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user