spiffe_rustls/
client.rs

1use crate::error::Result;
2use crate::resolve::MaterialWatcher;
3use crate::types::{authorize_any, AuthorizeSpiffeId};
4use crate::verifier::SpiffeServerCertVerifier;
5use rustls::client::ResolvesClientCert;
6use rustls::ClientConfig;
7use spiffe::{TrustDomain, X509Source};
8use std::sync::Arc;
9
10/// Configuration options for [`ClientConfigBuilder`].
11///
12/// These options control trust bundle selection and server authorization.
13#[derive(Clone)]
14pub struct ClientConfigOptions {
15    /// Trust domain whose bundle is used as the verification root set.
16    pub trust_domain: TrustDomain,
17
18    /// Authorization hook invoked with the server SPIFFE ID.
19    ///
20    /// The hook receives the SPIFFE ID extracted from the server certificate’s
21    /// URI SAN and must return `true` to allow the connection.
22    pub authorize_server: AuthorizeSpiffeId,
23}
24
25impl std::fmt::Debug for ClientConfigOptions {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        f.debug_struct("ClientConfigOptions")
28            .field("trust_domain", &self.trust_domain)
29            .field("authorize_server", &"<authorize_fn>")
30            .finish()
31    }
32}
33
34impl ClientConfigOptions {
35    /// Creates options that authenticate the server but allow any SPIFFE ID.
36    ///
37    /// This disables authorization while retaining full TLS authentication.
38    /// Use only if authorization is performed at another layer.
39    pub fn allow_any(trust_domain: TrustDomain) -> Self {
40        Self {
41            trust_domain,
42            authorize_server: authorize_any(),
43        }
44    }
45}
46
47/// Builds a [`rustls::ClientConfig`] backed by a live SPIFFE `X509Source`.
48///
49/// The resulting client configuration:
50///
51/// * presents the current SPIFFE X.509 SVID as the client certificate
52/// * validates the server certificate chain against the trust domain bundle
53/// * authorizes the server by SPIFFE ID (URI SAN)
54///
55/// The builder retains an `Arc<X509Source>`. When the underlying SVID or trust
56/// bundle is rotated by the SPIRE agent, **new TLS handshakes automatically use
57/// the updated material**.
58///
59/// ## Authorization
60///
61/// Server authorization is performed by invoking the provided
62/// [`AuthorizeSpiffeId`] hook with the server’s SPIFFE ID extracted from the
63/// certificate’s URI SAN.
64///
65/// Use [`ClientConfigOptions::allow_any`] to disable authorization while
66/// retaining full TLS authentication.
67#[derive(Debug)]
68pub struct ClientConfigBuilder {
69    source: Arc<X509Source>,
70    opts: ClientConfigOptions,
71}
72
73impl ClientConfigBuilder {
74    /// Creates a new builder from an `X509Source` and options.
75    pub fn new(source: Arc<X509Source>, opts: ClientConfigOptions) -> Self {
76        Self { source, opts }
77    }
78
79    /// Builds the `rustls::ClientConfig`.
80    ///
81    /// The returned configuration:
82    ///
83    /// * presents the current SPIFFE X.509 SVID as the client certificate
84    /// * validates the server certificate chain against the configured trust domain
85    /// * authorizes the server by SPIFFE ID (URI SAN)
86    ///
87    /// The configuration is backed by a live [`X509Source`]. When the underlying
88    /// SVID or trust bundle is rotated by the SPIRE agent, **new TLS handshakes
89    /// automatically use the updated material**.
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if:
94    ///
95    /// * the Rustls crypto provider is not installed
96    /// * no current X.509 SVID is available from the `X509Source`
97    /// * the trust bundle for the configured trust domain is missing
98    /// * building the underlying Rustls certificate verifier fails
99    pub fn build(self) -> Result<ClientConfig> {
100        crate::crypto::ensure_crypto_provider_installed();
101
102        let watcher = MaterialWatcher::new(self.source, self.opts.trust_domain)?;
103
104        let resolver: Arc<dyn ResolvesClientCert> =
105            Arc::new(resolve_client::SpiffeClientCertResolver {
106                watcher: watcher.clone(),
107            });
108
109        let verifier = Arc::new(SpiffeServerCertVerifier::from_watcher(
110            watcher.clone(),
111            self.opts.authorize_server,
112        ));
113
114        let cfg = ClientConfig::builder()
115            .dangerous()
116            .with_custom_certificate_verifier(verifier)
117            .with_client_cert_resolver(resolver);
118
119        Ok(cfg)
120    }
121}
122
123mod resolve_client {
124    use crate::resolve::MaterialWatcher;
125    use rustls::client::ResolvesClientCert;
126    use rustls::sign::CertifiedKey;
127    use std::sync::Arc;
128
129    #[derive(Clone, Debug)]
130    pub(crate) struct SpiffeClientCertResolver {
131        pub watcher: MaterialWatcher,
132    }
133
134    impl ResolvesClientCert for SpiffeClientCertResolver {
135        fn resolve(
136            &self,
137            _acceptable_issuers: &[&[u8]],
138            _sigschemes: &[rustls::SignatureScheme],
139        ) -> Option<Arc<CertifiedKey>> {
140            Some(self.watcher.current().certified_key.clone())
141        }
142
143        fn has_certs(&self) -> bool {
144            true
145        }
146    }
147}