Skip to main content

rustfs_tls_runtime/
server.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::certs::{CertDirectoryLoadOptions, load_all_certs_from_directory};
16use crate::config::TlsReloadOptions;
17use crate::error::TlsRuntimeError;
18use crate::material::{ServerTlsMaterial, server_material_fingerprint};
19use crate::metrics::{record_tls_generation, record_tls_publication_fail, record_tls_reload_result, record_tls_reload_skipped};
20use crate::source::TlsSource;
21use rustls::pki_types::{CertificateDer, PrivateKeyDer};
22use rustls::server::{ClientHello, ResolvesServerCert, ResolvesServerCertUsingSni};
23use rustls::sign::CertifiedKey;
24use std::collections::HashMap;
25use std::io;
26use std::sync::atomic::{AtomicU64, Ordering};
27use std::sync::{Arc, RwLock};
28use tokio::sync::watch;
29use tokio::task::JoinHandle;
30use tokio::time::MissedTickBehavior;
31use tracing::{debug, info, warn};
32
33#[derive(Debug)]
34struct ResolverState {
35    cert_resolver: ResolvesServerCertUsingSni,
36    default_cert: Option<Arc<CertifiedKey>>,
37    cert_count: usize,
38    fingerprint: crate::fingerprint::TlsFingerprint,
39}
40
41impl ResolverState {
42    fn load_from_source(source: &TlsSource) -> Result<Self, TlsRuntimeError> {
43        let base_dir = source.validate_directory()?;
44        let cert_key_pairs = load_all_certs_from_directory(
45            CertDirectoryLoadOptions::builder(base_dir, &source.layout.server_cert_filename, &source.layout.server_key_filename)
46                .build(),
47        )?;
48        if cert_key_pairs.is_empty() {
49            return Err(TlsRuntimeError::Material("No valid certificates found in directory".to_string()));
50        }
51
52        Self::from_cert_key_pairs(cert_key_pairs)
53    }
54
55    fn from_cert_key_pairs(
56        cert_key_pairs: HashMap<String, (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>,
57    ) -> Result<Self, TlsRuntimeError> {
58        let cert_count = cert_key_pairs.len();
59        let mut cert_resolver = ResolvesServerCertUsingSni::new();
60        let mut default_cert = None;
61        let mut entries = cert_key_pairs.into_iter().collect::<Vec<_>>();
62        entries.sort_by(|(left_domain, _), (right_domain, _)| left_domain.cmp(right_domain));
63        let material = ServerTlsMaterial::MultiCert {
64            cert_key_pairs: entries
65                .iter()
66                .map(|(domain, (certs, key))| (domain.clone(), (certs.clone(), key.clone_key())))
67                .collect(),
68        };
69        let fingerprint = server_material_fingerprint(&material);
70
71        for (domain, (certs, key)) in entries {
72            let signing_key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key)
73                .map_err(|e| io::Error::other(format!("unsupported private key type for {domain}: {e:?}")))?;
74            let certified_key = CertifiedKey::new(certs, signing_key);
75
76            if domain.as_str() == "default" {
77                default_cert = Some(Arc::new(certified_key.clone()));
78            } else {
79                cert_resolver
80                    .add(&domain, certified_key)
81                    .map_err(|e| io::Error::other(format!("failed to add certificate for {domain}: {e:?}")))?;
82            }
83        }
84
85        Ok(Self {
86            cert_resolver,
87            default_cert,
88            cert_count,
89            fingerprint,
90        })
91    }
92}
93
94#[derive(Debug)]
95pub struct ReloadableServerCertResolver {
96    source: TlsSource,
97    current: RwLock<ResolverState>,
98    generation: AtomicU64,
99}
100
101impl ReloadableServerCertResolver {
102    pub fn load_from_source(source: TlsSource) -> Result<Arc<Self>, TlsRuntimeError> {
103        let state = ResolverState::load_from_source(&source)?;
104        record_tls_generation("server_resolver", 1);
105        Ok(Arc::new(Self {
106            source,
107            current: RwLock::new(state),
108            generation: AtomicU64::new(1),
109        }))
110    }
111
112    pub fn load_from_directory(cert_dir: &str) -> Result<Arc<Self>, TlsRuntimeError> {
113        Self::load_from_source(TlsSource::from_directory(cert_dir))
114    }
115
116    pub fn reload(&self) -> Result<Option<usize>, TlsRuntimeError> {
117        let new_state = ResolverState::load_from_source(&self.source)?;
118
119        match self.current.write() {
120            Ok(mut guard) => {
121                if guard.fingerprint == new_state.fingerprint {
122                    return Ok(None);
123                }
124                let cert_count = new_state.cert_count;
125                *guard = new_state;
126                self.generation.fetch_add(1, Ordering::Relaxed);
127                Ok(Some(cert_count))
128            }
129            Err(poisoned) => {
130                let mut guard = poisoned.into_inner();
131                if guard.fingerprint == new_state.fingerprint {
132                    return Ok(None);
133                }
134                let cert_count = new_state.cert_count;
135                *guard = new_state;
136                self.generation.fetch_add(1, Ordering::Relaxed);
137                Ok(Some(cert_count))
138            }
139        }
140    }
141
142    pub fn generation(&self) -> u64 {
143        self.generation.load(Ordering::Relaxed)
144    }
145}
146
147impl ResolvesServerCert for ReloadableServerCertResolver {
148    fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
149        let guard = match self.current.read() {
150            Ok(guard) => guard,
151            Err(poisoned) => poisoned.into_inner(),
152        };
153
154        guard
155            .cert_resolver
156            .resolve(client_hello)
157            .or_else(|| guard.default_cert.clone())
158    }
159}
160
161pub fn spawn_server_cert_reload_loop(
162    protocol: &'static str,
163    resolver: Arc<ReloadableServerCertResolver>,
164    options: TlsReloadOptions,
165    mut shutdown_rx: watch::Receiver<bool>,
166) -> Option<JoinHandle<()>> {
167    if !options.enabled {
168        debug!(protocol, "TLS certificate hot reload is disabled");
169        return None;
170    }
171
172    info!(
173        protocol,
174        cert_dir = %resolver.source.base_dir.display(),
175        "TLS certificate hot reload enabled, checking every {}s",
176        options.interval.as_secs()
177    );
178
179    Some(tokio::spawn(async move {
180        let mut interval = tokio::time::interval(options.interval);
181        interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
182        interval.tick().await;
183
184        loop {
185            tokio::select! {
186                changed = shutdown_rx.changed() => {
187                    match changed {
188                        Ok(()) => {
189                            if *shutdown_rx.borrow() {
190                                info!(protocol, cert_dir = %resolver.source.base_dir.display(), "TLS certificate hot reload task stopped");
191                                break;
192                            }
193                            continue;
194                        }
195                        Err(_) => {
196                            info!(
197                                protocol,
198                                cert_dir = %resolver.source.base_dir.display(),
199                                "TLS certificate hot reload task stopped because the shutdown channel closed"
200                            );
201                            break;
202                        }
203                    }
204                }
205                _ = interval.tick() => {}
206            }
207
208            match resolver.reload() {
209                Ok(Some(cert_count)) => {
210                    record_tls_reload_result(protocol, "ok", None, Some(resolver.generation()));
211                    info!(
212                        protocol,
213                        cert_dir = %resolver.source.base_dir.display(),
214                        cert_count,
215                        "TLS certificates reloaded successfully"
216                    );
217                }
218                Ok(None) => {
219                    record_tls_reload_skipped(protocol, "unchanged");
220                    debug!(
221                        protocol,
222                        cert_dir = %resolver.source.base_dir.display(),
223                        "TLS certificate material unchanged; skipping reload"
224                    );
225                }
226                Err(e) => {
227                    record_tls_publication_fail(protocol);
228                    warn!(
229                        protocol,
230                        cert_dir = %resolver.source.base_dir.display(),
231                        "TLS certificate reload failed (will retry): {}",
232                        e
233                    );
234                }
235            }
236        }
237    }))
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use rcgen::generate_simple_self_signed;
244    use std::fs;
245    use tempfile::TempDir;
246
247    fn cert_key_pair(san: &str) -> (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>) {
248        let cert = generate_simple_self_signed(vec![san.to_string()]).expect("cert should generate");
249        (
250            vec![cert.cert.der().clone()],
251            PrivateKeyDer::try_from(cert.signing_key.serialize_der()).expect("key should convert"),
252        )
253    }
254
255    fn clone_cert_key_pair(
256        cert_key_pair: &(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
257    ) -> (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>) {
258        (cert_key_pair.0.clone(), cert_key_pair.1.clone_key())
259    }
260
261    fn write_default_cert(dir: &std::path::Path, san: &str) {
262        let cert = generate_simple_self_signed(vec![san.to_string()]).expect("cert should generate");
263        fs::write(dir.join(rustfs_config::RUSTFS_TLS_CERT), cert.cert.pem()).expect("cert should write");
264        fs::write(dir.join(rustfs_config::RUSTFS_TLS_KEY), cert.signing_key.serialize_pem()).expect("key should write");
265    }
266
267    #[test]
268    fn reload_replaces_default_certificate() {
269        let temp_dir = TempDir::new().expect("tempdir should create");
270        write_default_cert(temp_dir.path(), "localhost");
271
272        let resolver = ReloadableServerCertResolver::load_from_directory(temp_dir.path().to_str().expect("path should utf8"))
273            .expect("resolver should load");
274        let before = {
275            let guard = resolver.current.read().expect("lock should acquire");
276            guard.default_cert.as_ref().expect("default cert should exist").clone()
277        };
278
279        write_default_cert(temp_dir.path(), "rotated.local");
280
281        let cert_count = resolver.reload().expect("reload should succeed");
282        assert_eq!(cert_count, Some(1));
283
284        let after = {
285            let guard = resolver.current.read().expect("lock should acquire");
286            guard.default_cert.as_ref().expect("default cert should exist").clone()
287        };
288
289        assert_ne!(before.cert[0].as_ref(), after.cert[0].as_ref());
290    }
291
292    #[test]
293    fn reload_skips_when_material_is_unchanged() {
294        let temp_dir = TempDir::new().expect("tempdir should create");
295        write_default_cert(temp_dir.path(), "localhost");
296
297        let resolver = ReloadableServerCertResolver::load_from_directory(temp_dir.path().to_str().expect("path should utf8"))
298            .expect("resolver should load");
299        let outcome = resolver.reload().expect("reload should succeed");
300        assert_eq!(outcome, None);
301    }
302
303    #[test]
304    fn resolver_state_fingerprint_is_stable_across_domain_ordering() {
305        let default_cert = cert_key_pair("localhost");
306        let api_cert = cert_key_pair("api.example.com");
307        let web_cert = cert_key_pair("web.example.com");
308
309        let mut first = HashMap::new();
310        first.insert("default".to_string(), clone_cert_key_pair(&default_cert));
311        first.insert("api.example.com".to_string(), clone_cert_key_pair(&api_cert));
312        first.insert("web.example.com".to_string(), clone_cert_key_pair(&web_cert));
313
314        let mut second = HashMap::new();
315        second.insert("web.example.com".to_string(), clone_cert_key_pair(&web_cert));
316        second.insert("default".to_string(), clone_cert_key_pair(&default_cert));
317        second.insert("api.example.com".to_string(), clone_cert_key_pair(&api_cert));
318
319        let first_state = ResolverState::from_cert_key_pairs(first).expect("first state should build");
320        let second_state = ResolverState::from_cert_key_pairs(second).expect("second state should build");
321
322        assert_eq!(first_state.cert_count, 3);
323        assert_eq!(second_state.cert_count, 3);
324        assert_eq!(first_state.fingerprint, second_state.fingerprint);
325    }
326}