spiffe_rustls/
authorizer.rs

1//! Authorization abstractions for SPIFFE ID-based access control.
2
3use crate::error::{AuthorizerConfigError, Result};
4use spiffe::{SpiffeId, TrustDomain};
5use std::collections::BTreeSet;
6use std::sync::Arc;
7
8/// Authorization policy for peer SPIFFE IDs.
9///
10/// Authorization runs **after** cryptographic verification succeeds.
11/// Implementations must be thread-safe.
12pub trait Authorizer: Send + Sync + 'static {
13    /// Returns `true` if the peer SPIFFE ID is authorized.
14    fn authorize(&self, peer: &SpiffeId) -> bool;
15}
16
17// ---- ergonomic blanket impl (closures / function pointers) ----
18
19impl<F> Authorizer for F
20where
21    F: Fn(&SpiffeId) -> bool + Send + Sync + 'static,
22{
23    fn authorize(&self, peer: &SpiffeId) -> bool {
24        self(peer)
25    }
26}
27
28impl Authorizer for Arc<dyn Authorizer> {
29    fn authorize(&self, peer: &SpiffeId) -> bool {
30        (**self).authorize(peer)
31    }
32}
33
34impl Authorizer for Box<dyn Authorizer> {
35    fn authorize(&self, peer: &SpiffeId) -> bool {
36        (**self).authorize(peer)
37    }
38}
39
40/// Authorizes any SPIFFE ID (authentication only, no authorization).
41#[derive(Debug, Clone, Copy, Default)]
42pub struct Any;
43
44impl Authorizer for Any {
45    fn authorize(&self, _peer: &SpiffeId) -> bool {
46        true
47    }
48}
49
50/// Authorizes only the exact SPIFFE IDs in the allow list.
51#[derive(Debug, Clone)]
52pub struct Exact {
53    allowed: Arc<BTreeSet<SpiffeId>>,
54}
55
56impl Exact {
57    /// Creates a new `Exact` authorizer.
58    ///
59    /// If the iterator is empty, the authorizer authorizes nothing.
60    ///
61    /// # Errors
62    ///
63    /// Returns `Error::AuthorizerConfig` if any ID cannot be parsed.
64    pub fn new<I>(ids: I) -> Result<Self>
65    where
66        I: IntoIterator,
67        I::Item: TryInto<SpiffeId>,
68        <I::Item as TryInto<SpiffeId>>::Error: std::fmt::Display,
69    {
70        let mut allowed = BTreeSet::new();
71
72        for id in ids {
73            let spiffe_id = id
74                .try_into()
75                .map_err(|e| AuthorizerConfigError::InvalidSpiffeId(e.to_string()))?;
76            allowed.insert(spiffe_id);
77        }
78
79        Ok(Self {
80            allowed: Arc::new(allowed),
81        })
82    }
83}
84
85impl Authorizer for Exact {
86    fn authorize(&self, peer: &SpiffeId) -> bool {
87        self.allowed.contains(peer)
88    }
89}
90
91/// Authorizes any SPIFFE ID from the given trust domains.
92#[derive(Debug, Clone)]
93pub struct TrustDomains {
94    allowed: Arc<BTreeSet<TrustDomain>>,
95}
96
97impl TrustDomains {
98    /// Creates a new `TrustDomains` authorizer.
99    ///
100    /// If the iterator is empty, the authorizer authorizes nothing.
101    ///
102    /// # Errors
103    ///
104    /// Returns `Error::AuthorizerConfig` if any trust domain cannot be parsed.
105    pub fn new<I>(domains: I) -> Result<Self>
106    where
107        I: IntoIterator,
108        I::Item: TryInto<TrustDomain>,
109        <I::Item as TryInto<TrustDomain>>::Error: std::fmt::Display,
110    {
111        let mut allowed = BTreeSet::new();
112
113        for domain in domains {
114            let td = domain
115                .try_into()
116                .map_err(|e| AuthorizerConfigError::InvalidTrustDomain(e.to_string()))?;
117            allowed.insert(td);
118        }
119
120        Ok(Self {
121            allowed: Arc::new(allowed),
122        })
123    }
124}
125
126impl Authorizer for TrustDomains {
127    fn authorize(&self, peer: &SpiffeId) -> bool {
128        self.allowed.contains(peer.trust_domain())
129    }
130}
131
132/// Returns an authorizer that accepts any SPIFFE ID.
133///
134/// This is useful when authorization is performed at another layer
135/// (e.g., application-level RBAC). Authentication (certificate verification)
136/// still applies.
137///
138/// Returns a zero-sized `Any` value that can be used directly.
139///
140/// # Examples
141///
142/// ```rust
143/// use spiffe_rustls::authorizer;
144///
145/// let auth = authorizer::any();
146/// ```
147pub fn any() -> Any {
148    Any
149}
150
151/// Returns an authorizer that only accepts the exact SPIFFE IDs.
152///
153/// # Arguments
154///
155/// * `ids` - An iterator of SPIFFE IDs (or types that can be converted to `SpiffeId`)
156///
157/// If the iterator is empty, the resulting authorizer will authorize no SPIFFE IDs
158/// (all authorization checks will return `false`).
159///
160/// # Errors
161///
162/// Returns `Error::AuthorizerConfig` if any SPIFFE ID is invalid.
163///
164/// # Examples
165///
166/// ```rust
167/// use spiffe_rustls::authorizer;
168///
169/// // Pass string literals directly - exact() will convert them
170/// let auth = authorizer::exact([
171///     "spiffe://example.org/payment",
172///     "spiffe://example.org/checkout",
173/// ])?;
174/// # Ok::<(), spiffe_rustls::Error>(())
175/// ```
176pub fn exact<I>(ids: I) -> Result<Exact>
177where
178    I: IntoIterator,
179    I::Item: TryInto<SpiffeId>,
180    <I::Item as TryInto<SpiffeId>>::Error: std::fmt::Display,
181{
182    Exact::new(ids)
183}
184
185/// Returns an authorizer that accepts any SPIFFE ID from the given trust domains.
186///
187/// # Arguments
188///
189/// * `domains` - An iterator of trust domains (or types that can be converted to `TrustDomain`)
190///
191/// If the iterator is empty, the resulting authorizer will authorize no trust domains
192/// (all authorization checks will return `false`).
193///
194/// # Errors
195///
196/// Returns `Error::AuthorizerConfig` if any trust domain is invalid.
197///
198/// # Examples
199///
200/// ```rust
201/// use spiffe_rustls::authorizer;
202///
203/// // Pass string literals directly - trust_domains() will convert them
204/// let auth = authorizer::trust_domains([
205///     "broker.example",
206///     "stockmarket.example",
207/// ])?;
208/// # Ok::<(), spiffe_rustls::Error>(())
209/// ```
210pub fn trust_domains<I>(domains: I) -> Result<TrustDomains>
211where
212    I: IntoIterator,
213    I::Item: TryInto<TrustDomain>,
214    <I::Item as TryInto<TrustDomain>>::Error: std::fmt::Display,
215{
216    TrustDomains::new(domains)
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_exact_authorizer() {
225        let id1 = SpiffeId::new("spiffe://example.org/service1").unwrap();
226        let id2 = SpiffeId::new("spiffe://example.org/service2").unwrap();
227        let id3 = SpiffeId::new("spiffe://other.org/service1").unwrap();
228
229        let auth = Exact::new([id1.clone(), id2.clone()]).unwrap();
230        assert!(auth.authorize(&id1));
231        assert!(auth.authorize(&id2));
232        assert!(!auth.authorize(&id3));
233    }
234
235    #[test]
236    fn test_exact_authorizer_rejects_invalid() {
237        let result = Exact::new(["invalid-spiffe-id", "also-invalid"]);
238        assert!(result.is_err());
239    }
240
241    #[test]
242    fn test_trust_domains_authorizer() {
243        let td1 = TrustDomain::new("example.org").unwrap();
244        let td2 = TrustDomain::new("other.org").unwrap();
245
246        let id1 = SpiffeId::new("spiffe://example.org/service1").unwrap();
247        let id2 = SpiffeId::new("spiffe://example.org/service2").unwrap();
248        let id3 = SpiffeId::new("spiffe://other.org/service1").unwrap();
249        let id4 = SpiffeId::new("spiffe://third.org/service1").unwrap();
250
251        let auth = TrustDomains::new([td1, td2]).unwrap();
252        assert!(auth.authorize(&id1));
253        assert!(auth.authorize(&id2));
254        assert!(auth.authorize(&id3));
255        assert!(!auth.authorize(&id4));
256    }
257
258    #[test]
259    fn test_trust_domains_authorizer_rejects_invalid() {
260        // Use a string with invalid characters (uppercase and special chars not allowed)
261        // TrustDomain::new explicitly validates the format, so this should fail
262        let result = TrustDomains::new(["Invalid@Trust#Domain"]);
263        assert!(result.is_err());
264
265        // Verify that valid trust domains are accepted
266        let valid = TrustDomains::new(["example.org", "other.org"]).unwrap();
267        let id1 = SpiffeId::new("spiffe://example.org/service").unwrap();
268        let id2 = SpiffeId::new("spiffe://other.org/service").unwrap();
269        let id3 = SpiffeId::new("spiffe://rejected.org/service").unwrap();
270        assert!(valid.authorize(&id1));
271        assert!(valid.authorize(&id2));
272        assert!(!valid.authorize(&id3));
273    }
274
275    #[test]
276    fn test_any_authorizer_always_authorizes() {
277        // Verify that `Any` authorizer accepts all valid SPIFFE IDs regardless of trust domain
278        let auth = any();
279        let id1 = SpiffeId::new("spiffe://example.org/service").unwrap();
280        let id2 = SpiffeId::new("spiffe://other.org/another").unwrap();
281        let id3 = SpiffeId::new("spiffe://test.domain/path/to/resource").unwrap();
282
283        // Any authorizer should accept all SPIFFE IDs
284        assert!(auth.authorize(&id1));
285        assert!(auth.authorize(&id2));
286        assert!(auth.authorize(&id3));
287    }
288}