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}