spiffe_rustls/
client.rs

1use crate::error::Result;
2use crate::resolve::MaterialWatcher;
3use crate::types::{AuthorizeSpiffeId, authorize_any};
4use crate::verifier::SpiffeServerCertVerifier;
5use rustls::ClientConfig;
6use rustls::client::ResolvesClientCert;
7use spiffe::{TrustDomain, X509Source};
8use std::sync::Arc;
9
10/// Options for building a SPIFFE-aware `rustls::ClientConfig`.
11#[derive(Clone)]
12pub struct ClientConfigOptions {
13    /// Trust domain whose bundle is used as the verification root set.
14    pub trust_domain: TrustDomain,
15
16    /// Authorization hook invoked with the server SPIFFE ID.
17    ///
18    /// Returning `false` rejects the peer even if the certificate chain is valid.
19    pub authorize_server: AuthorizeSpiffeId,
20}
21
22impl ClientConfigOptions {
23    /// Creates options that accept any server SPIFFE ID for the given trust domain.
24    ///
25    /// Authentication still happens via bundle verification; only authorization is permissive.
26    pub fn allow_any(trust_domain: TrustDomain) -> Self {
27        Self {
28            trust_domain,
29            authorize_server: authorize_any(),
30        }
31    }
32}
33
34/// Builds a `rustls::ClientConfig` backed by an [`spiffe::X509Source`].
35///
36/// The resulting config:
37/// - presents the current SVID as the client certificate
38/// - verifies server certificates using the trust domain bundle
39/// - authorizes the server by SPIFFE ID (URI SAN)
40///
41/// New handshakes use the latest SVID/bundle material after rotations.
42pub struct ClientConfigBuilder {
43    source: Arc<X509Source>,
44    opts: ClientConfigOptions,
45}
46
47impl ClientConfigBuilder {
48    /// Creates a new builder from an `X509Source` and options.
49    pub fn new(source: Arc<X509Source>, opts: ClientConfigOptions) -> Self {
50        Self { source, opts }
51    }
52
53    /// Builds the `rustls::ClientConfig`.
54    pub async fn build(self) -> Result<ClientConfig> {
55        crate::crypto::ensure_crypto_provider_installed();
56
57        let watcher = MaterialWatcher::new(self.source, self.opts.trust_domain).await?;
58
59        let resolver: Arc<dyn ResolvesClientCert> =
60            Arc::new(resolve_client::SpiffeClientCertResolver {
61                watcher: watcher.clone(),
62            });
63
64        let verifier = Arc::new(SpiffeServerCertVerifier::new(
65            Arc::new(watcher.clone()),
66            self.opts.authorize_server,
67        )?);
68
69        let cfg = ClientConfig::builder()
70            .dangerous()
71            .with_custom_certificate_verifier(verifier)
72            .with_client_cert_resolver(resolver);
73
74        Ok(cfg)
75    }
76}
77
78mod resolve_client {
79    use crate::resolve::MaterialWatcher;
80    use rustls::client::ResolvesClientCert;
81    use rustls::sign::CertifiedKey;
82    use std::sync::Arc;
83
84    #[derive(Clone, Debug)]
85    pub(crate) struct SpiffeClientCertResolver {
86        pub watcher: MaterialWatcher,
87    }
88
89    impl ResolvesClientCert for SpiffeClientCertResolver {
90        fn resolve(
91            &self,
92            _acceptable_issuers: &[&[u8]],
93            _sigschemes: &[rustls::SignatureScheme],
94        ) -> Option<Arc<CertifiedKey>> {
95            Some(self.watcher.current().certified_key.clone())
96        }
97
98        fn has_certs(&self) -> bool {
99            true
100        }
101    }
102}