Skip to main content

rustls_spiffe/
server_stream.rs

1// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2
3use std::{
4    pin::Pin,
5    sync::Arc,
6    task::{Context, Poll},
7};
8
9use rustls::{
10    ServerConfig,
11    pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer},
12    server::WebPkiClientVerifier,
13};
14use rustls_config_stream::{ServerConfigStreamBuilder, ServerConfigStreamError};
15use spiffe::{TrustDomain, WorkloadApiClient, WorkloadApiError, X509Context};
16use tokio_stream::Stream;
17
18pub use rustls_config_stream::ServerConfigProvider;
19
20#[cfg(feature = "tracing")]
21use tracing::debug;
22
23use crate::TrustDomainStore;
24
25/// Builder for a [`SpiffeServerConfigStream`] that provides [`rustls::ServerConfig`]
26/// objects built w/ trust bundles and workload X509-SVID from SPIFFE.
27///
28/// The builder controls which SPIFFE trust domains are allowed to authenticate
29/// clients.
30pub struct SpiffeServerConfigStreamBuilder {
31    trust_domains: Vec<TrustDomain>,
32    client: Option<WorkloadApiClient>,
33}
34
35impl SpiffeServerConfigStreamBuilder {
36    /// Create a builder that can create [`SpiffeServerConfigStream`] objects
37    /// with the provided SPIFFE trust domains.
38    const fn new(trust_domains: Vec<TrustDomain>) -> Self {
39        Self {
40            trust_domains,
41            client: None,
42        }
43    }
44}
45impl ServerConfigStreamBuilder for SpiffeServerConfigStreamBuilder {
46    type ConfigStream = SpiffeServerConfigStream;
47
48    async fn build(&mut self) -> Result<Self::ConfigStream, ServerConfigStreamError> {
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| ServerConfigStreamError::StreamBuilderError(e.into()))?
55        };
56        Ok(SpiffeServerConfigStream {
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| ServerConfigStreamError::StreamError(e.into()))?,
63            )),
64        })
65    }
66}
67
68/// A stream that yields updated [`rustls::ServerConfig`] values derived from the
69/// SPIFFE Workload API X509-SVID and Trust Bundles.
70///
71/// Each yielded config:
72/// * Uses the workload's default SVID (certificate chain + private key).
73/// * Requires (and verifies) client certificates whose trust anchors come from
74///   the configured SPIFFE trust domains.
75///
76/// # Behavior
77///
78/// * If the Workload API stream returns an error, this stream yields
79///   a [`ServerConfigStreamError::StreamError`] wrapping the original
80///   [`WorkloadApiError`].
81/// * If an update lacks roots/SVID or the verifier cannot be built, the error
82///   is returned on the stream as a [`ServerConfigStreamError`]
83///
84/// # Usage
85///
86/// ```rust
87/// use rustls_spiffe::{SpiffeServerConfigStream, ServerConfigProvider};
88/// use tracing::warn;
89///
90/// async fn run() {
91///     let config_stream_builder =
92///         SpiffeServerConfigStream::builder(vec!["example.org".try_into().unwrap()]);
93///     let config_provider = ServerConfigProvider::start(config_stream_builder)
94///         .await
95///         .unwrap();
96///     let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
97///         .await
98///         .unwrap();
99///     loop {
100///         let (stream, _) = listener.accept().await.unwrap();
101///
102///         let acceptor =
103///             tokio_rustls::LazyConfigAcceptor::new(rustls::server::Acceptor::default(), stream);
104///         tokio::pin!(acceptor);
105///
106///         let config_provider = config_provider.clone();
107///         match acceptor.as_mut().await {
108///             Ok(start) => {
109///                 tokio::spawn(async move {
110///                     if !config_provider.stream_healthy() {
111///                         warn!(
112///                             "config provider does not have healthy stream; TLS config may be out of date"
113///                         );
114///                     }
115///                     let config = config_provider.get_config();
116///                     match start.into_stream(config).await {
117///                         Ok(stream) => {
118///                             // serve some app (e.g. hyper, tower, axum)
119///                             todo!();
120///                         }
121///                         Err(err) => {
122///                             // add error handling as-desired
123///                             todo!()
124///                         },
125///                     }
126///                 });
127///             }
128///             Err(err) => {
129///                 // add error-handling as-desired
130///                 todo!()
131///             },
132///         }
133///     }
134/// }
135/// ```
136pub struct SpiffeServerConfigStream {
137    inner:
138        Pin<Box<dyn Stream<Item = Result<X509Context, WorkloadApiError>> + Send + Sync + 'static>>,
139    trust_domains: Vec<TrustDomain>,
140}
141
142impl TrustDomainStore for SpiffeServerConfigStream {
143    fn get_trust_domains(&self) -> &Vec<TrustDomain> {
144        &self.trust_domains
145    }
146}
147
148impl SpiffeServerConfigStream {
149    /// Create a builder that can create [`SpiffeServerConfigStream`] objects
150    /// with the provided SPIFFE trust domains.
151    #[must_use]
152    pub const fn builder(trust_domains: Vec<TrustDomain>) -> SpiffeServerConfigStreamBuilder {
153        SpiffeServerConfigStreamBuilder::new(trust_domains)
154    }
155
156    fn build_server_config(
157        &self,
158        x509_context: &X509Context,
159    ) -> Result<Arc<ServerConfig>, ServerConfigStreamError> {
160        let roots = self.build_root_store(x509_context.bundle_set());
161        if roots.is_empty() {
162            return Err(ServerConfigStreamError::MissingRoots);
163        }
164        let verifier = WebPkiClientVerifier::builder(roots)
165            .build()
166            .map_err(ServerConfigStreamError::VerifierBuilderError)?;
167        let svid = x509_context
168            .default_svid()
169            .ok_or(ServerConfigStreamError::MissingCertifiedKey)?;
170
171        #[cfg(feature = "tracing")]
172        debug!(workload_identity = %svid.spiffe_id());
173
174        let config = ServerConfig::builder()
175            .with_client_cert_verifier(verifier)
176            .with_single_cert(
177                svid.cert_chain()
178                    .iter()
179                    .map(|c| CertificateDer::from(c.as_bytes().to_owned()))
180                    .collect(),
181                PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
182                    svid.private_key().as_bytes().to_owned(),
183                )),
184            )
185            .map_err(ServerConfigStreamError::RustlsError)?;
186        Ok(Arc::from(config))
187    }
188}
189
190impl Stream for SpiffeServerConfigStream {
191    type Item = Result<Arc<ServerConfig>, ServerConfigStreamError>;
192
193    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
194        match self.inner.as_mut().poll_next(cx) {
195            Poll::Pending => Poll::Pending,
196            Poll::Ready(None) => Poll::Ready(None),
197            Poll::Ready(Some(Err(err))) => {
198                Poll::Ready(Some(Err(ServerConfigStreamError::StreamError(err.into()))))
199            }
200            Poll::Ready(Some(Ok(x509_context))) => match self.build_server_config(&x509_context) {
201                Ok(config) => Poll::Ready(Some(Ok(config))),
202                Err(err) => Poll::Ready(Some(Err(err))),
203            },
204        }
205    }
206}