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}