spiffe-rustls
spiffe-rustls integrates rustls with SPIFFE/SPIRE using the
spiffe crate's X509Source (SPIFFE Workload API).
It provides builders for rustls::ClientConfig and rustls::ServerConfig backed by a live
X509Source. When the SPIRE agent rotates SVIDs or trust bundles, new TLS handshakes automatically
use the updated material, without restarting the application.
The crate focuses on TLS authentication and connection-level authorization via SPIFFE IDs, while
delegating all cryptography and TLS mechanics to rustls.
Key Features
- Federation support: Automatically handles multiple trust domains when SPIFFE federation is configured
- Typed authorization: Strongly-typed
Authorizertrait for SPIFFE ID-based access control - Live updates: Material rotates automatically when SPIRE updates SVIDs or bundles
- Production-ready: Zero unsafe code, comprehensive error handling, graceful degradation
Quick Start
1. Create an X509Source
The source is configured via SPIFFE_ENDPOINT_SOCKET:
let source = new.await?;
2. Build a rustls client configuration
use ;
let source = new.await?;
// Pass string literals directly - exact() and trust_domains() will convert them
let client_cfg = mtls_client
.authorize
.build?;
The resulting ClientConfig can be used directly with rustls, or integrated into
tokio-rustls, tonic-rustls, or similar libraries.
Federation
When SPIFFE federation is configured, the Workload API delivers trust bundles for multiple
trust domains. spiffe-rustls automatically handles this:
- Extracts the SPIFFE ID from the peer certificate
- Derives the trust domain from that SPIFFE ID
- Selects the correct root certificate bundle from the bundle set
- Verifies the certificate chain using the selected bundle
No federation-specific configuration is required. Federation works automatically whenever the Workload API provides bundles for multiple trust domains. The verifier dynamically selects the correct trust domain based on the peer's SPIFFE ID.
Trust Domain Policy
You can optionally restrict which trust domains are accepted using [TrustDomainPolicy].
This is a defense-in-depth mechanism—the primary trust model comes from the bundle set
delivered by the Workload API.
use ;
use BTreeSet;
// Default: use all bundles from the Workload API
let policy = AnyInBundleSet;
// Restrict to specific trust domains
let mut allowed = new;
allowed.insert;
allowed.insert;
let policy = AllowList;
// Only trust a single trust domain
let policy = LocalOnly;
// You can also use the full path if preferred
let policy = AnyInBundleSet;
Authorization
Authorization is performed after cryptographic verification succeeds. The crate provides a
strongly-typed [Authorizer] trait for implementing authorization policies.
Using the Authorizer Trait
The new Authorizer trait works with strongly-typed SpiffeId values:
use ;
use SpiffeId;
use Arc;
// Accept any SPIFFE ID (authentication only)
let auth: = new;
// Accept only exact SPIFFE IDs - pass string literals directly
let auth = exact?;
// Accept any SPIFFE ID from specific trust domains - pass string literals directly
let auth = trust_domains?;
// Custom authorizer using a closure
let auth: = new;
API Overview
The public API consists of:
Builders
ClientConfigBuilder- buildsrustls::ClientConfigServerConfigBuilder- buildsrustls::ServerConfig
Each builder:
- retains an
Arc<X509Source> - builds a
rustls::{ClientConfig, ServerConfig} - always uses the latest SVIDs and trust bundles
- authorizes peers by SPIFFE ID (URI SAN)
Authorization Types
- [
Authorizer] - trait for SPIFFE ID-based authorization authorizer::any()- accept any SPIFFE IDauthorizer::exact()- accept only exact SPIFFE IDsauthorizer::trust_domains()- accept any SPIFFE ID from specific trust domains
Policy Types
- [
TrustDomainPolicy] - optional policy for restricting which trust domains are accepted AnyInBundleSet(orTrustDomainPolicy::AnyInBundleSet) - use all bundles from the Workload API (default)AllowList(orTrustDomainPolicy::AllowList) - restrict to specific trust domainsLocalOnly(orTrustDomainPolicy::LocalOnly) - only trust a single trust domain
Policy variants are re-exported at the crate root for convenience, so you can use AllowList(domains) instead of TrustDomainPolicy::AllowList(domains).
Client Configuration
ClientConfigBuilder
Builds a rustls::ClientConfig that:
- presents the current SPIFFE X.509 SVID as the client certificate
- validates the server certificate chain using bundles from the Workload API
- automatically selects the correct trust domain bundle based on the server's SPIFFE ID
- authorizes the server by SPIFFE ID (URI SAN)
Example:
use ;
use BTreeSet;
let source = new.await?;
// Pass string literals directly - exact() will convert them
let allowed_server_ids = ;
let mut allowed_trust_domains = new;
allowed_trust_domains.insert;
let client_cfg = mtls_client
.authorize
.trust_domain_policy
.build?;
The builder automatically handles multiple trust domains when SPIFFE federation is configured. No federation-specific configuration is required.
Server Configuration
ServerConfigBuilder
Builds a rustls::ServerConfig that:
- presents the current SPIFFE X.509 SVID as the server certificate
- requires and validates client certificates (mTLS)
- automatically selects the correct trust domain bundle based on the client's SPIFFE ID
- authorizes the client by SPIFFE ID (URI SAN)
Example:
use ;
use ;
let source = new.await?;
// Pass string literals directly - trust_domains() will convert them
let allowed_trust_domains = ;
let local_trust_domain: TrustDomain = "example.org".try_into?;
let server_cfg = mtls_server
.authorize
.trust_domain_policy
.build?;
The builder automatically handles multiple trust domains when SPIFFE federation is configured. No federation-specific configuration is required.
Features
All features are additive and opt-in unless explicitly stated otherwise.
Crypto Providers
spiffe-rustls supports multiple rustls crypto providers:
[]
= ["ring"]
= ["rustls/ring"]
= ["rustls/aws_lc_rs"]
- Default:
ring - Optional:
aws-lc-rs
Exactly one provider must be enabled. Enabling more than one results in a compile-time error.
Example (AWS-LC):
Provider choice affects only cryptographic primitives; SPIFFE semantics and API behavior are identical across providers.
Observability features
The crate supports optional observability through two mutually compatible features:
logging and tracing. Both features are optional and can be enabled independently
or together.
Feature precedence
When multiple observability features are enabled, the following precedence applies:
tracing(highest priority) — If enabled, all events are emitted viatracinglogging— Iftracingis not enabled, events are emitted via thelogcrate- No observability — If neither feature is enabled, observability calls are no-ops
logging (default)
Enables observability using the log crate.
This is a lightweight option suitable for applications that use the standard log
facade. Events are emitted via log::debug!, log::info!, log::warn!, and log::error!.
Note: The logging feature is included in the default features, so it's enabled
by default. To disable it, use --no-default-features and explicitly select only the
features you need.
[]
= { = "0.2" } # logging enabled by default
tracing
Enables structured observability using the tracing crate.
This is recommended for production environments that use structured logs, spans,
or distributed tracing systems. When both tracing and logging features are enabled,
tracing takes precedence and all events are emitted via tracing macros.
[]
= { = "0.2", = ["tracing"] }
Note: The tracing and logging features are not mutually exclusive. When both
features are enabled, events are emitted via tracing. The tracing feature also
enables tracing in the underlying spiffe crate.
Examples
Prerequisites
All examples require:
- a running SPIRE agent
- a valid Workload API socket (
SPIFFE_ENDPOINT_SOCKET) - local DNS resolution for
example.org
For local testing, add to /etc/hosts:
127.0.0.1 example.org
Raw TLS (tokio-rustls)
Direct TLS integration using tokio-rustls.
Examples:
mtls_tcp_server.rsmtls_tcp_client.rs
gRPC (tonic + tonic-rustls)
gRPC examples live in a separate crate (spiffe-rustls-grpc-examples) to avoid pulling gRPC and
protobuf build dependencies into the library.
Security Considerations
Certificate Verification
- Certificates are verified against the trust bundle set delivered by the Workload API
- Only certificates with exactly one SPIFFE ID URI SAN are accepted (per SPIFFE spec)
- The verifier automatically selects the correct trust domain based on the peer's SPIFFE ID
- Authorization runs after cryptographic verification succeeds
Trust Domain Policy
The TrustDomainPolicy is a defense-in-depth mechanism. The primary trust model comes from
the bundle set delivered by the Workload API. When SPIFFE federation is configured, the Workload
API provides bundles for multiple trust domains, and the policy allows you to restrict which of
those bundles are actually used during certificate verification.
Authorization
By default, the builder accepts any SPIFFE ID (authentication only, no authorization).
Use authorizer::exact() or authorizer::trust_domains() to restrict which SPIFFE IDs
are accepted. Use authorizer::any() explicitly if you want to make it clear that
authorization is performed at another layer (e.g., application-level RBAC).
Notes
- Examples rely exclusively on the SPIFFE Workload API; they do not start or configure SPIRE.
- Standard TLS name (SNI) verification still applies; the DNS name must match the certificate SAN.
- Material updates are atomic; new handshakes use the latest material without blocking.
License
Licensed under the Apache License, Version 2.0. See LICENSE for details.