rustls_spiffe/
client_stream.rs1use std::{
4 pin::Pin,
5 sync::Arc,
6 task::{Context, Poll},
7};
8
9use rustls::{
10 ClientConfig,
11 pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer},
12};
13use rustls_config_stream::{ClientConfigStreamBuilder, ClientConfigStreamError};
14use spiffe::{TrustDomain, WorkloadApiClient, WorkloadApiError, X509Context};
15use tokio_stream::Stream;
16
17pub use rustls_config_stream::ClientConfigProvider;
18
19#[cfg(feature = "tracing")]
20use tracing::debug;
21
22use crate::TrustDomainStore;
23
24pub struct SpiffeClientConfigStreamBuilder {
30 trust_domains: Vec<TrustDomain>,
31 client: Option<WorkloadApiClient>,
32}
33
34impl SpiffeClientConfigStreamBuilder {
35 const fn new(trust_domains: Vec<TrustDomain>) -> Self {
38 Self {
39 trust_domains,
40 client: None,
41 }
42 }
43}
44
45impl ClientConfigStreamBuilder for SpiffeClientConfigStreamBuilder {
46 type ConfigStream = SpiffeClientConfigStream;
47
48 async fn build(&mut self) -> Result<Self::ConfigStream, ClientConfigStreamError> {
49 let client = if let Some(client) = &mut self.client {
50 client
51 } else {
52 &mut WorkloadApiClient::connect_env()
53 .await
54 .map_err(|e| ClientConfigStreamError::StreamBuilderError(e.into()))?
55 };
56 Ok(SpiffeClientConfigStream {
57 trust_domains: self.trust_domains.clone(),
58 inner: Pin::from(Box::from(
59 client
60 .stream_x509_contexts()
61 .await
62 .map_err(|e| ClientConfigStreamError::StreamError(e.into()))?,
63 )),
64 })
65 }
66}
67
68pub struct SpiffeClientConfigStream {
84 inner:
85 Pin<Box<dyn Stream<Item = Result<X509Context, WorkloadApiError>> + Send + Sync + 'static>>,
86 trust_domains: Vec<TrustDomain>,
87}
88
89impl TrustDomainStore for SpiffeClientConfigStream {
90 fn get_trust_domains(&self) -> &Vec<TrustDomain> {
91 &self.trust_domains
92 }
93}
94
95impl SpiffeClientConfigStream {
96 #[must_use]
99 pub const fn builder(trust_domains: Vec<TrustDomain>) -> SpiffeClientConfigStreamBuilder {
100 SpiffeClientConfigStreamBuilder::new(trust_domains)
101 }
102
103 fn build_client_config(
104 &self,
105 x509_context: &X509Context,
106 ) -> Result<Arc<ClientConfig>, ClientConfigStreamError> {
107 let roots = self.build_root_store(x509_context.bundle_set());
108 if roots.is_empty() {
109 return Err(ClientConfigStreamError::MissingRoots);
110 }
111 let svid = x509_context
112 .default_svid()
113 .ok_or(ClientConfigStreamError::MissingCertifiedKey)?;
114
115 #[cfg(feature = "tracing")]
116 debug!(workload_identity = %svid.spiffe_id());
117
118 let config = ClientConfig::builder()
119 .with_root_certificates(roots)
120 .with_client_auth_cert(
121 svid.cert_chain()
122 .iter()
123 .map(|c| CertificateDer::from(c.as_bytes().to_owned()))
124 .collect(),
125 PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
126 svid.private_key().as_bytes().to_owned(),
127 )),
128 )
129 .map_err(ClientConfigStreamError::RustlsError)?;
130 Ok(Arc::from(config))
131 }
132}
133
134impl Stream for SpiffeClientConfigStream {
135 type Item = Result<Arc<ClientConfig>, ClientConfigStreamError>;
136
137 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
138 match self.inner.as_mut().poll_next(cx) {
139 Poll::Pending => Poll::Pending,
140 Poll::Ready(None) => Poll::Ready(None),
141 Poll::Ready(Some(Err(err))) => {
142 Poll::Ready(Some(Err(ClientConfigStreamError::StreamError(err.into()))))
143 }
144 Poll::Ready(Some(Ok(x509_context))) => match self.build_client_config(&x509_context) {
145 Ok(config) => Poll::Ready(Some(Ok(config))),
146 Err(err) => Poll::Ready(Some(Err(err))),
147 },
148 }
149 }
150}