Skip to main content

synapse_mtls/
lib.rs

1use std::path::{Path, PathBuf};
2
3pub mod certs;
4pub mod client;
5pub mod server;
6
7pub use client::build_mtls_client;
8pub use server::build_mtls_acceptor;
9
10/// Re-export the mTLS client type
11pub type MtlsClient = hyper_util::client::legacy::Client<
12    hyper_rustls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>,
13    hyper::body::Incoming,
14>;
15
16pub mod names {
17    pub const ORGANIZATION: &str = "mips-synapse";
18    pub const SERVICE_UNIT: &str = "service";
19    pub const AUTHORITY_NAME: &str = "service";
20    pub const GATEWAY_NAME: &str = "gateway";
21}
22
23/// relative paths from mtls folder
24pub mod paths {
25    pub const CA_CERT_PEM: &str = "ca-cert.pem";
26    pub const CA_KEY_PEM: &str = "ca-key.pem";
27    pub const CERT: &str = "cert.pem";
28    pub const KEY: &str = "key.pem";
29}
30
31/// mTLS configuration loaded from a directory
32#[derive(Debug, Clone)]
33pub struct MtlsConfig {
34    pub cert_path: PathBuf,
35    pub key_path: PathBuf,
36    pub ca_cert_path: PathBuf,
37}
38
39impl MtlsConfig {
40    /// Load mTLS config from a directory containing cert.pem, key.pem
41    /// CA cert is loaded from parent directory's ca-cert.pem
42    pub fn from_dir<P: AsRef<Path>>(dir: P) -> Self {
43        let dir = dir.as_ref();
44        Self {
45            cert_path: dir.join(paths::CERT),
46            key_path: dir.join(paths::KEY),
47            // CA cert is in the parent mtls directory
48            ca_cert_path: dir
49                .parent()
50                .map(|p| p.join(paths::CA_CERT_PEM))
51                .unwrap_or_else(|| dir.join(paths::CA_CERT_PEM)),
52        }
53    }
54
55    /// Try to load from default ./mtls directory (for gateway)
56    pub fn from_default_dir() -> Option<Self> {
57        Self::for_service("gateway")
58    }
59
60    /// Load mTLS config for a specific service from ./mtls/{service_name}/
61    pub fn for_service(service_name: &str) -> Option<Self> {
62        let base_dir = PathBuf::from("./mtls");
63        let service_dir = base_dir.join(service_name);
64
65        let cert_path = service_dir.join(paths::CERT);
66        let key_path = service_dir.join(paths::KEY);
67        let ca_cert_path = base_dir.join(paths::CA_CERT_PEM);
68
69        if cert_path.exists() && key_path.exists() && ca_cert_path.exists() {
70            Some(Self {
71                cert_path,
72                key_path,
73                ca_cert_path,
74            })
75        } else {
76            None
77        }
78    }
79
80    /// Build a TLS acceptor for server-side mTLS
81    pub fn build_server_acceptor(&self) -> anyhow::Result<tokio_rustls::TlsAcceptor> {
82        server::build_mtls_acceptor(&self.cert_path, &self.key_path, &self.ca_cert_path)
83    }
84
85    /// Build an mTLS HTTP client
86    pub fn build_client(&self) -> anyhow::Result<MtlsClient> {
87        client::build_mtls_client(&self.cert_path, &self.key_path, &self.ca_cert_path)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_from_dir_paths() {
97        let config = MtlsConfig::from_dir("/mtls/my-service");
98        assert_eq!(config.cert_path, PathBuf::from("/mtls/my-service/cert.pem"));
99        assert_eq!(config.key_path, PathBuf::from("/mtls/my-service/key.pem"));
100        assert_eq!(config.ca_cert_path, PathBuf::from("/mtls/ca-cert.pem"));
101    }
102
103    #[test]
104    fn test_from_dir_no_parent() {
105        // If the dir has no parent (e.g., just a name), CA cert falls back to same dir
106        let config = MtlsConfig::from_dir("service");
107        assert_eq!(config.cert_path, PathBuf::from("service/cert.pem"));
108        assert_eq!(config.key_path, PathBuf::from("service/key.pem"));
109        // Parent of "service" is "" which is falsy, so falls back
110        // Actually PathBuf::from("service").parent() returns Some(""), let's just check it's set
111        assert!(
112            config
113                .ca_cert_path
114                .to_str()
115                .unwrap()
116                .contains("ca-cert.pem")
117        );
118    }
119
120    #[test]
121    fn test_constants() {
122        assert_eq!(paths::CA_CERT_PEM, "ca-cert.pem");
123        assert_eq!(paths::CA_KEY_PEM, "ca-key.pem");
124        assert_eq!(paths::CERT, "cert.pem");
125        assert_eq!(paths::KEY, "key.pem");
126        assert_eq!(names::ORGANIZATION, "mips-synapse");
127        assert_eq!(names::GATEWAY_NAME, "gateway");
128    }
129}