Skip to main content

hackamore_control/
tenants.rs

1//! Multi-tenant mint authorization. When one hackamore serves more than one trust domain,
2//! a tenant authenticates to the mint endpoint and may only mint tokens scoped to the
3//! targets it **owns**. Without this, any caller could submit a policy naming another
4//! tenant's target and launder its credential.
5//!
6//! Single-trust-domain deployments leave this registry empty, and minting is open (the
7//! mint endpoint is the operator's own surface).
8
9use parking_lot::RwLock;
10use std::collections::{BTreeSet, HashMap};
11
12/// Maps a tenant credential (an opaque key the operator issues) to the set of service
13/// instance names that tenant owns.
14#[derive(Default)]
15pub struct Tenants {
16    owned: RwLock<HashMap<String, BTreeSet<String>>>,
17}
18
19impl Tenants {
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Register (or replace) a tenant's owned-target set.
25    pub fn insert(&self, key: impl Into<String>, targets: impl IntoIterator<Item = String>) {
26        self.owned
27            .write()
28            .insert(key.into(), targets.into_iter().collect());
29    }
30
31    /// Whether any tenant is configured. Empty ⇒ single-trust-domain ⇒ minting is open.
32    pub fn is_empty(&self) -> bool {
33        self.owned.read().is_empty()
34    }
35
36    /// The owned-target set for `key`, or `None` if the tenant key is unknown.
37    pub fn owned(&self, key: &str) -> Option<BTreeSet<String>> {
38        self.owned.read().get(key).cloned()
39    }
40}
41
42#[cfg(test)]
43#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn empty_until_seeded_then_resolves_owned() {
49        let t = Tenants::new();
50        assert!(t.is_empty());
51        t.insert("tenant-a", ["github".to_string(), "eks-prod".to_string()]);
52        assert!(!t.is_empty());
53        let owned = t.owned("tenant-a").unwrap();
54        assert!(owned.contains("github"));
55        assert!(!owned.contains("aws-acct-b"));
56        assert!(t.owned("nobody").is_none());
57    }
58}