From 5d33dba103dc2ae78b269d284c30ac9eac40fe39 Mon Sep 17 00:00:00 2001 From: asonix Date: Sun, 20 Nov 2022 21:42:38 -0600 Subject: [PATCH] Add support for binding TLS --- Cargo.lock | 4 ++++ Cargo.toml | 6 +++++- README.md | 6 ++++++ src/config.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- src/jobs.rs | 4 ++-- src/main.rs | 28 ++++++++++++++++++++++------ 6 files changed, 88 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04bd6b1..1944884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,7 @@ dependencies = [ "actix-codec", "actix-rt", "actix-service", + "actix-tls", "actix-utils", "ahash", "base64", @@ -192,6 +193,7 @@ dependencies = [ "actix-rt", "actix-server", "actix-service", + "actix-tls", "actix-utils", "ahash", "bytes", @@ -311,6 +313,8 @@ dependencies = [ "rsa", "rsa-magic-public-key", "ructe", + "rustls", + "rustls-pemfile", "serde", "serde_json", "sha2", diff --git a/Cargo.toml b/Cargo.toml index 1a85bad..98673e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,9 @@ default = [] [dependencies] anyhow = "1.0" actix-rt = "2.7.0" -actix-web = { version = "4.0.1", default-features = false } +actix-web = { version = "4.0.1", default-features = false, features = [ + "rustls", +] } actix-webfinger = "0.4.0" activitystreams = "0.7.0-alpha.19" activitystreams-ext = "0.1.0-alpha.2" @@ -48,6 +50,8 @@ quanta = "0.10.1" rand = "0.8" rsa = "0.7" rsa-magic-public-key = "0.6.0" +rustls = "0.20.7" +rustls-pemfile = "1.0.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sha2 = { version = "0.10", features = ["oid"] } diff --git a/README.md b/README.md index 1f08efe..ed0957b 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ API_TOKEN=somepasswordishtoken OPENTELEMETRY_URL=localhost:4317 TELEGRAM_TOKEN=secret TELEGRAM_ADMIN_HANDLE=your_handle +TLS_KEY=/path/to/key +TLS_CERT=/path/to/cert ``` #### Descriptions @@ -131,6 +133,10 @@ A URL for exporting opentelemetry spans. This is mostly useful for debugging. Th A Telegram Bot Token for running the relay administration bot. There is no default. ##### `TELEGRAM_ADMIN_HANDLE` The handle of the telegram user allowed to administer the relay. There is no default. +##### `TLS_KEY` +Optional - This is specified if you are running the relay directly on the internet and have a TLS key to provide HTTPS for your relay +##### `TLS_CERT` +Optional - This is specified if you are running the relay directly on the internet and have a TLS certificate chain to provide HTTPS for your relay ### Subscribing Mastodon admins can subscribe to this relay by adding the `/inbox` route to their relay settings. diff --git a/src/config.rs b/src/config.rs index 49c24ba..308c0bf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,8 +14,9 @@ use activitystreams::{ }; use config::Environment; use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature}; +use rustls::{Certificate, PrivateKey}; use sha2::{Digest, Sha256}; -use std::{net::IpAddr, path::PathBuf}; +use std::{io::BufReader, net::IpAddr, path::PathBuf}; use uuid::Uuid; #[derive(Clone, Debug, serde::Deserialize)] @@ -34,6 +35,8 @@ pub(crate) struct ParsedConfig { telegram_token: Option, telegram_admin_handle: Option, api_token: Option, + tls_key: Option, + tls_cert: Option, } #[derive(Clone)] @@ -52,6 +55,13 @@ pub struct Config { telegram_token: Option, telegram_admin_handle: Option, api_token: Option, + tls: Option, +} + +#[derive(Clone)] +struct TlsConfig { + key: PathBuf, + cert: PathBuf, } #[derive(Debug)] @@ -100,6 +110,8 @@ impl std::fmt::Debug for Config { .field("telegram_token", &"[redacted]") .field("telegram_admin_handle", &self.telegram_admin_handle) .field("api_token", &"[redacted]") + .field("tls_key", &"[redacted]") + .field("tls_cert", &"[redacted]") .finish() } } @@ -121,6 +133,8 @@ impl Config { .set_default("telegram_token", None as Option<&str>)? .set_default("telegram_admin_handle", None as Option<&str>)? .set_default("api_token", None as Option<&str>)? + .set_default("tls_key", None as Option<&str>)? + .set_default("tls_cert", None as Option<&str>)? .add_source(Environment::default()) .build()?; @@ -129,6 +143,10 @@ impl Config { let scheme = if config.https { "https" } else { "http" }; let base_uri = iri!(format!("{}://{}", scheme, config.hostname)).into_absolute(); + let tls = config + .tls_key + .and_then(|key| config.tls_cert.map(|cert| TlsConfig { key, cert })); + Ok(Config { hostname: config.hostname, addr: config.addr, @@ -144,9 +162,39 @@ impl Config { telegram_token: config.telegram_token, telegram_admin_handle: config.telegram_admin_handle, api_token: config.api_token, + tls, }) } + pub(crate) fn open_keys(&self) -> Result, PrivateKey)>, Error> { + let tls = if let Some(tls) = &self.tls { + tls + } else { + return Ok(None); + }; + + let mut certs_reader = BufReader::new(std::fs::File::open(&tls.cert)?); + let certs = rustls_pemfile::certs(&mut certs_reader)?; + + let mut key_reader = BufReader::new(std::fs::File::open(&tls.key)?); + let keys = rustls_pemfile::read_all(&mut key_reader)?; + + let certs = certs.into_iter().map(Certificate).collect(); + + let key = if let Some(key) = keys.into_iter().find_map(|item| match item { + rustls_pemfile::Item::RSAKey(der) => Some(PrivateKey(der)), + rustls_pemfile::Item::PKCS8Key(der) => Some(PrivateKey(der)), + rustls_pemfile::Item::ECKey(der) => Some(PrivateKey(der)), + _ => None, + }) { + key + } else { + return Ok(None); + }; + + Ok(Some((certs, key))) + } + pub(crate) fn sled_path(&self) -> &PathBuf { &self.sled_path } diff --git a/src/jobs.rs b/src/jobs.rs index 296c641..014bd72 100644 --- a/src/jobs.rs +++ b/src/jobs.rs @@ -44,8 +44,8 @@ pub(crate) fn create_workers( media: MediaCache, config: Config, ) -> (Manager, JobServer) { - let parallelism = - std::thread::available_parallelism().unwrap_or_else(|_| NonZeroUsize::try_from(1).expect("nonzero")); + let parallelism = std::thread::available_parallelism() + .unwrap_or_else(|_| NonZeroUsize::try_from(1).expect("nonzero")); let shared = WorkerConfig::new_managed(Storage::new(ActixTimer), move |queue_handle| { JobState::new( diff --git a/src/main.rs b/src/main.rs index cb500bb..5045b0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use collector::MemoryCollector; use console_subscriber::ConsoleLayer; use opentelemetry::{sdk::Resource, KeyValue}; use opentelemetry_otlp::WithExportConfig; +use rustls::ServerConfig; use tracing_actix_web::TracingLogger; use tracing_error::ErrorLayer; use tracing_log::LogTracer; @@ -203,9 +204,10 @@ async fn do_server_main( telegram::start(admin_handle.to_owned(), db.clone(), token); } + let keys = config.open_keys()?; + let bind_address = config.bind_address(); - tracing::warn!("Binding to {}:{}", bind_address.0, bind_address.1); - HttpServer::new(move || { + let server = HttpServer::new(move || { let app = App::new() .app_data(web::Data::new(db.clone())) .app_data(web::Data::new(state.clone())) @@ -258,10 +260,24 @@ async fn do_server_main( .route("/stats", web::get().to(admin::routes::stats)), ), ) - }) - .bind(bind_address)? - .run() - .await?; + }); + + if let Some((certs, key)) = keys { + tracing::warn!("Binding to {}:{} with TLS", bind_address.0, bind_address.1); + let server_config = ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions()? + .with_no_client_auth() + .with_single_cert(certs, key)?; + server + .bind_rustls(bind_address, server_config)? + .run() + .await?; + } else { + tracing::warn!("Binding to {}:{}", bind_address.0, bind_address.1); + server.bind(bind_address)?.run().await?; + } tracing::warn!("Server closed");