Compare commits

..

2 Commits

Author SHA1 Message Date
sam e1f3bca708 built-in https 2024-11-09 14:34:12 -07:00
sam 105deab45d use config.toml & send gui config to client 2024-11-09 14:08:16 -07:00
5 changed files with 794 additions and 291 deletions
+1
View File
@@ -2,3 +2,4 @@
cert.pem cert.pem
key.pem key.pem
bundle bundle
config.toml
Generated
+682 -249
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -8,9 +8,14 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.86" anyhow = "1.0.86"
axum = "0.7.7" axum = "0.7.7"
axum-server = { version = "0.7.1", features = ["tls-rustls"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
rustls-pemfile = "2.2.0"
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"
tokio = { version = "1.37.0", features = ["full"] } tokio = { version = "1.37.0", features = ["full"] }
tokio-rustls = "0.26.0" tokio-rustls = "0.26.0"
toml = "0.8.19"
tower = "0.5.1" tower = "0.5.1"
tower-http = { version = "0.6.1", features = ["fs"] } tower-http = { version = "0.6.1", features = ["fs"] }
tracing = { version = "0.1.40", features = ["async-await"] } tracing = { version = "0.1.40", features = ["async-await"] }
+11
View File
@@ -0,0 +1,11 @@
proxy_listen_address = "127.0.0.1:4433"
http_listen_address = "127.0.0.1:4434"
cert_path = "./cert.pem"
key_path = "./key.pem"
mumble_server_url = "voip.ohea.xyz:64738"
gui_path = "../mumble-web2/dist"
serve_https = true
[gui]
proxy_url = "https://voip2.ohea.xyz"
# cert_hash = [...]
+95 -42
View File
@@ -1,12 +1,13 @@
use anyhow::{Context, Result}; use anyhow::{anyhow, Context, Result};
use axum::extract::State; use axum::extract::State;
use axum::http::{Response, StatusCode}; use axum::http::{Response, StatusCode};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use std::env; use serde::{Deserialize, Serialize};
use std::future::IntoFuture; use std::future::IntoFuture;
use std::net::{SocketAddr, ToSocketAddrs}; use std::net::{SocketAddr, ToSocketAddrs};
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
use tokio::fs::read_to_string;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::pin; use tokio::pin;
@@ -87,48 +88,41 @@ impl ServerCertVerifier for NoCertificateVerification {
} }
} }
#[derive(Clone)] #[derive(Clone, Deserialize, Serialize)]
struct Config { struct GuiConfig {
proxy_listen_address: String, proxy_url: Option<String>,
http_listen_address: String, cert_hash: Option<Vec<u8>>,
cert_path: String,
key_path: String,
mumble_server_address: SocketAddr,
gui_path: PathBuf,
} }
impl Config { #[derive(Clone, Deserialize)]
fn get() -> Result<Config> { struct Config {
Ok(Config { proxy_listen_address: SocketAddr,
proxy_listen_address: env::var("MUMBLE_WEBTRANSPORT_PROXY_LISTEN_ADDRESS") http_listen_address: SocketAddr,
.unwrap_or("127.0.0.1:4433".to_string()), cert_path: String,
http_listen_address: env::var("MUMBLE_WEBTRANSPORT_HTTP_LISTEN_ADDRESS") key_path: String,
.unwrap_or("127.0.0.1:4434".to_string()), #[serde(default)]
cert_path: env::var("MUMBLE_WEBTRANSPORT_CERT_PATH").unwrap_or("cert.pem".to_string()), serve_https: bool,
key_path: env::var("MUMBLE_WEBTRANSPORT_KEY_PATH").unwrap_or("key.pem".to_string()), mumble_server_url: String,
mumble_server_address: env::var("MUMBLE_WEBTRANSPORT_MUMBLE_SERVER_ADDRESS") gui_path: PathBuf,
.with_context(|| "Please set MUMBLE_WEBTRANSPORT_MUMBLE_SERVER_ADDRESS")? gui: GuiConfig,
.to_socket_addrs()?
.next()
.ok_or(anyhow::anyhow!("Invalid mumble server address"))?,
gui_path: PathBuf::from(
env::var("MUMBLE_WEBTRANSPORT_GUI_PATH").unwrap_or("gui".to_string()),
),
})
}
} }
//async fn serve_index_html_with_config(State(config): State<Config>) -> impl IntoResponse { //async fn serve_index_html_with_config(State(config): State<Config>) -> impl IntoResponse {
async fn serve_index_html_with_config(State(config): State<Config>) -> impl IntoResponse { async fn serve_index_html_with_config(State(config): State<Config>) -> impl IntoResponse {
// Load the HTML file // Load the HTML file
let html = match tokio::fs::read_to_string(config.gui_path.join("index.html")).await { let html = match read_to_string(config.gui_path.join("index.html")).await {
Ok(content) => content, Ok(content) => content,
Err(_) => return (StatusCode::NOT_FOUND, "File not found").into_response(), Err(_) => return (StatusCode::NOT_FOUND, "File not found").into_response(),
}; };
// Insert the script tag with configuration // Insert the script tag with configuration
let config_script = "<script>window.config = {\"foo\": \"bar\"}</script>"; let modified_html = html.replace(
let modified_html = html.replace("</head>", &format!("{}\n</head>", config_script)); "</head>",
&format!(
"<script>window.config = {}</script>\n</head>",
serde_json::to_string(&config.gui).unwrap(),
),
);
// Create a response with the modified HTML // Create a response with the modified HTML
Response::builder() Response::builder()
@@ -139,11 +133,57 @@ async fn serve_index_html_with_config(State(config): State<Config>) -> impl Into
.into_response() .into_response()
} }
fn configure_tls(config: &Config) -> Result<rustls::ServerConfig, anyhow::Error> {
// Thanks perplexity!
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::fs::File;
use std::io::BufReader;
// Create a new ServerConfig with no client authentication
//(rustls::server::NoClientAuth::new());
// Read the certificate file
let cert_file = File::open(&config.cert_path)?;
let mut cert_reader = BufReader::new(cert_file);
let cert_chain = certs(&mut cert_reader).collect::<Result<_, _>>()?;
// Read the private key file
let key_file = File::open(&config.key_path)?;
let mut key_reader = BufReader::new(key_file);
let key = pkcs8_private_keys(&mut key_reader)
.next()
.ok_or(anyhow!("no keys in key.pem"))??;
// Set the certificate chain and private key
let config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert_chain, key.into())?;
Ok(config)
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
init_logging(); init_logging();
let proxy_config = Config::get()?; let proxy_config: Config = toml::from_str(
&read_to_string("./config.toml")
.await
.context("reading config.toml (try making a copy of config.toml.example)")?,
)?;
let mumble_server_addr = proxy_config
.mumble_server_url
.to_socket_addrs()
.context(format!(
"parsing mumble_server_url={}",
proxy_config.mumble_server_url
))?
.next()
.ok_or(anyhow!(
"no socket addrs in mumble_server_url={}",
proxy_config.mumble_server_url
))?;
// Setup HTTP Server // Setup HTTP Server
//let http = axum::Router::new().route("/", axum::routing::get(serve_gui)); //let http = axum::Router::new().route("/", axum::routing::get(serve_gui));
@@ -151,17 +191,30 @@ async fn main() -> Result<()> {
.route("/", axum::routing::get(serve_index_html_with_config)) .route("/", axum::routing::get(serve_index_html_with_config))
.fallback_service(tower_http::services::ServeDir::new(&proxy_config.gui_path)) .fallback_service(tower_http::services::ServeDir::new(&proxy_config.gui_path))
.with_state(proxy_config.clone()); .with_state(proxy_config.clone());
let listener = tokio::net::TcpListener::bind(proxy_config.http_listen_address) if proxy_config.serve_https {
.await tokio::spawn(
.unwrap(); axum_server::bind_rustls(
tokio::spawn(axum::serve(listener, app).into_future()); proxy_config.http_listen_address,
axum_server::tls_rustls::RustlsConfig::from_config(Arc::new(configure_tls(
&proxy_config,
)?)),
)
.serve(app.into_make_service())
.into_future(),
);
} else {
tokio::spawn(
axum_server::bind(proxy_config.http_listen_address)
.serve(app.into_make_service())
.into_future(),
);
}
// Setup WebTransport proxy listener // Setup WebTransport proxy listener
let identity = Identity::load_pemfiles(proxy_config.cert_path, proxy_config.key_path).await?;
let config = ServerConfig::builder() let config = ServerConfig::builder()
.with_bind_address(proxy_config.proxy_listen_address.parse()?) .with_bind_address(proxy_config.proxy_listen_address)
.with_identity( .with_identity(&identity)
&Identity::load_pemfiles(proxy_config.cert_path, proxy_config.key_path).await?,
)
.keep_alive_interval(Some(Duration::from_secs(20))) .keep_alive_interval(Some(Duration::from_secs(20)))
.build(); .build();
@@ -172,7 +225,7 @@ async fn main() -> Result<()> {
for id in 0.. { for id in 0.. {
let incoming_session = server.accept().await; let incoming_session = server.accept().await;
tokio::spawn( tokio::spawn(
handle_connection(incoming_session, id, proxy_config.mumble_server_address) handle_connection(incoming_session, id, mumble_server_addr)
.instrument(info_span!("Connection", id)), .instrument(info_span!("Connection", id)),
); );
} }