spiffe_rustls/
lib.rs

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
9//! # spiffe-rustls
10//!
11//! `spiffe-rustls` integrates [`rustls`] with SPIFFE/SPIRE using a live
12//! [`spiffe::X509Source`] (SPIFFE Workload API).
13//!
14//! It provides builders for [`rustls::ClientConfig`] and
15//! [`rustls::ServerConfig`] that are backed by an `X509Source`. When the SPIRE
16//! agent rotates X.509 SVIDs or trust bundles, **new TLS handshakes automatically
17//! use the updated material**, without restarting the application.
18//!
19//! The crate focuses on TLS authentication and **connection-level authorization
20//! via SPIFFE IDs**, while delegating all cryptography and TLS mechanics to
21//! `rustls`.
22//!
23//! ## Federation
24//!
25//! When SPIFFE federation is configured, the Workload API delivers trust bundles
26//! for multiple trust domains. `spiffe-rustls` automatically handles this:
27//!
28//! * The verifier extracts the peer's SPIFFE ID from their certificate
29//! * It derives the trust domain from that SPIFFE ID
30//! * It selects the correct root certificate bundle from the bundle set
31//! * Certificate verification proceeds using the selected bundle
32//!
33//! **No federation-specific configuration is required.** Federation works
34//! automatically whenever the Workload API provides bundles for multiple trust
35//! domains. You can optionally restrict which trust domains are trusted using
36//! [`TrustDomainPolicy`], but this is purely a defense-in-depth mechanism.
37//! Policy variants (`AnyInBundleSet`, `AllowList`, `LocalOnly`) are re-exported
38//! at the crate root for convenience.
39//!
40//! ## Security Model
41//!
42//! The crate follows a strict security model to ensure cryptographic verification
43//! is never bypassed:
44//!
45//! 1. **SPIFFE ID extraction (pre-verification)**: The peer's SPIFFE ID is extracted
46//!    from the certificate's URI SAN **before** cryptographic verification. This is
47//!    safe because it is **only used to select the trust domain's root certificate bundle**.
48//!    The extracted SPIFFE ID has no security impact at this stage.
49//!
50//! 2. **Cryptographic verification**: Certificate verification (signature validation,
51//!    chain validation, expiration checks) is performed by `rustls`/`webpki` using the
52//!    selected root certificate bundle. This is the authoritative security boundary.
53//!
54//! 3. **Authorization (post-verification)**: Authorization based on SPIFFE ID is applied
55//!    **only after** cryptographic verification succeeds. If authorization fails, the
56//!    handshake is rejected.
57//!
58//! **Failure modes**: If the trust domain's bundle is absent from the bundle set, or if
59//! the trust domain is rejected by policy, certificate verification fails and the handshake
60//! is rejected. This ensures that only cryptographically verified peers from allowed trust
61//! domains can establish connections.
62//!
63//! ## Authorization
64//!
65//! Authorization is performed **after** cryptographic verification succeeds. The
66//! crate provides a strongly-typed [`authorizer::Authorizer`] trait for implementing
67//! authorization policies.
68//!
69//! ## Feature flags
70//!
71//! Exactly **one** `rustls` crypto provider must be enabled:
72//!
73//! * `ring` (default)
74//! * `aws-lc-rs`
75//!
76//! Enabling more than one provider results in a compile-time error.
77
78#[cfg(all(feature = "ring", feature = "aws-lc-rs"))]
79compile_error!("Enable only one crypto provider feature: `ring` or `aws-lc-rs`.");
80
81#[cfg(not(any(feature = "ring", feature = "aws-lc-rs")))]
82compile_error!("Enable one crypto provider feature: `ring` (default) or `aws-lc-rs`.");
83
84pub mod authorizer;
85
86// Crate-internal modules
87mod client;
88mod crypto;
89mod error;
90mod material;
91
92mod observability;
93mod prelude;
94
95mod policy;
96mod resolve;
97mod server;
98mod verifier;
99
100// Public re-exports
101pub use authorizer::{any, exact, trust_domains, Authorizer};
102pub use client::ClientConfigBuilder;
103pub use error::{Error, Result};
104pub use policy::TrustDomainPolicy;
105pub use policy::TrustDomainPolicy::{AllowList, AnyInBundleSet, LocalOnly};
106pub use server::ServerConfigBuilder;
107pub use spiffe::{SpiffeId, TrustDomain};
108
109/// Convenience constructor for the mTLS client builder.
110///
111/// This creates a client builder with default settings:
112/// - Authorization: accepts any SPIFFE ID (authentication only)
113/// - Trust domain policy: `AnyInBundleSet` (uses all bundles from the Workload API)
114///
115/// # Examples
116///
117/// ```no_run
118/// use spiffe_rustls::{authorizer, mtls_client};
119///
120/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
121/// let source = spiffe::X509Source::new().await?;
122///
123/// // Using a convenience constructor - pass string literals directly
124/// let client_config = mtls_client(source.clone())
125///     .authorize(authorizer::exact([
126///         "spiffe://example.org/myservice",
127///         "spiffe://example.org/myservice2",
128///     ])?)
129///     .build()?;
130///
131/// // Using a closure
132/// let client_config = mtls_client(source.clone())
133///     .authorize(|id: &spiffe::SpiffeId| id.path().starts_with("/api/"))
134///     .build()?;
135///
136/// // Using the Any authorizer (default)
137/// let client_config = mtls_client(source)
138///     .authorize(authorizer::any())
139///     .build()?;
140/// # Ok(())
141/// # }
142/// ```
143pub fn mtls_client(source: std::sync::Arc<spiffe::X509Source>) -> ClientConfigBuilder {
144    ClientConfigBuilder::new(source)
145}
146
147/// Convenience constructor for the mTLS server builder.
148///
149/// This creates a server builder with default settings:
150/// - Authorization: accepts any SPIFFE ID (authentication only)
151/// - Trust domain policy: `AnyInBundleSet` (uses all bundles from the Workload API)
152///
153/// # Examples
154///
155/// ```no_run
156/// use spiffe_rustls::{authorizer, mtls_server};
157///
158/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
159/// let source = spiffe::X509Source::new().await?;
160///
161/// // Using a convenience constructor - pass string literals directly
162/// let server_config = mtls_server(source.clone())
163///     .authorize(authorizer::trust_domains([
164///         "example.org",
165///     ])?)
166///     .build()?;
167///
168/// // Using a closure
169/// let server_config = mtls_server(source.clone())
170///     .authorize(|id: &spiffe::SpiffeId| id.path().starts_with("/api/"))
171///     .build()?;
172///
173/// // Using the Any authorizer (default)
174/// let server_config = mtls_server(source)
175///     .authorize(authorizer::any())
176///     .build()?;
177/// # Ok(())
178/// # }
179/// ```
180pub fn mtls_server(source: std::sync::Arc<spiffe::X509Source>) -> ServerConfigBuilder {
181    ServerConfigBuilder::new(source)
182}