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:
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user