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}