iroh_relay/server/
resolver.rs

1use std::{path::PathBuf, sync::Arc};
2
3use n0_error::{AnyError, StdResultExt};
4use n0_future::{
5    task::{self, AbortOnDropHandle},
6    time::{self, Duration},
7};
8use reloadable_state::Reloadable;
9use rustls::{
10    crypto::CryptoProvider,
11    server::{ClientHello, ResolvesServerCert},
12    sign::CertifiedKey,
13};
14use rustls_cert_file_reader::{FileReader, Format};
15use rustls_cert_reloadable_resolver::{CertifiedKeyLoader, key_provider::Dyn};
16use tokio_util::sync::CancellationToken;
17
18/// The default certificate reload interval.
19pub const DEFAULT_CERT_RELOAD_INTERVAL: Duration = Duration::from_secs(60 * 60 * 24);
20
21/// Builds a [`ResolvesServerCert`] that reloads its certificate and key from disk on an interval.
22///
23/// Loads the PEM-encoded certificate chain from `cert_path` and the PEM-encoded private key
24/// from `key_path` using `crypto_provider`'s key provider, then spawns a background task that
25/// re-reads both files every `interval`. The returned resolver hands the most recently loaded
26/// `CertifiedKey` to rustls for each TLS handshake, so certificate rotation takes effect without
27/// restarting the server. See [`DEFAULT_CERT_RELOAD_INTERVAL`] for a sensible default.
28///
29/// The reload task is tied to the returned `Arc` and is aborted when the last reference is
30/// dropped. Reload failures on the interval are silently ignored; the previously loaded
31/// certificate remains in use.
32///
33/// # Errors
34///
35/// Returns an error if the initial certificate or key load fails (for example, the files do not
36/// exist, cannot be read, or cannot be parsed as PEM).
37pub async fn reloading_resolver(
38    crypto_provider: &CryptoProvider,
39    cert_path: PathBuf,
40    key_path: PathBuf,
41    interval: std::time::Duration,
42) -> Result<Arc<dyn ResolvesServerCert>, AnyError> {
43    let key_reader = FileReader::new(key_path, Format::PEM);
44    let certs_reader = FileReader::new(cert_path, Format::PEM);
45    let loader = CertifiedKeyLoader {
46        key_provider: Dyn(crypto_provider.key_provider),
47        key_reader,
48        certs_reader,
49    };
50    let resolver = ReloadingResolver::init(loader, interval)
51        .await
52        .std_context("cert loading")?;
53    Ok(Arc::new(resolver))
54}
55
56/// A Certificate resolver that reloads the certificate every interval
57#[derive(Debug)]
58struct ReloadingResolver<Loader: Send + 'static> {
59    /// The inner reloadable value.
60    reloadable: Arc<Reloadable<CertifiedKey, Loader>>,
61    /// The handle to the task that reloads the certificate.
62    _handle: AbortOnDropHandle<()>,
63}
64
65impl<Loader> ReloadingResolver<Loader>
66where
67    Loader: Send + reloadable_state::core::Loader<Value = CertifiedKey> + 'static,
68{
69    /// Perform the initial load and construct the [`ReloadingResolver`].
70    async fn init(loader: Loader, interval: Duration) -> Result<Self, Loader::Error> {
71        let (reloadable, _) = Reloadable::init_load(loader).await?;
72        let reloadable = Arc::new(reloadable);
73
74        let cancel_token = CancellationToken::new();
75
76        // Spawn a task to reload the certificate every interval.
77        let _reloadable = reloadable.clone();
78        let _cancel_token = cancel_token.clone();
79        let _handle = task::spawn(async move {
80            let mut interval = time::interval(interval);
81            loop {
82                tokio::select! {
83                    _ = interval.tick() => {
84                        let _ = _reloadable.reload().await;
85                        tracing::info!("Reloaded the certificate");
86                    },
87                    _ = _cancel_token.cancelled() => {
88                        tracing::trace!("shutting down");
89                        break;
90                    }
91                }
92            }
93        });
94        let _handle = AbortOnDropHandle::new(_handle);
95
96        Ok(Self {
97            reloadable,
98            _handle,
99        })
100    }
101}
102
103impl<Loader> ResolvesServerCert for ReloadingResolver<Loader>
104where
105    Loader: reloadable_state::core::Loader<Value = CertifiedKey>,
106    Loader: Send,
107    Loader: std::fmt::Debug,
108{
109    fn resolve(&self, _client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
110        Some(self.reloadable.get())
111    }
112}
113
114impl<Loader: Send> std::ops::Deref for ReloadingResolver<Loader> {
115    type Target = Reloadable<CertifiedKey, Loader>;
116
117    fn deref(&self) -> &Self::Target {
118        &self.reloadable
119    }
120}