feat(config): tls_cert_path / tls_key_path env parsers (deploy-C §5.4)

Signed-off-by: Aaron D. Lee <himself@adlee.work>
This commit is contained in:
2026-07-06 00:55:24 -04:00
parent 66006400bf
commit 5d14215053

View File

@@ -8,6 +8,7 @@
//! Closes 2026-07-04 scalability review "HTTP bind hardcoded" (minor).
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use rutster_media::MediaAddressConfig;
use rutster_trunk::provider::TwilioCredentials;
@@ -222,6 +223,59 @@ pub fn twilio_credentials(
}))
}
/// `RUTSTER_TLS_CERT` — filesystem path to the TLS certificate (PEM) used by
/// the axum HTTPS surface (deploy slice C §5.4).
///
/// `None` means the operator did not enable HTTPS; `main.rs` will serve plain
/// HTTP. `Some(path)` is validated to point at an existing regular file so a
/// typo fails-fast at startup instead of failing mid-request.
///
/// This is a pure function over `Option<String>` (not a direct `std::env::var`
/// call) so unit tests never mutate process env and never touch the real
/// filesystem unless the test itself creates a tempfile.
pub fn tls_cert_path(raw: Option<String>) -> Result<Option<PathBuf>, String> {
match raw {
None => Ok(None),
Some(s) => {
let p = PathBuf::from(s);
if !p.is_file() {
return Err(format!(
"RUTSTER_TLS_CERT {:?}: file does not exist (or is not a regular file)",
p.display()
));
}
Ok(Some(p))
}
}
}
/// `RUTSTER_TLS_KEY` — filesystem path to the TLS private key (PEM) paired
/// with [`tls_cert_path`].
///
/// `None` means HTTPS is disabled; `main.rs` will serve plain HTTP. When both
/// `RUTSTER_TLS_CERT` and `RUTSTER_TLS_KEY` are `Some`, `main.rs` feeds the
/// pair to `tokio_rustls` (or equivalent) to terminate TLS on the signaling
/// surface. The mutual presence check lives in `main.rs`, not here — this
/// parser only validates its own path.
///
/// Like the other parsers in this module, the function is pure over
/// `Option<String>` so tests stay deterministic and env-free.
pub fn tls_key_path(raw: Option<String>) -> Result<Option<PathBuf>, String> {
match raw {
None => Ok(None),
Some(s) => {
let p = PathBuf::from(s);
if !p.is_file() {
return Err(format!(
"RUTSTER_TLS_KEY {:?}: file does not exist (or is not a regular file)",
p.display()
));
}
Ok(Some(p))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -430,4 +484,35 @@ mod tests {
assert!(err.contains("RUTSTER_TRUSTED_PROXIES"));
assert!(err.contains("not-a-cidr"));
}
#[test]
fn tls_cert_path_none_when_unset() {
assert!(tls_cert_path(None).unwrap().is_none());
}
#[test]
fn tls_cert_path_some_when_file_exists() {
let path = std::env::temp_dir().join("rutster_tls_cert_path_test.txt");
std::fs::write(&path, b"test cert").unwrap();
let result = tls_cert_path(Some(path.to_str().unwrap().to_string())).unwrap();
assert_eq!(result, Some(path.clone()));
let _ = std::fs::remove_file(&path);
}
#[test]
fn tls_cert_path_rejects_missing_file_with_var_name_in_error() {
let err = tls_cert_path(Some("/nonexistent/cert.pem".into())).unwrap_err();
assert!(err.contains("RUTSTER_TLS_CERT"));
}
#[test]
fn tls_key_path_none_when_unset() {
assert!(tls_key_path(None).unwrap().is_none());
}
#[test]
fn tls_key_path_rejects_missing_file_with_var_name_in_error() {
let err = tls_key_path(Some("/nonexistent/key.pem".into())).unwrap_err();
assert!(err.contains("RUTSTER_TLS_KEY"));
}
}