#![expect(missing_docs, reason = "integration test")]
#![expect(
clippy::tests_outside_test_module,
reason = "https://github.com/rust-lang/rust-clippy/issues/11024"
)]
#![expect(unused_crate_dependencies, reason = "used in the library target")]
use rustls::pki_types::ServerName;
use spiffe::{TrustDomain, X509Source};
use spiffe_rustls::{authorizer, mtls_client, mtls_server};
use std::env;
use std::sync::Arc;
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::{TlsAcceptor, TlsConnector};
fn env_var(name: &str) -> Result<String, String> {
let s = env::var_os(name).ok_or_else(|| format!("missing required env var: {name}"))?;
s.into_string().map_err(|s| {
format!(
"required env var {name} is not valid UTF-8: {}",
s.display()
)
})
}
#[tokio::test]
#[ignore = "requires two running SPIFFE Workload API endpoints (primary + federated)"]
async fn integration_mtls_federation_cross_trust_domain() -> Result<(), Box<dyn std::error::Error>>
{
let primary_socket = env_var("SPIFFE_ENDPOINT_SOCKET")?;
let federated_socket = env_var("SPIFFE_ENDPOINT_SOCKET_FEDERATED")?;
let primary = X509Source::builder()
.endpoint(primary_socket)
.build()
.await?;
let federated = X509Source::builder()
.endpoint(federated_socket)
.build()
.await?;
{
let td_primary: TrustDomain = "example.org".try_into()?;
let td_federated: TrustDomain = "example-federated.org".try_into()?;
let p = primary.x509_context()?;
let f = federated.x509_context()?;
if p.bundle_set().get(&td_federated).is_none() {
return Err(
"primary bundle set missing example-federated.org (federation not established)"
.into(),
);
}
if f.bundle_set().get(&td_primary).is_none() {
return Err(
"federated bundle set missing example.org (federation not established)".into(),
);
}
}
let server_auth = authorizer::trust_domains(["example.org"])?;
let client_auth = authorizer::trust_domains(["example-federated.org"])?;
let server_cfg = mtls_server(federated.clone())
.authorize(server_auth)
.build()?;
let client_cfg = mtls_client(primary.clone())
.authorize(client_auth)
.build()?;
let acceptor = TlsAcceptor::from(Arc::new(server_cfg));
let connector = TlsConnector::from(Arc::new(client_cfg));
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let server_task = tokio::spawn(async move {
let (tcp, _) = listener.accept().await?;
acceptor
.accept(tcp)
.await
.map(|_tls| ())
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { Box::new(e) })
});
let tcp = TcpStream::connect(addr).await?;
let server_name = ServerName::try_from("example.org")?;
let client_res = connector.connect(server_name, tcp).await;
let server_res = server_task.await.expect("server task panicked");
if let Err(e) = client_res {
return Err(format!("client handshake failed: {e:?}").into());
}
if let Err(e) = server_res {
return Err(format!("server accept failed: {e:?}").into());
}
primary.shutdown().await;
federated.shutdown().await;
Ok(())
}