Skip to main content

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/// Function type for customizing a `ServerConfig`.
12type ServerConfigCustomizer = Box<dyn FnOnce(&mut ServerConfig) + Send>;
13
14/// Builds a [`rustls::ServerConfig`] backed by a live SPIFFE `X509Source`.
15///
16/// The resulting server configuration:
17///
18/// * presents the current SPIFFE X.509 SVID as the server certificate
19/// * requires and validates client certificates (mTLS)
20/// * authorizes the client by SPIFFE ID (URI SAN)
21///
22/// ## Trust Domain Selection
23///
24/// The builder uses the bundle set from `X509Source`, which may contain bundles
25/// for multiple trust domains (when SPIFFE federation is configured). The verifier
26/// automatically selects the correct bundle based on the peer's SPIFFE ID—no
27/// manual configuration is required. You can optionally restrict which trust
28/// domains are accepted using [`Self::trust_domain_policy`].
29///
30/// The default policy is [`TrustDomainPolicy::AnyInBundleSet`], which accepts any
31/// trust domain present in the source bundle set. For non-federated deployments,
32/// prefer [`TrustDomainPolicy::LocalOnly`] to restrict verification to the local
33/// trust domain.
34///
35/// ## Authorization
36///
37/// Client authorization is performed by invoking the provided [`Authorizer`] with
38/// the client's SPIFFE ID extracted from the certificate's URI SAN.
39///
40/// The default authorizer is [`crate::authorizer::any`]. It accepts any authenticated
41/// SPIFFE ID from any trust domain accepted by the configured trust-domain policy.
42/// By default, this means every trust domain in the source bundle set. Production
43/// deployments should usually configure a more specific authorizer.
44///
45/// # Examples
46///
47/// ```no_run
48/// use spiffe::{TrustDomain, X509Source};
49/// use spiffe_rustls::{authorizer, mtls_server, LocalOnly};
50///
51/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
52/// let source = X509Source::new().await?;
53///
54/// // Pass string literals directly - trust_domains() will convert them
55/// let allowed_trust_domains = ["example.org"];
56///
57/// let local_trust_domain: TrustDomain = "example.org".try_into()?;
58///
59/// let server_config = mtls_server(source)
60///     .authorize(authorizer::trust_domains(allowed_trust_domains)?)
61///     .trust_domain_policy(LocalOnly(local_trust_domain))
62///     .build()?;
63/// # Ok(())
64/// # }
65/// ```
66pub struct ServerConfigBuilder {
67    source: Arc<X509Source>,
68    authorizer: Arc<dyn Authorizer>,
69    trust_domain_policy: TrustDomainPolicy,
70    alpn_protocols: Vec<Vec<u8>>,
71    config_customizer: Option<ServerConfigCustomizer>,
72}
73
74impl std::fmt::Debug for ServerConfigBuilder {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        f.debug_struct("ServerConfigBuilder")
77            .field("source", &"<Arc<X509Source>>")
78            .field("authorizer", &"<Arc<dyn Authorizer>>")
79            .field("trust_domain_policy", &self.trust_domain_policy)
80            .field("alpn_protocols", &self.alpn_protocols)
81            .field("config_customizer", &self.config_customizer.is_some())
82            .finish()
83    }
84}
85
86impl ServerConfigBuilder {
87    /// Creates a new builder from an `X509Source`.
88    ///
89    /// Defaults:
90    /// - Authorization: [`crate::authorizer::any`], which accepts any authenticated
91    ///   SPIFFE ID from any trust domain accepted by the configured trust-domain policy.
92    ///   By default, this means every trust domain in the source bundle set.
93    /// - Trust domain policy: [`TrustDomainPolicy::AnyInBundleSet`], which accepts any
94    ///   trust domain present in the source bundle set
95    /// - ALPN protocols: empty (no ALPN)
96    ///
97    /// Production deployments should usually configure a more specific authorizer.
98    /// Non-federated deployments should usually configure [`TrustDomainPolicy::LocalOnly`].
99    pub fn new(source: X509Source) -> Self {
100        Self {
101            source: Arc::new(source),
102            authorizer: Arc::new(crate::authorizer::any()),
103            trust_domain_policy: TrustDomainPolicy::default(),
104            alpn_protocols: Vec::new(),
105            config_customizer: None,
106        }
107    }
108
109    /// Sets the authorization policy for client SPIFFE IDs.
110    ///
111    /// Accepts any type that implements `Authorizer`, including closures.
112    ///
113    /// # Examples
114    ///
115    /// ```no_run
116    /// use spiffe_rustls::{authorizer, mtls_server};
117    ///
118    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
119    /// let source = spiffe::X509Source::new().await?;
120    ///
121    /// // Pass string literals directly
122    /// let config = mtls_server(source.clone())
123    ///     .authorize(authorizer::trust_domains([
124    ///         "example.org",
125    ///     ])?)
126    ///     .build()?;
127    ///
128    /// // Using a closure
129    /// let config = mtls_server(source.clone())
130    ///     .authorize(|id: &spiffe::SpiffeId| id.path().starts_with("/api/"))
131    ///     .build()?;
132    ///
133    /// // Using the Any authorizer (default)
134    /// let config = mtls_server(source)
135    ///     .authorize(authorizer::any())
136    ///     .build()?;
137    /// # Ok(())
138    /// # }
139    /// ```
140    #[must_use]
141    pub fn authorize<A: Authorizer>(mut self, authorizer: A) -> Self {
142        self.authorizer = Arc::new(authorizer);
143        self
144    }
145
146    /// Sets the trust domain policy.
147    ///
148    /// Defaults to [`TrustDomainPolicy::AnyInBundleSet`], which accepts any trust
149    /// domain present in the source bundle set. For non-federated deployments,
150    /// prefer [`TrustDomainPolicy::LocalOnly`] to restrict verification to the local
151    /// trust domain.
152    #[must_use]
153    pub fn trust_domain_policy(mut self, policy: TrustDomainPolicy) -> Self {
154        self.trust_domain_policy = policy;
155        self
156    }
157
158    /// Sets the ALPN (Application-Layer Protocol Negotiation) protocols.
159    ///
160    /// The protocols are advertised during the TLS handshake. Common values:
161    /// - `b"h2"` for HTTP/2 (required for gRPC)
162    /// - `b"http/1.1"` for HTTP/1.1
163    ///
164    /// Protocols should be specified in order of preference (most preferred first).
165    ///
166    /// # Examples
167    ///
168    /// ```no_run
169    /// use spiffe_rustls::mtls_server;
170    ///
171    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
172    /// let source = spiffe::X509Source::new().await?;
173    /// let config = mtls_server(source)
174    ///     .with_alpn_protocols([b"h2"])
175    ///     .build()?;
176    /// # Ok(())
177    /// # }
178    /// ```
179    #[must_use]
180    pub fn with_alpn_protocols<I, P>(mut self, protocols: I) -> Self
181    where
182        I: IntoIterator<Item = P>,
183        P: AsRef<[u8]>,
184    {
185        self.alpn_protocols = protocols.into_iter().map(|p| p.as_ref().to_vec()).collect();
186        self
187    }
188
189    /// Applies a customizer function to the `ServerConfig` after it's built.
190    ///
191    /// This is an **advanced** API for configuration not directly exposed by the builder.
192    /// The customizer is called **last**, after all other builder settings (including
193    /// ALPN) have been applied, and gives direct access to the underlying `rustls`
194    /// configuration.
195    ///
196    /// **Warning:** Replacing the verifier or resolver disables SPIFFE authentication.
197    /// Do not use this hook for that purpose; build a custom `rustls` configuration instead.
198    ///
199    /// # Examples
200    ///
201    /// ```no_run
202    /// use spiffe_rustls::mtls_server;
203    ///
204    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
205    /// let source = spiffe::X509Source::new().await?;
206    /// let config = mtls_server(source)
207    ///     .with_config_customizer(|cfg| {
208    ///         // Example: adjust cipher suite preferences
209    ///     })
210    ///     .build()?;
211    /// # Ok(())
212    /// # }
213    /// ```
214    #[must_use]
215    pub fn with_config_customizer<F>(mut self, customizer: F) -> Self
216    where
217        F: FnOnce(&mut ServerConfig) + Send + 'static,
218    {
219        self.config_customizer = Some(Box::new(customizer));
220        self
221    }
222
223    /// Builds the `rustls::ServerConfig`.
224    ///
225    /// # Errors
226    ///
227    /// Returns an error if:
228    ///
229    /// * the SPIFFE `X509Source` does not currently have an SVID,
230    /// * rustls crypto providers are not installed,
231    /// * or the material watcher cannot be initialized.
232    pub fn build(self) -> Result<ServerConfig> {
233        crate::crypto::ensure_crypto_provider_installed();
234
235        let watcher = MaterialWatcher::spawn(self.source)?;
236
237        let resolver: Arc<dyn ResolvesServerCert> =
238            Arc::new(resolve_server::SpiffeServerCertResolver {
239                watcher: watcher.clone(),
240            });
241
242        let verifier = Arc::new(SpiffeClientCertVerifier::new(
243            Arc::new(watcher),
244            self.authorizer,
245            self.trust_domain_policy,
246        ));
247
248        let mut cfg = ServerConfig::builder()
249            .with_client_cert_verifier(verifier)
250            .with_cert_resolver(resolver);
251
252        cfg.alpn_protocols = self.alpn_protocols;
253
254        // Apply customizer last
255        if let Some(customizer) = self.config_customizer {
256            customizer(&mut cfg);
257        }
258
259        Ok(cfg)
260    }
261}
262
263mod resolve_server {
264    use crate::resolve::MaterialWatcher;
265    use rustls::server::ResolvesServerCert;
266    use rustls::sign::CertifiedKey;
267    use std::sync::Arc;
268
269    #[derive(Clone, Debug)]
270    pub(crate) struct SpiffeServerCertResolver {
271        pub watcher: MaterialWatcher,
272    }
273
274    impl ResolvesServerCert for SpiffeServerCertResolver {
275        fn resolve(
276            &self,
277            _client_hello: rustls::server::ClientHello<'_>,
278        ) -> Option<Arc<CertifiedKey>> {
279            Some(Arc::clone(&self.watcher.current().certified_key))
280        }
281    }
282}