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
86mod crypto;
87mod error;
88mod material;
89
90mod observability;
91mod prelude;
92
93mod client;
94mod policy;
95mod resolve;
96mod server;
97mod verifier;
98
99// Public re-exports
100pub use authorizer::{any, exact, trust_domains, Authorizer};
101pub use client::ClientConfigBuilder;
102pub use error::{Error, Result};
103pub use policy::TrustDomainPolicy;
104pub use policy::TrustDomainPolicy::{AllowList, AnyInBundleSet, LocalOnly};
105pub use server::ServerConfigBuilder;
106pub use spiffe::{SpiffeId, TrustDomain};
107
108/// Convenience constructor for the mTLS client builder.
109///
110/// This creates a client builder with default settings:
111/// - Authorization: accepts any SPIFFE ID (authentication only)
112/// - Trust domain policy: `AnyInBundleSet` (uses all bundles from the Workload API)
113///
114/// # Examples
115///
116/// ```no_run
117/// use spiffe_rustls::{authorizer, mtls_client};
118///
119/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
120/// let source = spiffe::X509Source::new().await?;
121///
122/// // Using a convenience constructor - pass string literals directly
123/// let client_config = mtls_client(source.clone())
124/// .authorize(authorizer::exact([
125/// "spiffe://example.org/myservice",
126/// "spiffe://example.org/myservice2",
127/// ])?)
128/// .build()?;
129///
130/// // Using a closure
131/// let client_config = mtls_client(source.clone())
132/// .authorize(|id: &spiffe::SpiffeId| id.path().starts_with("/api/"))
133/// .build()?;
134///
135/// // Using the Any authorizer (default)
136/// let client_config = mtls_client(source)
137/// .authorize(authorizer::any())
138/// .build()?;
139/// # Ok(())
140/// # }
141/// ```
142pub fn mtls_client(source: spiffe::X509Source) -> ClientConfigBuilder {
143 ClientConfigBuilder::new(source)
144}
145
146/// Convenience constructor for the mTLS server builder.
147///
148/// This creates a server builder with default settings:
149/// - Authorization: accepts any SPIFFE ID (authentication only)
150/// - Trust domain policy: `AnyInBundleSet` (uses all bundles from the Workload API)
151///
152/// # Examples
153///
154/// ```no_run
155/// use spiffe_rustls::{authorizer, mtls_server};
156///
157/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
158/// let source = spiffe::X509Source::new().await?;
159///
160/// // Using a convenience constructor - pass string literals directly
161/// let server_config = mtls_server(source.clone())
162/// .authorize(authorizer::trust_domains([
163/// "example.org",
164/// ])?)
165/// .build()?;
166///
167/// // Using a closure
168/// let server_config = mtls_server(source.clone())
169/// .authorize(|id: &spiffe::SpiffeId| id.path().starts_with("/api/"))
170/// .build()?;
171///
172/// // Using the Any authorizer (default)
173/// let server_config = mtls_server(source)
174/// .authorize(authorizer::any())
175/// .build()?;
176/// # Ok(())
177/// # }
178/// ```
179pub fn mtls_server(source: spiffe::X509Source) -> ServerConfigBuilder {
180 ServerConfigBuilder::new(source)
181}