Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

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"));
}