spiffe_rustls/
server.rs

1use crate::authorizer::Authorizer;
2use crate::error::Result;
3use crate::policy::TrustDomainPolicy;
4use crate::resolve::MaterialWatcher;
5use crate::verifier::SpiffeClientCertVerifier;
6use rustls::server::ResolvesServerCert;
7use rustls::ServerConfig;
8use spiffe::X509Source;
9use std::sync::Arc;
10
11/// Builds a [`rustls::ServerConfig`] backed by a live SPIFFE `X509Source`.
12///
13/// The resulting server configuration:
14///
15/// * presents the current SPIFFE X.509 SVID as the server certificate
16/// * requires and validates client certificates (mTLS)
17/// * authorizes the client by SPIFFE ID (URI SAN)
18///
19/// ## Trust Domain Selection
20///
21/// The builder uses the bundle set from `X509Source`, which may contain bundles
22/// for multiple trust domains (when SPIFFE federation is configured). The verifier
23/// automatically selects the correct bundle based on the peer's SPIFFE ID—no
24/// manual configuration is required. You can optionally restrict which trust
25/// domains are accepted using [`Self::trust_domain_policy`].
26///
27/// ## Authorization
28///
29/// Client authorization is performed by invoking the provided [`Authorizer`] with
30/// the client's SPIFFE ID extracted from the certificate's URI SAN.
31///
32/// Use [`authorizer::any`] to disable authorization while retaining full TLS authentication.
33///
34/// # Examples
35///
36/// ```no_run
37/// use spiffe::{TrustDomain, X509Source};
38/// use spiffe_rustls::{authorizer, mtls_server, LocalOnly};
39///
40/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
41/// let source = X509Source::new().await?;
42///
43/// // Pass string literals directly - trust_domains() will convert them
44/// let allowed_trust_domains = ["example.org"];
45///
46/// let local_trust_domain: TrustDomain = "example.org".try_into()?;
47///
48/// let server_config = mtls_server(source)
49///     .authorize(authorizer::trust_domains(allowed_trust_domains)?)
50///     .trust_domain_policy(LocalOnly(local_trust_domain))
51///     .build()?;
52/// # Ok(())
53/// # }
54/// ```
55pub struct ServerConfigBuilder {
56    source: Arc<X509Source>,
57    authorizer: Arc<dyn Authorizer>,
58    trust_domain_policy: TrustDomainPolicy,
59}
60
61impl std::fmt::Debug for ServerConfigBuilder {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.debug_struct("ServerConfigBuilder")
64            .field("source", &"<Arc<X509Source>>")
65            .field("authorizer", &"<Arc<dyn Authorizer>>")
66            .field("trust_domain_policy", &self.trust_domain_policy)
67            .finish()
68    }
69}
70
71impl ServerConfigBuilder {
72    /// Creates a new builder from an `X509Source`.
73    ///
74    /// Defaults:
75    /// - Authorization: accepts any SPIFFE ID (authentication only)
76    /// - Trust domain policy: `AnyInBundleSet` (uses all bundles from the Workload API)
77    pub fn new(source: X509Source) -> Self {
78        Self {
79            source: Arc::new(source),
80            authorizer: Arc::new(crate::authorizer::any()),
81            trust_domain_policy: TrustDomainPolicy::default(),
82        }
83    }
84
85    /// Sets the authorization policy for client SPIFFE IDs.
86    ///
87    /// Accepts any type that implements `Authorizer`, including closures.
88    ///
89    /// # Examples
90    ///
91    /// ```no_run
92    /// use spiffe_rustls::{authorizer, mtls_server};
93    ///
94    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
95    /// let source = spiffe::X509Source::new().await?;
96    ///
97    /// // Using a convenience constructor - pass string literals directly
98    /// let config = mtls_server(source.clone())
99    ///     .authorize(authorizer::trust_domains([
100    ///         "example.org",
101    ///     ])?)
102    ///     .build()?;
103    ///
104    /// // Using a closure
105    /// let config = mtls_server(source.clone())
106    ///     .authorize(|id: &spiffe::SpiffeId| id.path().starts_with("/api/"))
107    ///     .build()?;
108    ///
109    /// // Using the Any authorizer (default)
110    /// let config = mtls_server(source)
111    ///     .authorize(authorizer::any())
112    ///     .build()?;
113    /// # Ok(())
114    /// # }
115    /// ```
116    #[must_use]
117    pub fn authorize<A: Authorizer>(mut self, authorizer: A) -> Self {
118        self.authorizer = Arc::new(authorizer);
119        self
120    }
121
122    /// Sets the trust domain policy.
123    ///
124    /// Defaults to `AnyInBundleSet` (uses all bundles from the Workload API).
125    #[must_use]
126    pub fn trust_domain_policy(mut self, policy: TrustDomainPolicy) -> Self {
127        self.trust_domain_policy = policy;
128        self
129    }
130
131    /// Builds the `rustls::ServerConfig`.
132    ///
133    /// # Errors
134    ///
135    /// Returns an error if:
136    ///
137    /// * the SPIFFE `X509Source` does not currently have an SVID,
138    /// * rustls crypto providers are not installed,
139    /// * or the material watcher cannot be initialized.
140    pub fn build(self) -> Result<ServerConfig> {
141        crate::crypto::ensure_crypto_provider_installed();
142
143        let watcher = MaterialWatcher::spawn(self.source)?;
144
145        let resolver: Arc<dyn ResolvesServerCert> =
146            Arc::new(resolve_server::SpiffeServerCertResolver {
147                watcher: watcher.clone(),
148            });
149
150        let verifier = Arc::new(SpiffeClientCertVerifier::new(
151            Arc::new(watcher) as Arc<dyn crate::verifier::MaterialProvider>,
152            self.authorizer,
153            self.trust_domain_policy,
154        ));
155
156        let cfg = ServerConfig::builder()
157            .with_client_cert_verifier(verifier)
158            .with_cert_resolver(resolver);
159
160        Ok(cfg)
161    }
162}
163
164mod resolve_server {
165    use crate::resolve::MaterialWatcher;
166    use rustls::server::ResolvesServerCert;
167    use rustls::sign::CertifiedKey;
168    use std::sync::Arc;
169
170    #[derive(Clone, Debug)]
171    pub(crate) struct SpiffeServerCertResolver {
172        pub watcher: MaterialWatcher,
173    }
174
175    impl ResolvesServerCert for SpiffeServerCertResolver {
176        fn resolve(
177            &self,
178            _client_hello: rustls::server::ClientHello<'_>,
179        ) -> Option<Arc<CertifiedKey>> {
180            Some(self.watcher.current().certified_key.clone())
181        }
182    }
183}