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