crates/gunnar-core/tests/acl.rs
//! The ACL, asserted in **both** directions.
//!
//! Measure, never assert, in its most literal form: an `may()` that returns `true` for
//! everything passes every "an admin may push" test ever written, and an
//! `may()` that returns `false` for everything passes every "a reader may not
//! push" test. Every case below therefore has its opposite next to it.
mod common;
use common::Fixture;
use gunnar_core::model::PrincipalKind;
use gunnar_core::{Grant, Level, Principal, RepoRef, Scope, Visibility};
fn principal(fx: &Fixture, id: &str) {
fx.auth
.add_principal(Principal::new(id, id, PrincipalKind::User).expect("legal id"))
.expect("add principal");
}
/// upload-pack needs Read, receive-pack needs Write. This is the check the SSH
/// listener makes, and it must go both ways.
#[test]
fn a_read_principal_is_refused_write_and_an_admin_is_allowed() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
let path = repo.path();
principal(&fx, "reader");
principal(&fx, "boss");
fx.auth
.grant(Grant::repo("reader", path.clone(), Level::Read))
.expect("grant");
fx.auth
.grant(Grant::repo("boss", path.clone(), Level::Admin))
.expect("grant");
// The reader may fetch…
assert!(fx.auth.may("reader", &repo, Level::Read).expect("acl"));
// …and may NOT push. This is the assertion the whole crate exists for.
assert!(!fx.auth.may("reader", &repo, Level::Write).expect("acl"));
assert!(!fx.auth.may("reader", &repo, Level::Admin).expect("acl"));
// The admin may do all three, because Level is ordered.
assert!(fx.auth.may("boss", &repo, Level::Read).expect("acl"));
assert!(fx.auth.may("boss", &repo, Level::Write).expect("acl"));
assert!(fx.auth.may("boss", &repo, Level::Admin).expect("acl"));
// And `require` reports the refusal as an error the SSH layer can return.
let err = fx
.auth
.require("reader", &repo, Level::Write)
.expect_err("must be denied");
assert!(err.is_denied(), "{err}");
assert!(format!("{err}").contains("acme/widget"), "{err}");
fx.auth
.require("boss", &repo, Level::Write)
.expect("the admin is not denied");
}
#[test]
fn a_writer_may_fetch_and_push_but_not_administer() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
principal(&fx, "dev");
fx.auth
.grant(Grant::repo("dev", repo.path(), Level::Write))
.expect("grant");
assert!(fx.auth.may("dev", &repo, Level::Read).expect("acl"));
assert!(fx.auth.may("dev", &repo, Level::Write).expect("acl"));
assert!(!fx.auth.may("dev", &repo, Level::Admin).expect("acl"));
}
/// A principal with no grant at all, and an id nobody registered, are both
/// refused everything, read included. "Unknown" must not mean "read-only".
#[test]
fn an_ungranted_or_unknown_principal_may_do_nothing() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
principal(&fx, "stranger");
for level in [Level::Read, Level::Write, Level::Admin] {
assert!(!fx.auth.may("stranger", &repo, level).expect("acl"));
assert!(!fx.auth.may("does-not-exist", &repo, level).expect("acl"));
assert!(!fx.auth.may("", &repo, level).expect("acl"));
}
}
/// A grant on one repository must not leak onto its neighbour. This is the
/// case a map keyed only by principal would get wrong.
#[test]
fn a_repo_grant_does_not_reach_another_repo() {
let fx = Fixture::new();
let mine = fx.repo("acme", "mine");
let yours = fx.repo("acme", "yours");
principal(&fx, "dev");
fx.auth
.grant(Grant::repo("dev", mine.path(), Level::Write))
.expect("grant");
assert!(fx.auth.may("dev", &mine, Level::Write).expect("acl"));
assert!(!fx.auth.may("dev", &yours, Level::Read).expect("acl"));
}
#[test]
fn a_namespace_grant_reaches_every_repo_in_that_namespace_and_no_other() {
let fx = Fixture::new();
let one = fx.repo("acme", "one");
let two = fx.repo("acme", "two");
let elsewhere = fx.repo("other", "three");
principal(&fx, "team");
fx.auth
.grant(Grant::namespace("team", "acme", Level::Write))
.expect("grant");
assert!(fx.auth.may("team", &one, Level::Write).expect("acl"));
assert!(fx.auth.may("team", &two, Level::Write).expect("acl"));
assert!(!fx.auth.may("team", &elsewhere, Level::Read).expect("acl"));
assert!(fx
.auth
.may_namespace("team", "acme", Level::Write)
.expect("acl"));
assert!(!fx
.auth
.may_namespace("team", "other", Level::Read)
.expect("acl"));
}
/// Nested namespaces are separate tenants: `team` does not inherit into
/// `team/sub`. Pinned deliberately rather than left to be discovered, because
/// the other answer is a privilege escalation nobody wrote down.
#[test]
fn a_namespace_grant_does_not_inherit_into_a_nested_namespace() {
let fx = Fixture::new();
let outer = fx.repo("team", "outer");
let inner = fx.repo("team/sub", "inner");
principal(&fx, "lead");
fx.auth
.grant(Grant::namespace("lead", "team", Level::Admin))
.expect("grant");
assert!(fx.auth.may("lead", &outer, Level::Admin).expect("acl"));
assert!(!fx.auth.may("lead", &inner, Level::Read).expect("acl"));
// The nested namespace is grantable on its own terms.
fx.auth
.grant(Grant::namespace("lead", "team/sub", Level::Read))
.expect("grant");
assert!(fx.auth.may("lead", &inner, Level::Read).expect("acl"));
assert!(!fx.auth.may("lead", &inner, Level::Write).expect("acl"));
}
/// A server-scope admin — what the bootstrap key gets — reaches all of it.
/// Without [`Scope::Server`] the first admin could not create a namespace and
/// the server would boot reachable and useless.
#[test]
fn a_server_admin_reaches_every_namespace_and_repo() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
let elsewhere = fx.repo("other", "thing");
principal(&fx, "root");
fx.auth
.grant(Grant::server("root", Level::Admin))
.expect("grant");
assert!(fx.auth.may("root", &repo, Level::Admin).expect("acl"));
assert!(fx.auth.may("root", &elsewhere, Level::Write).expect("acl"));
assert!(fx.auth.may_server("root", Level::Admin).expect("acl"));
assert!(fx
.auth
.may_namespace("root", "anything", Level::Admin)
.expect("acl"));
// …and a repo-scoped admin is NOT a server admin. The red half.
principal(&fx, "repo-boss");
fx.auth
.grant(Grant::repo("repo-boss", repo.path(), Level::Admin))
.expect("grant");
assert!(!fx.auth.may_server("repo-boss", Level::Read).expect("acl"));
assert!(!fx
.auth
.may_namespace("repo-boss", "acme", Level::Read)
.expect("acl"));
}
/// A public repository is readable by any *registered* principal, and writable
/// by none of them without a grant. The second half is what makes the first
/// safe.
#[test]
fn public_visibility_grants_read_and_never_write() {
let fx = Fixture::new();
let repo = fx.repo_with(
"acme",
"open",
gunnar_core::HashKind::Sha256,
Visibility::Public,
);
principal(&fx, "anyone");
assert!(fx.auth.may("anyone", &repo, Level::Read).expect("acl"));
assert!(!fx.auth.may("anyone", &repo, Level::Write).expect("acl"));
assert!(!fx.auth.may("anyone", &repo, Level::Admin).expect("acl"));
// An unregistered id is still nothing, even on a public repository. Public
// is not "anyone who types a name": the one identity that reads a public
// repository without being in the table is the *anonymous* principal, and
// it is a reserved id nobody else can spell. See
// `the_anonymous_principal_may_read_public_and_nothing_else` below.
assert!(!fx.auth.may("ghost", &repo, Level::Read).expect("acl"));
// Flipping it back to private takes the read away again: the red half.
fx.catalog
.set_visibility(&repo.path(), Visibility::Private)
.expect("set");
let repo = fx.catalog.open(&repo.path()).expect("reopen");
assert!(!fx.auth.may("anyone", &repo, Level::Read).expect("acl"));
}
// ───────────────────────────────────────────────────────────────────────────
// The anonymous principal: `Repo.visibility` made to mean something
// ───────────────────────────────────────────────────────────────────────────
/// **Rule 0 of `may`, in every direction it has.**
///
/// One `true` and eleven `false`s, and the `true` is what makes the `false`s
/// worth anything: an anonymous rule that answered `false` to everything would
/// satisfy every refusal below and serve no stranger at all, and one that
/// answered `true` to everything would satisfy nothing but would also be
/// invisible in a suite that only ever asserted the public read.
#[test]
fn the_anonymous_principal_may_read_public_and_nothing_else() {
let fx = Fixture::new();
let open = fx.repo_with(
"acme",
"open",
gunnar_core::HashKind::Sha256,
Visibility::Public,
);
let shut = fx.repo_with(
"acme",
"shut",
gunnar_core::HashKind::Sha256,
Visibility::Private,
);
let anon = gunnar_core::ANONYMOUS_PRINCIPAL_ID;
// The one thing it may do.
assert!(
fx.auth.may(anon, &open, Level::Read).expect("acl"),
"an anonymous caller must be able to READ a public repository. That is the whole of \
what `Repo.visibility` is for"
);
// Everything else on the same, public repository.
assert!(!fx.auth.may(anon, &open, Level::Write).expect("acl"));
assert!(!fx.auth.may(anon, &open, Level::Admin).expect("acl"));
// Every level on a private one, including read.
for level in [Level::Read, Level::Write, Level::Admin] {
assert!(
!fx.auth.may(anon, &shut, level).expect("acl"),
"anonymous reached a PRIVATE repository at {level:?}"
);
}
// The control-plane questions, which are never anonymous at any level.
for level in [Level::Read, Level::Write, Level::Admin] {
assert!(!fx.auth.may_namespace(anon, "acme", level).expect("acl"));
assert!(!fx.auth.may_server(anon, level).expect("acl"));
}
// And the flip, on the live catalog: making the open one private takes the
// anonymous read away, and making the shut one public grants it. Without
// this pair, a `may` that ignored `visibility` entirely and hard-coded the
// anonymous answer would pass everything above.
fx.catalog
.set_visibility(&open.path(), Visibility::Private)
.expect("set");
fx.catalog
.set_visibility(&shut.path(), Visibility::Public)
.expect("set");
let open = fx.catalog.open(&open.path()).expect("reopen");
let shut = fx.catalog.open(&shut.path()).expect("reopen");
assert!(!fx.auth.may(anon, &open, Level::Read).expect("acl"));
assert!(fx.auth.may(anon, &shut, Level::Read).expect("acl"));
}
/// **The reserved id, from all four doors it could be walked through.**
///
/// Rule 0 is a statement about a *name*. It is only worth anything if the name
/// cannot be claimed. A registered principal called `anonymous` would be handed
/// the public-read rule and could then be given keys and grants on top of it,
/// which is an authorisation bypass with a green suite.
#[test]
fn the_anonymous_id_is_reserved_and_can_hold_neither_key_nor_grant() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
let anon = gunnar_core::ANONYMOUS_PRINCIPAL_ID;
// 1. The constructor.
let err = Principal::new(anon, "me", PrincipalKind::User)
.expect_err("`anonymous` must not be constructible as an ordinary principal");
assert!(format!("{err}").contains("reserved"), "{err}");
// 2. The table, handed the one value that *can* be built.
let err = fx
.auth
.add_principal(Principal::anonymous())
.expect_err("`anonymous` must not be registrable");
assert!(format!("{err}").contains("reserved"), "{err}");
// 3. A key. There is no principal to hang it on, and that is the refusal.
const KEY: &str =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMW4aqVr75w+3DWorOCbfwl5XCDCq1yCQ6puzU4qzol2 anon";
let err = fx
.auth
.register_key(anon, KEY, None)
.expect_err("no key may ever authenticate AS anonymous");
assert!(format!("{err}").contains("no such principal"), "{err}");
// 4. A grant.
let err = fx
.auth
.grant(Grant::repo(anon, repo.path(), Level::Admin))
.expect_err("anonymous must not be grantable");
assert!(format!("{err}").contains("no such principal"), "{err}");
// The red half of all four: the identical operations succeed for an
// ordinary id, so none of the refusals above is a table that simply
// refuses everything.
principal(&fx, "ordinary");
fx.auth
.register_key("ordinary", KEY, None)
.expect("an ordinary principal takes a key");
fx.auth
.grant(Grant::repo("ordinary", repo.path(), Level::Admin))
.expect("an ordinary principal takes a grant");
assert!(fx.auth.may("ordinary", &repo, Level::Admin).expect("acl"));
// And nothing landed in the table under the reserved name.
assert!(fx.auth.principal(anon).expect("read").is_none());
assert!(fx.auth.grants(Some(anon)).expect("read").is_empty());
assert!(
fx.auth
.keys()
.expect("read")
.iter()
.all(|key| key.principal_id != anon),
"a key is registered to the anonymous principal"
);
}
/// The anonymous principal is a *value*, and its shape is pinned: id, kind and
/// the predicate agree. A `is_anonymous` keyed on something the ACL does not
/// look at would be a guard watching the wrong field.
#[test]
fn the_anonymous_principal_value_is_what_the_acl_is_keyed_on() {
let anon = Principal::anonymous();
assert_eq!(anon.id, gunnar_core::ANONYMOUS_PRINCIPAL_ID);
assert_eq!(anon.kind, PrincipalKind::Anonymous);
assert!(anon.is_anonymous());
// The red half: an ordinary principal is not anonymous, and no other kind
// claims to be.
let ordinary = Principal::new("dev", "dev", PrincipalKind::User).expect("legal");
assert!(!ordinary.is_anonymous());
assert_ne!(ordinary.kind, PrincipalKind::Anonymous);
}
/// Granting again on the same scope replaces rather than accumulates, so a
/// downgrade is really a downgrade.
#[test]
fn a_downgrade_is_a_downgrade() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
let path = repo.path();
principal(&fx, "dev");
fx.auth
.grant(Grant::repo("dev", path.clone(), Level::Admin))
.expect("grant");
assert!(fx.auth.may("dev", &repo, Level::Admin).expect("acl"));
fx.auth
.grant(Grant::repo("dev", path.clone(), Level::Read))
.expect("grant");
assert_eq!(fx.auth.grants(Some("dev")).expect("grants").len(), 1);
assert!(fx.auth.may("dev", &repo, Level::Read).expect("acl"));
assert!(!fx.auth.may("dev", &repo, Level::Write).expect("acl"));
// And revoking removes it entirely.
assert!(fx
.auth
.revoke_grant("dev", &Scope::Repo(path.clone()))
.expect("revoke"));
assert!(!fx.auth.may("dev", &repo, Level::Read).expect("acl"));
assert!(!fx
.auth
.revoke_grant("dev", &Scope::Repo(path))
.expect("revoke again"));
}
#[test]
fn removing_a_principal_removes_its_grants() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
principal(&fx, "leaver");
fx.auth
.grant(Grant::repo("leaver", repo.path(), Level::Admin))
.expect("grant");
assert!(fx.auth.may("leaver", &repo, Level::Admin).expect("acl"));
fx.auth.remove_principal("leaver").expect("remove");
assert!(fx.auth.grants(None).expect("grants").is_empty());
assert!(!fx.auth.may("leaver", &repo, Level::Read).expect("acl"));
}
#[test]
fn a_grant_to_an_unknown_principal_is_refused() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
let err = fx
.auth
.grant(Grant::repo("ghost", repo.path(), Level::Admin))
.expect_err("must refuse");
assert!(format!("{err}").contains("no such principal"), "{err}");
assert!(fx.auth.grants(None).expect("grants").is_empty());
}
/// The level ordering the whole ACL rests on.
#[test]
fn levels_are_ordered() {
assert!(Level::Read < Level::Write);
assert!(Level::Write < Level::Admin);
assert_eq!(
[Level::Admin, Level::Read, Level::Write].iter().max(),
Some(&Level::Admin)
);
}
#[test]
fn scopes_cover_what_they_say() {
let fx = Fixture::new();
let repo = fx.repo("acme", "widget");
assert!(Scope::Server.covers(&repo));
assert!(Scope::Namespace("acme".into()).covers(&repo));
assert!(!Scope::Namespace("acm".into()).covers(&repo));
assert!(Scope::Repo(RepoRef::new("acme", "widget").unwrap()).covers(&repo));
assert!(!Scope::Repo(RepoRef::new("acme", "widgets").unwrap()).covers(&repo));
assert!(!Scope::Repo(RepoRef::new("acme", "widget").unwrap()).covers_namespace("acme"));
}