use tonic::transport::{Certificate, Identity, ServerTlsConfig};
use crate::config::AdminTlsConfig;
use crate::error::StandaloneError;
pub(crate) struct AdminTlsMaterial {
pub server: ServerTlsConfig,
}
fn read(path: &std::path::Path) -> Result<Vec<u8>, StandaloneError> {
std::fs::read(path).map_err(|source| StandaloneError::Tls {
path: path.to_path_buf(),
source: Box::new(source),
})
}
pub(crate) fn build_admin_tls(cfg: &AdminTlsConfig) -> Result<AdminTlsMaterial, StandaloneError> {
let cert = read(&cfg.cert)?;
let key = read(&cfg.key)?;
let ca = read(&cfg.ca)?;
let identity = Identity::from_pem(&cert, &key);
let ca_cert = Certificate::from_pem(&ca);
let server = ServerTlsConfig::new()
.identity(identity)
.client_ca_root(ca_cert);
tonic::transport::Server::builder()
.tls_config(server.clone())
.map_err(|source| StandaloneError::Tls {
path: cfg.cert.clone(),
source: Box::new(source),
})?;
Ok(AdminTlsMaterial { server })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::AdminTlsConfig;
fn write_admin_certs(dir: &std::path::Path) -> AdminTlsConfig {
use rcgen::{BasicConstraints, CertificateParams, IsCa, KeyPair};
let ca_key = KeyPair::generate().unwrap();
let mut ca_params = CertificateParams::new(vec!["tso-admin-ca".to_string()]).unwrap();
ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
let ca_cert = ca_params.self_signed(&ca_key).unwrap();
let node_key = KeyPair::generate().unwrap();
let node_params = CertificateParams::new(vec!["127.0.0.1".to_string()]).unwrap();
let node_cert = node_params.signed_by(&node_key, &ca_cert, &ca_key).unwrap();
let cert = dir.join("admin.crt");
let key = dir.join("admin.key");
let ca = dir.join("admin-ca.crt");
std::fs::write(&cert, node_cert.pem()).unwrap();
std::fs::write(&key, node_key.serialize_pem()).unwrap();
std::fs::write(&ca, ca_cert.pem()).unwrap();
AdminTlsConfig { cert, key, ca }
}
#[tokio::test]
async fn valid_trio_builds_admin_server_tls() {
let dir = tempfile::tempdir().unwrap();
let cfg = write_admin_certs(dir.path());
let mat = build_admin_tls(&cfg).expect("valid trio must build");
let _ = mat.server;
}
#[tokio::test]
async fn missing_file_is_a_tls_error() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = write_admin_certs(dir.path());
cfg.cert = dir.path().join("does-not-exist.crt");
let err = match build_admin_tls(&cfg) {
Ok(_) => panic!("expected a Tls error"),
Err(e) => e,
};
assert!(matches!(err, StandaloneError::Tls { .. }), "got {err:?}");
}
#[tokio::test]
async fn invalid_pem_is_a_tls_error() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = write_admin_certs(dir.path());
let garbage = dir.path().join("garbage.crt");
std::fs::write(&garbage, b"not a pem").unwrap();
cfg.cert = garbage;
let err = match build_admin_tls(&cfg) {
Ok(_) => panic!("expected a Tls error"),
Err(e) => e,
};
assert!(matches!(err, StandaloneError::Tls { .. }), "got {err:?}");
}
}