Skip to content

Commit 67013e0

Browse files
authored
refactor(spiffe-rustls): make builders sync and refresh material pipeline (#210)
Signed-off-by: Max Lambrecht <[email protected]>
1 parent 4341d35 commit 67013e0

File tree

13 files changed

+491
-218
lines changed

13 files changed

+491
-218
lines changed

spiffe-rustls-grpc-examples/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ rust-version = "1.83"
66
publish = false
77

88
[dependencies]
9-
spiffe = "0.7.4"
9+
spiffe = "0.8.0"
1010
spiffe-rustls = { path = "../spiffe-rustls", features = ["ring"] }
1111

1212
# gRPC stack

spiffe-rustls-grpc-examples/src/bin/grpc_client_mtls.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3232
};
3333

3434
// Build rustls client config backed by SPIFFE X509Source.
35-
let mut client_cfg = ClientConfigBuilder::new(source.clone(), opts)
36-
.build()
37-
.await?;
35+
let mut client_cfg = ClientConfigBuilder::new(source.clone(), opts).build()?;
3836

3937
// gRPC requires HTTP/2 via ALPN.
4038
client_cfg.alpn_protocols = vec![b"h2".to_vec()];

spiffe-rustls-grpc-examples/src/bin/grpc_server_mtls.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3737
source.clone(),
3838
ServerConfigOptions::allow_any("example.org".try_into()?),
3939
)
40-
.build()
41-
.await?;
40+
.build()?;
4241

4342
// gRPC requires HTTP/2 via ALPN.
4443
server_cfg.alpn_protocols = vec![b"h2".to_vec()];

spiffe-rustls/Cargo.toml

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,8 @@ readme = "README.md"
1010
keywords = ["spiffe", "spire", "rustls", "mtls", "tls"]
1111
categories = ["network-programming", "cryptography", "authentication"]
1212

13-
[features]
14-
default = ["ring"]
15-
16-
# Crypto backend selection (rustls feature passthrough)
17-
ring = ["rustls/ring"]
18-
aws-lc-rs = ["rustls/aws_lc_rs"]
19-
20-
tcp-examples = ["dep:tokio-rustls"]
21-
22-
# Internal: enables deps used by integration tests
23-
integration-tests = ["dep:tokio-rustls"]
24-
2513
[dependencies]
26-
spiffe = "0.7.4"
14+
spiffe = "0.8.0"
2715
rustls = { version = "0.23", default-features = false, features = ["std"] }
2816
tokio = { version = "1", default-features = false, features = ["rt", "sync"] }
2917
tokio-util = "0.7"
@@ -48,6 +36,19 @@ tokio = { version = "1", default-features = false, features = [
4836
"sync",
4937
] }
5038

39+
[features]
40+
default = ["ring"]
41+
42+
# Crypto backend selection (rustls feature passthrough)
43+
ring = ["rustls/ring"]
44+
aws-lc-rs = ["rustls/aws_lc_rs"]
45+
46+
tcp-examples = ["dep:tokio-rustls"]
47+
48+
# Internal: enables deps used by integration tests
49+
integration-tests = ["dep:tokio-rustls"]
50+
51+
5152
[[example]]
5253
name = "mtls_tcp_server"
5354
path = "examples/mtls_tcp_server.rs"

spiffe-rustls/examples/mtls_tcp_client.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ async fn main() -> anyhow::Result<()> {
2020
}),
2121
};
2222

23-
let client_cfg = ClientConfigBuilder::new(source.clone(), opts)
24-
.build()
25-
.await?;
23+
let client_cfg = ClientConfigBuilder::new(source.clone(), opts).build()?;
2624
let connector = TlsConnector::from(Arc::new(client_cfg));
2725

2826
let tcp = TcpStream::connect("127.0.0.1:8443").await?;
@@ -42,6 +40,6 @@ async fn main() -> anyhow::Result<()> {
4240

4341
let _ = tls.shutdown().await;
4442

45-
source.shutdown().await?;
43+
source.shutdown().await;
4644
Ok(())
4745
}

spiffe-rustls/examples/mtls_tcp_server.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ async fn main() -> anyhow::Result<()> {
2020

2121
let opts = ServerConfigOptions::allow_any("example.org".try_into()?);
2222

23-
let server_cfg = ServerConfigBuilder::new(source.clone(), opts)
24-
.build()
25-
.await?;
23+
let server_cfg = ServerConfigBuilder::new(source.clone(), opts).build()?;
2624
let acceptor = TlsAcceptor::from(Arc::new(server_cfg));
2725

2826
let addr = "127.0.0.1:8443";
@@ -66,6 +64,6 @@ async fn main() -> anyhow::Result<()> {
6664
}
6765
}
6866

69-
source.shutdown().await?;
67+
source.shutdown().await;
7068
Ok(())
7169
}

spiffe-rustls/src/client.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ pub struct ClientConfigOptions {
2222
pub authorize_server: AuthorizeSpiffeId,
2323
}
2424

25+
impl std::fmt::Debug for ClientConfigOptions {
26+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27+
f.debug_struct("ClientConfigOptions")
28+
.field("trust_domain", &self.trust_domain)
29+
.field("authorize_server", &"<authorize_fn>")
30+
.finish()
31+
}
32+
}
33+
2534
impl ClientConfigOptions {
2635
/// Creates options that authenticate the server but allow any SPIFFE ID.
2736
///
@@ -55,6 +64,7 @@ impl ClientConfigOptions {
5564
///
5665
/// Use [`ClientConfigOptions::allow_any`] to disable authorization while
5766
/// retaining full TLS authentication.
67+
#[derive(Debug)]
5868
pub struct ClientConfigBuilder {
5969
source: Arc<X509Source>,
6070
opts: ClientConfigOptions,
@@ -67,20 +77,39 @@ impl ClientConfigBuilder {
6777
}
6878

6979
/// Builds the `rustls::ClientConfig`.
70-
pub async fn build(self) -> Result<ClientConfig> {
80+
///
81+
/// The returned configuration:
82+
///
83+
/// * presents the current SPIFFE X.509 SVID as the client certificate
84+
/// * validates the server certificate chain against the configured trust domain
85+
/// * authorizes the server by SPIFFE ID (URI SAN)
86+
///
87+
/// The configuration is backed by a live [`X509Source`]. When the underlying
88+
/// SVID or trust bundle is rotated by the SPIRE agent, **new TLS handshakes
89+
/// automatically use the updated material**.
90+
///
91+
/// # Errors
92+
///
93+
/// Returns an error if:
94+
///
95+
/// * the Rustls crypto provider is not installed
96+
/// * no current X.509 SVID is available from the `X509Source`
97+
/// * the trust bundle for the configured trust domain is missing
98+
/// * building the underlying Rustls certificate verifier fails
99+
pub fn build(self) -> Result<ClientConfig> {
71100
crate::crypto::ensure_crypto_provider_installed();
72101

73-
let watcher = MaterialWatcher::new(self.source, self.opts.trust_domain).await?;
102+
let watcher = MaterialWatcher::new(self.source, self.opts.trust_domain)?;
74103

75104
let resolver: Arc<dyn ResolvesClientCert> =
76105
Arc::new(resolve_client::SpiffeClientCertResolver {
77106
watcher: watcher.clone(),
78107
});
79108

80-
let verifier = Arc::new(SpiffeServerCertVerifier::new(
81-
Arc::new(watcher.clone()),
109+
let verifier = Arc::new(SpiffeServerCertVerifier::from_watcher(
110+
watcher.clone(),
82111
self.opts.authorize_server,
83-
)?);
112+
));
84113

85114
let cfg = ClientConfig::builder()
86115
.dangerous()

spiffe-rustls/src/lib.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
#![deny(missing_docs)]
2+
#![deny(unsafe_code)]
3+
#![warn(missing_debug_implementations)]
4+
#![warn(clippy::all)]
5+
#![warn(clippy::pedantic)]
6+
#![allow(clippy::module_name_repetitions)]
7+
#![allow(clippy::must_use_candidate)]
8+
19
//! # spiffe-rustls
210
//!
311
//! `spiffe-rustls` integrates [`rustls`] with SPIFFE/SPIRE using a live
@@ -28,9 +36,7 @@
2836
//! }),
2937
//! };
3038
//!
31-
//! let client_config = ClientConfigBuilder::new(source, opts)
32-
//! .build()
33-
//! .await?;
39+
//! let client_config = ClientConfigBuilder::new(source, opts).build()?;
3440
//! # Ok(())
3541
//! # }
3642
//! ```

spiffe-rustls/src/material.rs

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
use crate::error::{Error, Result};
22
use log::debug;
3-
use rustls::pki_types::CertificateDer;
3+
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
44
use rustls::RootCertStore;
55
use std::sync::Arc;
66

77
#[derive(Clone, Debug)]
88
pub(crate) struct MaterialSnapshot {
9+
pub generation: u64,
910
pub certified_key: Arc<rustls::sign::CertifiedKey>,
1011
pub roots: Arc<RootCertStore>,
1112
}
1213

13-
pub(crate) fn roots_from_bundle_der(bundle_authorities: &[Vec<u8>]) -> Result<Arc<RootCertStore>> {
14+
/// Build a `RootCertStore` from DER-encoded certificate authorities.
15+
///
16+
/// ## Errors
17+
///
18+
/// Returns [`Error::Internal`] if no certificates are accepted into the store.
19+
pub(crate) fn roots_from_certs(certs: &[CertificateDer<'static>]) -> Result<Arc<RootCertStore>> {
1420
let mut store = RootCertStore::empty();
1521

16-
let ders: Vec<CertificateDer<'static>> = bundle_authorities
17-
.iter()
18-
.cloned()
19-
.map(CertificateDer::from)
20-
.collect();
22+
let added = store.add_parsable_certificates(certs.iter().cloned());
2123

22-
let added = store.add_parsable_certificates(ders);
23-
24-
debug!("loaded root cert(s): {:?}", added);
24+
debug!("loaded root cert(s): {added:?}");
2525

2626
if store.is_empty() {
2727
return Err(Error::Internal(
@@ -32,18 +32,17 @@ pub(crate) fn roots_from_bundle_der(bundle_authorities: &[Vec<u8>]) -> Result<Ar
3232
Ok(Arc::new(store))
3333
}
3434

35-
pub(crate) fn certified_key_from_der(
36-
cert_chain_der: &[Vec<u8>],
35+
/// Build a rustls `CertifiedKey` from a cert chain and a PKCS#8 private key.
36+
///
37+
/// ## Errors
38+
///
39+
/// Returns [`Error::CertifiedKey`] if the crypto provider is not installed
40+
/// or the key can't be loaded.
41+
pub(crate) fn certified_key_from_chain_and_key(
42+
cert_chain: Vec<CertificateDer<'static>>,
3743
private_key_pkcs8_der: &[u8],
3844
) -> Result<Arc<rustls::sign::CertifiedKey>> {
39-
let certs: Vec<rustls::pki_types::CertificateDer<'static>> = cert_chain_der
40-
.iter()
41-
.map(|c| rustls::pki_types::CertificateDer::from(c.clone()))
42-
.collect();
43-
44-
let key_der = rustls::pki_types::PrivateKeyDer::Pkcs8(
45-
rustls::pki_types::PrivatePkcs8KeyDer::from(private_key_pkcs8_der.to_vec()),
46-
);
45+
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(private_key_pkcs8_der.to_vec()));
4746

4847
let provider = rustls::crypto::CryptoProvider::get_default()
4948
.ok_or_else(|| Error::CertifiedKey("rustls crypto provider is not installed".into()))?;
@@ -54,7 +53,29 @@ pub(crate) fn certified_key_from_der(
5453
.map_err(|e| Error::CertifiedKey(format!("{e:?}")))?;
5554

5655
Ok(Arc::new(rustls::sign::CertifiedKey::new(
57-
certs,
56+
cert_chain,
5857
signing_key,
5958
)))
6059
}
60+
61+
/// Helper: build an owned cert chain from an iterator of DER bytes.
62+
///
63+
/// This prevents higher layers from passing around `Vec<Vec<u8>>`.
64+
pub(crate) fn cert_chain_from_der_bytes<'a, I>(ders: I) -> Vec<CertificateDer<'static>>
65+
where
66+
I: IntoIterator<Item = &'a [u8]>,
67+
{
68+
ders.into_iter()
69+
.map(|b| CertificateDer::from(b.to_vec()))
70+
.collect()
71+
}
72+
73+
/// Helper: build owned root certs from an iterator of DER bytes.
74+
pub(crate) fn certs_from_der_bytes<'a, I>(ders: I) -> Vec<CertificateDer<'static>>
75+
where
76+
I: IntoIterator<Item = &'a [u8]>,
77+
{
78+
ders.into_iter()
79+
.map(|b| CertificateDer::from(b.to_vec()))
80+
.collect()
81+
}

0 commit comments

Comments
 (0)