1use std::{
7 path::{Path, PathBuf},
8 sync::Arc,
9 time::Duration,
10};
11
12use anyhow::Result;
13use rustls::{
14 pki_types::{CertificateDer, PrivateKeyDer},
15 ClientConfig, RootCertStore, ServerConfig,
16};
17use rustls_pemfile::{certs, pkcs8_private_keys};
18use tokio::{fs, sync::RwLock};
19use tonic::transport::Certificate;
20use tracing::{info, warn};
21
22#[derive(Debug, Clone)]
24pub struct MTLSConfig {
25 pub ca_cert_path: PathBuf,
27 pub server_cert_path: PathBuf,
29 pub server_key_path: PathBuf,
31 pub require_client_cert: bool,
33 pub rotation_check_interval: Duration,
35}
36
37impl Default for MTLSConfig {
38 fn default() -> Self {
39 Self {
40 ca_cert_path: PathBuf::from("/etc/ssl/certs/ca-certificates.crt"),
41 server_cert_path: PathBuf::from("/etc/ssl/certs/server.crt"),
42 server_key_path: PathBuf::from("/etc/ssl/private/server.key"),
43 require_client_cert: true,
44 rotation_check_interval: Duration::from_secs(300), }
46 }
47}
48
49#[derive(Debug)]
51pub struct MTLSManager {
52 config: MTLSConfig,
53 server_config: Arc<RwLock<Option<Arc<ServerConfig>>>>,
54 client_config: Arc<RwLock<Option<Arc<ClientConfig>>>>,
55}
56
57impl MTLSManager {
58 pub fn new(config: MTLSConfig) -> Self {
60 Self {
61 config,
62 server_config: Arc::new(RwLock::new(None)),
63 client_config: Arc::new(RwLock::new(None)),
64 }
65 }
66
67 pub async fn load_server_config(&self) -> Result<Arc<ServerConfig>> {
69 let certs = self.load_certs(&self.config.server_cert_path).await?;
70 let key = self.load_private_key(&self.config.server_key_path).await?;
71
72 let mut server_config = ServerConfig::builder()
73 .with_no_client_auth()
74 .with_single_cert(certs, key)?;
75
76 server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
78
79 let config = Arc::new(server_config);
80 *self.server_config.write().await = Some(config.clone());
81 Ok(config)
82 }
83
84 pub async fn load_client_config(&self) -> Result<Arc<ClientConfig>> {
86 let mut root_store = RootCertStore::empty();
87
88 let ca_certs = self.load_certs(&self.config.ca_cert_path).await?;
90 for cert in ca_certs {
91 root_store.add(cert)?;
92 }
93
94 let mut client_config = ClientConfig::builder()
95 .with_root_certificates(root_store)
96 .with_no_client_auth();
97
98 client_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
100
101 let config = Arc::new(client_config);
102 *self.client_config.write().await = Some(config.clone());
103 Ok(config)
104 }
105
106 pub async fn load_ca_certificate(&self) -> Result<Certificate> {
108 let ca_cert = fs::read(&self.config.ca_cert_path).await?;
109 Ok(Certificate::from_pem(ca_cert))
110 }
111
112 async fn load_certs(&self, path: &Path) -> Result<Vec<CertificateDer<'static>>> {
114 let cert_data = fs::read(path).await?;
115 let certs = certs(&mut cert_data.as_slice()).collect::<Result<Vec<_>, _>>()?;
116 Ok(certs)
117 }
118
119 async fn load_private_key(&self, path: &Path) -> Result<PrivateKeyDer<'static>> {
121 let key_data = fs::read(path).await?;
122 let mut keys =
123 pkcs8_private_keys(&mut key_data.as_slice()).collect::<Result<Vec<_>, _>>()?;
124
125 if keys.is_empty() {
126 return Err(anyhow::anyhow!("No private key found in file"));
127 }
128
129 Ok(PrivateKeyDer::Pkcs8(keys.remove(0)))
130 }
131
132 #[expect(
134 clippy::disallowed_methods,
135 reason = "fire-and-forget background monitor; rotation runs for the process lifetime and does not need explicit join"
136 )]
137 pub fn start_rotation_monitor(&self) {
138 let config = self.config.clone();
139 let server_config = self.server_config.clone();
140 let client_config = self.client_config.clone();
141
142 tokio::spawn(async move {
143 let mut interval = tokio::time::interval(config.rotation_check_interval);
144 loop {
145 interval.tick().await;
146
147 if let Err(e) =
149 Self::check_and_reload_certs(&config, &server_config, &client_config).await
150 {
151 warn!("Error checking certificate rotation: {}", e);
152 }
153 }
154 });
155 }
156
157 async fn check_and_reload_certs(
159 config: &MTLSConfig,
160 _server_config: &Arc<RwLock<Option<Arc<ServerConfig>>>>,
161 _client_config: &Arc<RwLock<Option<Arc<ClientConfig>>>>,
162 ) -> Result<()> {
163 let server_cert_mtime = fs::metadata(&config.server_cert_path).await?.modified()?;
165 let server_key_mtime = fs::metadata(&config.server_key_path).await?.modified()?;
166 let ca_cert_mtime = fs::metadata(&config.ca_cert_path).await?.modified()?;
167
168 info!(
171 "Certificate rotation check: server_cert={:?}, server_key={:?}, ca_cert={:?}",
172 server_cert_mtime, server_key_mtime, ca_cert_mtime
173 );
174
175 Ok(())
178 }
179
180 pub async fn get_server_config(&self) -> Option<Arc<ServerConfig>> {
182 self.server_config.read().await.clone()
183 }
184
185 pub async fn get_client_config(&self) -> Option<Arc<ClientConfig>> {
187 self.client_config.read().await.clone()
188 }
189}
190
191pub type OptionalMTLSManager = Option<Arc<MTLSManager>>;