use std::{
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use rustls::{
ServerConfig,
pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer},
server::WebPkiClientVerifier,
};
use rustls_config_stream::{ServerConfigStreamBuilder, ServerConfigStreamError};
use spiffe::{TrustDomain, WorkloadApiClient, WorkloadApiError, X509Context};
use tokio_stream::Stream;
pub use rustls_config_stream::ServerConfigProvider;
#[cfg(feature = "tracing")]
use tracing::debug;
use crate::TrustDomainStore;
pub struct SpiffeServerConfigStreamBuilder {
trust_domains: Vec<TrustDomain>,
client: Option<WorkloadApiClient>,
}
impl SpiffeServerConfigStreamBuilder {
const fn new(trust_domains: Vec<TrustDomain>) -> Self {
Self {
trust_domains,
client: None,
}
}
}
impl ServerConfigStreamBuilder for SpiffeServerConfigStreamBuilder {
type ConfigStream = SpiffeServerConfigStream;
async fn build(&mut self) -> Result<Self::ConfigStream, ServerConfigStreamError> {
let client = if let Some(client) = &mut self.client {
client
} else {
&mut WorkloadApiClient::connect_env()
.await
.map_err(|e| ServerConfigStreamError::StreamBuilderError(e.into()))?
};
Ok(SpiffeServerConfigStream {
trust_domains: self.trust_domains.clone(),
inner: Pin::from(Box::from(
client
.stream_x509_contexts()
.await
.map_err(|e| ServerConfigStreamError::StreamError(e.into()))?,
)),
})
}
}
pub struct SpiffeServerConfigStream {
inner:
Pin<Box<dyn Stream<Item = Result<X509Context, WorkloadApiError>> + Send + Sync + 'static>>,
trust_domains: Vec<TrustDomain>,
}
impl TrustDomainStore for SpiffeServerConfigStream {
fn get_trust_domains(&self) -> &Vec<TrustDomain> {
&self.trust_domains
}
}
impl SpiffeServerConfigStream {
#[must_use]
pub const fn builder(trust_domains: Vec<TrustDomain>) -> SpiffeServerConfigStreamBuilder {
SpiffeServerConfigStreamBuilder::new(trust_domains)
}
fn build_server_config(
&self,
x509_context: &X509Context,
) -> Result<Arc<ServerConfig>, ServerConfigStreamError> {
let roots = self.build_root_store(x509_context.bundle_set());
if roots.is_empty() {
return Err(ServerConfigStreamError::MissingRoots);
}
let verifier = WebPkiClientVerifier::builder(roots)
.build()
.map_err(ServerConfigStreamError::VerifierBuilderError)?;
let svid = x509_context
.default_svid()
.ok_or(ServerConfigStreamError::MissingCertifiedKey)?;
#[cfg(feature = "tracing")]
debug!(workload_identity = %svid.spiffe_id());
let config = ServerConfig::builder()
.with_client_cert_verifier(verifier)
.with_single_cert(
svid.cert_chain()
.iter()
.map(|c| CertificateDer::from(c.as_bytes().to_owned()))
.collect(),
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
svid.private_key().as_bytes().to_owned(),
)),
)
.map_err(ServerConfigStreamError::RustlsError)?;
Ok(Arc::from(config))
}
}
impl Stream for SpiffeServerConfigStream {
type Item = Result<Arc<ServerConfig>, ServerConfigStreamError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.inner.as_mut().poll_next(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some(Err(err))) => {
Poll::Ready(Some(Err(ServerConfigStreamError::StreamError(err.into()))))
}
Poll::Ready(Some(Ok(x509_context))) => match self.build_server_config(&x509_context) {
Ok(config) => Poll::Ready(Some(Ok(config))),
Err(err) => Poll::Ready(Some(Err(err))),
},
}
}
}