use crate::error::{AuthorizerConfigError, Result};
use spiffe::{SpiffeId, TrustDomain};
use std::collections::BTreeSet;
use std::sync::Arc;
pub trait Authorizer: Send + Sync + 'static {
fn authorize(&self, peer: &SpiffeId) -> bool;
}
impl<F> Authorizer for F
where
F: Fn(&SpiffeId) -> bool + Send + Sync + 'static,
{
fn authorize(&self, peer: &SpiffeId) -> bool {
self(peer)
}
}
impl Authorizer for Arc<dyn Authorizer> {
fn authorize(&self, peer: &SpiffeId) -> bool {
(**self).authorize(peer)
}
}
impl Authorizer for Box<dyn Authorizer> {
fn authorize(&self, peer: &SpiffeId) -> bool {
(**self).authorize(peer)
}
}
#[must_use]
#[derive(Debug, Clone, Copy, Default)]
pub struct Any;
impl Authorizer for Any {
fn authorize(&self, _peer: &SpiffeId) -> bool {
true
}
}
#[must_use]
#[derive(Debug, Clone)]
pub struct Exact {
allowed: BTreeSet<SpiffeId>,
}
impl Exact {
pub fn new<I>(ids: I) -> Result<Self>
where
I: IntoIterator,
I::Item: TryInto<SpiffeId>,
<I::Item as TryInto<SpiffeId>>::Error: std::fmt::Display,
{
let mut allowed = BTreeSet::new();
for id in ids {
let spiffe_id = id
.try_into()
.map_err(|e| AuthorizerConfigError::InvalidSpiffeId(e.to_string()))?;
allowed.insert(spiffe_id);
}
Ok(Self { allowed })
}
}
impl Authorizer for Exact {
fn authorize(&self, peer: &SpiffeId) -> bool {
self.allowed.contains(peer)
}
}
#[must_use]
#[derive(Debug, Clone)]
pub struct TrustDomainAllowList {
allowed: BTreeSet<TrustDomain>,
}
#[deprecated(
since = "0.4.1",
note = "Renamed to TrustDomainAllowList; TrustDomains remains as a compatibility alias."
)]
pub type TrustDomains = TrustDomainAllowList;
impl TrustDomainAllowList {
pub fn new<I>(domains: I) -> Result<Self>
where
I: IntoIterator,
I::Item: TryInto<TrustDomain>,
<I::Item as TryInto<TrustDomain>>::Error: std::fmt::Display,
{
let mut allowed = BTreeSet::new();
for domain in domains {
let td = domain
.try_into()
.map_err(|e| AuthorizerConfigError::InvalidTrustDomain(e.to_string()))?;
allowed.insert(td);
}
Ok(Self { allowed })
}
}
impl Authorizer for TrustDomainAllowList {
fn authorize(&self, peer: &SpiffeId) -> bool {
self.allowed.contains(peer.trust_domain())
}
}
pub const fn any() -> Any {
Any
}
pub fn exact<I>(ids: I) -> Result<Exact>
where
I: IntoIterator,
I::Item: TryInto<SpiffeId>,
<I::Item as TryInto<SpiffeId>>::Error: std::fmt::Display,
{
Exact::new(ids)
}
pub fn trust_domains<I>(domains: I) -> Result<TrustDomainAllowList>
where
I: IntoIterator,
I::Item: TryInto<TrustDomain>,
<I::Item as TryInto<TrustDomain>>::Error: std::fmt::Display,
{
TrustDomainAllowList::new(domains)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exact_authorizer() {
let id1 = SpiffeId::new("spiffe://example.org/service1").unwrap();
let id2 = SpiffeId::new("spiffe://example.org/service2").unwrap();
let id3 = SpiffeId::new("spiffe://other.org/service1").unwrap();
let auth = Exact::new([id1.clone(), id2.clone()]).unwrap();
assert!(auth.authorize(&id1));
assert!(auth.authorize(&id2));
assert!(!auth.authorize(&id3));
}
#[test]
fn test_exact_authorizer_rejects_invalid() {
let result = Exact::new(["invalid-spiffe-id", "also-invalid"]);
result.unwrap_err();
}
#[test]
fn test_trust_domains_authorizer() {
let td1 = TrustDomain::new("example.org").unwrap();
let td2 = TrustDomain::new("other.org").unwrap();
let id1 = SpiffeId::new("spiffe://example.org/service1").unwrap();
let id2 = SpiffeId::new("spiffe://example.org/service2").unwrap();
let id3 = SpiffeId::new("spiffe://other.org/service1").unwrap();
let id4 = SpiffeId::new("spiffe://third.org/service1").unwrap();
let auth = TrustDomainAllowList::new([td1, td2]).unwrap();
assert!(auth.authorize(&id1));
assert!(auth.authorize(&id2));
assert!(auth.authorize(&id3));
assert!(!auth.authorize(&id4));
}
#[test]
fn test_trust_domains_authorizer_rejects_invalid() {
let result = TrustDomainAllowList::new(["Invalid@Trust#Domain"]);
result.unwrap_err();
let valid = TrustDomainAllowList::new(["example.org", "other.org"]).unwrap();
let id1 = SpiffeId::new("spiffe://example.org/service").unwrap();
let id2 = SpiffeId::new("spiffe://other.org/service").unwrap();
let id3 = SpiffeId::new("spiffe://rejected.org/service").unwrap();
assert!(valid.authorize(&id1));
assert!(valid.authorize(&id2));
assert!(!valid.authorize(&id3));
}
#[test]
fn test_any_authorizer_always_authorizes() {
let auth = any();
let id1 = SpiffeId::new("spiffe://example.org/service").unwrap();
let id2 = SpiffeId::new("spiffe://other.org/another").unwrap();
let id3 = SpiffeId::new("spiffe://test.domain/path/to/resource").unwrap();
assert!(auth.authorize(&id1));
assert!(auth.authorize(&id2));
assert!(auth.authorize(&id3));
}
}