1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//! Dynamic [`rustls`] TLS configuration backed by the [SPIFFE Workload API][spiffe]
//! and [`rustls-config-stream`]. Effortlessly hot-swap new X509-SVIDs and Trust
//! Bundles as they rotate, without restarting your service.
//!
//! Provides [`SpiffeClientConfigStream`] and [`SpiffeServerConfigStream`] for
//! use with [`ClientConfigProvider`] and [`ServerConfigProvider`].
//!
//! ## Installation
//!
//! ```sh
//! cargo add rustls-spiffe
//! ```
//!
//! The providers connect to the Workload API at the address in the
//! `SPIFFE_ENDPOINT_SOCKET` environment variable (e.g.
//! `unix:///tmp/spire-agent/public/api.sock`).
//!
//! ## Example
//!
//! An mTLS server that only accepts clients presenting an X509-SVID from the
//! `example.org` trust domain and greets them by SPIFFE ID, plus a client that
//! connects to it. In a real deployment these would be separate workloads; they
//! share a process here so the example runs as-is.
//!
//! ```
//! use rustls_spiffe::{
//! ClientConfigProvider, ServerConfigProvider, SpiffeClientConfigStream,
//! SpiffeServerConfigStream, extract_leaf_cert, extract_spiffe_id,
//! };
//! use tokio::{
//! io::{AsyncReadExt, AsyncWriteExt},
//! net::{TcpListener, TcpStream},
//! };
//! use tokio_rustls::{TlsAcceptor, TlsConnector};
//!
//! type Error = Box<dyn std::error::Error + Send + Sync>;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Error> {
//! let listener = TcpListener::bind("127.0.0.1:8443").await?;
//! tokio::spawn(serve(listener));
//!
//! let greeting = greet().await?;
//! print!("{greeting}");
//! # assert!(greeting.starts_with("hello, spiffe://example.org/"), "{greeting:?}");
//! Ok(())
//! }
//!
//! /// Accept clients from `example.org` and greet them by SPIFFE ID.
//! async fn serve(listener: TcpListener) -> Result<(), Error> {
//! let builder = SpiffeServerConfigStream::builder(vec!["example.org".try_into()?]);
//!
//! // Waits for the first SVID, then keeps the config current in the background.
//! let provider = ServerConfigProvider::start(builder).await?;
//!
//! loop {
//! let (tcp, _) = listener.accept().await?;
//!
//! // Fetch the config per connection so every handshake uses the latest
//! // SVID and trust bundle.
//! let acceptor = TlsAcceptor::from(provider.get_config());
//!
//! tokio::spawn(async move {
//! let Ok(mut tls) = acceptor.accept(tcp).await else {
//! return;
//! };
//! if let Some(id) = extract_spiffe_id(extract_leaf_cert(&tls)) {
//! let _ = tls.write_all(format!("hello, {id}\n").as_bytes()).await;
//! }
//! let _ = tls.shutdown().await;
//! });
//! }
//! }
//!
//! /// Connect to a server from `example.org` and return its greeting.
//! async fn greet() -> Result<String, Error> {
//! let builder = SpiffeClientConfigStream::builder(vec!["example.org".try_into()?]);
//! let provider = ClientConfigProvider::start(builder).await?;
//!
//! // As on the server, fetch the config for each new connection.
//! let connector = TlsConnector::from(provider.get_config());
//! let tcp = TcpStream::connect("localhost:8443").await?;
//! let mut tls = connector.connect("localhost".try_into()?, tcp).await?;
//!
//! let mut greeting = String::new();
//! tls.read_to_string(&mut greeting).await?;
//! Ok(greeting)
//! }
//! ```
//!
//! The server verifies that client certificates chain to a configured trust
//! domain, but does not restrict *which* workloads in that trust domain may
//! connect. Use [`extract_spiffe_id`] to authorize individual SPIFFE IDs.
//!
//! The client performs standard hostname verification, so the server's SVID
//! must carry a DNS SAN matching the name you connect to (with SPIRE, register
//! the server entry with `-dns localhost`, for example).
//!
//! ## Rotation and Health
//!
//! Both providers subscribe to the Workload API and atomically swap in a new
//! config whenever SPIRE rotates the SVID or a trust bundle changes. Existing
//! connections keep the config they were established with.
//!
//! If the Workload API stream fails, the provider keeps serving the last good
//! config while it reconnects with exponential backoff. `stream_healthy()`
//! reports `false` until the stream is re-established, which is useful for
//! readiness checks.
//!
//! ## Trust Domains and Federation
//!
//! The list passed to `builder` selects which trust bundles are used as roots.
//! To accept peers from a federated trust domain (whose bundle SPIRE delivers
//! when the registration entry federates with it), include it alongside your
//! own:
//!
//! ```
//! # use rustls_spiffe::SpiffeServerConfigStream;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let builder = SpiffeServerConfigStream::builder(vec![
//! "example.org".try_into()?,
//! "partner.example.com".try_into()?,
//! ]);
//! # Ok(())
//! # }
//! ```
//!
//! ## Feature Flags
//!
//! All features are enabled by default via the `full` feature.
//!
//! | Feature | Description |
//! |------------------|-----------------------------------------------------------------------------|
//! | `config-stream` | [`SpiffeClientConfigStream`], [`SpiffeServerConfigStream`], and the providers |
//! | `svid-extractor` | [`extract_leaf_cert`] and [`extract_spiffe_id`] for `tokio-rustls` servers |
//! | `tracing` | Logs workload identities, bundle loads, and stream reconnects via `tracing` |
//!
//! ## Crypto Provider
//!
//! This crate enables `rustls`'s `aws-lc-rs` backend. If something else in your
//! dependency tree enables another backend (e.g. `ring`), install a process
//! default before starting a provider:
//!
//! ```
//! // Errs only if a default is already installed, which is safe to ignore.
//! let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
//! ```
//!
//! [`rustls`]: https://docs.rs/rustls
//! [spiffe]: https://spiffe.io/docs/latest/spiffe-about/overview/
//! [`rustls-config-stream`]: https://docs.rs/rustls-config-stream
//! [`SpiffeClientConfigStream`]: https://docs.rs/rustls-spiffe/latest/rustls_spiffe/struct.SpiffeClientConfigStream.html
//! [`SpiffeServerConfigStream`]: https://docs.rs/rustls-spiffe/latest/rustls_spiffe/struct.SpiffeServerConfigStream.html
//! [`ClientConfigProvider`]: https://docs.rs/rustls-config-stream/latest/rustls_config_stream/struct.ClientConfigProvider.html
//! [`ServerConfigProvider`]: https://docs.rs/rustls-config-stream/latest/rustls_config_stream/struct.ServerConfigProvider.html
//! [`extract_leaf_cert`]: https://docs.rs/rustls-spiffe/latest/rustls_spiffe/fn.extract_leaf_cert.html
//! [`extract_spiffe_id`]: https://docs.rs/rustls-spiffe/latest/rustls_spiffe/fn.extract_spiffe_id.html
pub use ;
pub use ;
pub use TrustDomainStore;
pub use ;