gunnar-server 2.0.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
Documentation
//! **A person's passkeys are rows in gunnar's identity table** (Stage L,
//! 2026-10-04), so they survive what the identity table survives: a restart of
//! the box, and — riding the same document the twin replicates
//! (`refs/gunnar/auth`) — a restore. Driven through the real plane over real
//! stores, reopened from disk between the write and the read.

use std::sync::Arc;

use gunnar_grpc::ControlPlane as _;
use gunnar_server::control_plane::CoreControlPlane;
use gunnar_server::stores::ServerStores;

fn passkey(seed: u8) -> Vec<u8> {
    let key = p256::ecdsa::SigningKey::from_slice(&[seed; 32]).expect("a scalar");
    key.verifying_key().to_sec1_point(false).as_bytes().to_vec()
}

fn session_key() -> gunnar_core::ssh_key::PrivateKey {
    gunnar_core::ssh_key::PrivateKey::from_openssh(gunnar_core::mint_ed25519().expect("mint"))
        .expect("parses")
}

fn person(stores: &ServerStores, id: &str, kind: gunnar_core::PrincipalKind) {
    stores
        .auth()
        .add_principal(gunnar_core::Principal {
            id: id.to_owned(),
            name: id.to_owned(),
            kind,
            created: 0,
        })
        .expect("principal");
}

/// ★ **A passkey written before a restart is read after it, key and all**,
/// and goes when its principal goes.
#[test]
fn a_passkey_survives_a_restart_and_goes_with_its_principal() {
    let dir = tempfile::tempdir().expect("tempdir");
    {
        let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
        person(&stores, "kim", gunnar_core::PrincipalKind::User);
        let plane = CoreControlPlane::new(Arc::clone(&stores));
        let added = plane
            .add_credential("kim", b"kim-yubikey", &passkey(1), "YubiKey", 7)
            .expect("kim's passkey");
        assert_eq!(added.credential_id, b"kim-yubikey".to_vec());
        assert_eq!(added.sign_count, 7);
    }
    let stores = Arc::new(ServerStores::open(dir.path()).expect("stores, reopened"));
    let plane = CoreControlPlane::new(Arc::clone(&stores));
    let listed = plane.credentials(Some("kim")).expect("listed");
    assert_eq!(listed.len(), 1, "{listed:?}");
    assert_eq!(listed[0].label, "YubiKey");
    let stored = plane
        .credential(b"kim-yubikey")
        .expect("read")
        .expect("the passkey is still there");
    assert_eq!(stored.public_key, passkey(1), "the key came back byte for byte");
    assert_eq!(stored.credential.principal_id, "kim");

    plane.remove_principal("kim").expect("removed");
    assert!(plane.credentials(None).expect("listed").is_empty(), "the passkey outlived kim");
    assert!(plane.credential(b"kim-yubikey").expect("read").is_none());
}

/// No human credential on a machine principal, and a credential id once.
#[test]
fn a_machine_holds_no_passkey_and_an_id_is_registered_once() {
    let dir = tempfile::tempdir().expect("tempdir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
    person(&stores, "ci", gunnar_core::PrincipalKind::Machine);
    person(&stores, "kim", gunnar_core::PrincipalKind::User);
    let plane = CoreControlPlane::new(Arc::clone(&stores));
    let refused = plane
        .add_credential("ci", b"ci-key", &passkey(2), "ci", 0)
        .expect_err("a machine holds no passkey");
    assert!(
        matches!(refused, gunnar_grpc::ControlError::FailedPrecondition(_)),
        "{refused:?}"
    );
    plane
        .add_credential("kim", b"kim-key", &passkey(3), "laptop", 0)
        .expect("kim's");
    let twice = plane
        .add_credential("kim", b"kim-key", &passkey(4), "again", 0)
        .expect_err("an id once");
    assert!(matches!(twice, gunnar_grpc::ControlError::AlreadyExists(_)), "{twice:?}");
    let ghost = plane
        .add_credential("ghost", b"ghost-key", &passkey(5), "ghost", 0)
        .expect_err("no such principal");
    assert!(matches!(ghost, gunnar_grpc::ControlError::NotFound(_)), "{ghost:?}");
}

/// ★ **A session key a web login enrolled stops authenticating at its
/// expiry** — through the gate's own key lookup (`Identity::registered_key`),
/// the real plane over real stores; and the login's counter is persisted.
#[test]
fn a_web_sessions_key_expires_and_the_counter_is_kept() {
    use gunnar_grpc::Identity as _;
    let dir = tempfile::tempdir().expect("tempdir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
    person(&stores, "kim", gunnar_core::PrincipalKind::User);
    let plane = CoreControlPlane::new(Arc::clone(&stores));
    plane
        .add_credential("kim", b"kim-yubikey", &passkey(6), "YubiKey", 0)
        .expect("kim's passkey");
    let session = session_key();
    let now = gunnar_core::now_ms();
    let outcome = plane
        .web_login(gunnar_grpc::WebLoginCommit {
            credential_id: b"kim-yubikey".to_vec(),
            sign_count: 4,
            now_unix_ms: now,
            session_openssh: session.public_key().to_openssh().expect("openssh"),
            comment: "web session".into(),
            expires_at_unix_ms: now + 1_500,
        })
        .expect("the login commits");
    assert_eq!(outcome.principal.id, "kim");
    let held = plane.registered_key(&outcome.fingerprint).expect("asked");
    assert_eq!(held.map(|k| k.principal_id).as_deref(), Some("kim"), "the session key is kim's");
    std::thread::sleep(std::time::Duration::from_millis(1_800));
    assert!(
        plane.registered_key(&outcome.fingerprint).expect("asked").is_none(),
        "an expired session key still authenticates"
    );
    let stored = plane.credential(b"kim-yubikey").expect("read").expect("there");
    assert_eq!(stored.credential.sign_count, 4);
    assert!(stored.credential.last_used_unix_ms.is_some());

    let rollback = plane
        .web_login(gunnar_grpc::WebLoginCommit {
            credential_id: b"kim-yubikey".to_vec(),
            sign_count: 4,
            now_unix_ms: gunnar_core::now_ms(),
            session_openssh: session_key().public_key().to_openssh().expect("openssh"),
            comment: "web session".into(),
            expires_at_unix_ms: gunnar_core::now_ms() + 60_000,
        })
        .expect_err("a counter that did not move is refused in the update itself");
    assert!(matches!(rollback, gunnar_grpc::ControlError::PermissionDenied(_)), "{rollback:?}");
}

/// ★ **An invite is redeemed in the same update that stores the passkey and
/// applies its server grant** (an operator's WRITE) — once; a second
/// redemption is refused.
#[test]
fn an_invite_is_redeemed_once_with_its_grant_in_one_update() {
    use gunnar_grpc::Identity as _;
    let dir = tempfile::tempdir().expect("tempdir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
    person(&stores, "kim", gunnar_core::PrincipalKind::User);
    let plane = CoreControlPlane::new(Arc::clone(&stores));
    let hash = gunnar_grpc::webauthn::sha256_hex(b"the-secret");
    plane
        .create_invite(gunnar_grpc::NewInvite {
            id: "inv-1".into(),
            principal_id: "kim".into(),
            created_by: "boss".into(),
            created_at_unix_ms: 1,
            expires_at_unix_ms: gunnar_core::now_ms() + 60_000,
            note: String::new(),
            server_level: gunnar_proto::GrantLevel::Write,
            secret_hash: hash.clone(),
        })
        .expect("kim's invite");
    let registration = |id: &[u8]| gunnar_grpc::WebRegistration {
        invite_secret_hash: Some(hash.clone()),
        principal_id: None,
        credential_id: id.to_vec(),
        public_key: passkey(7),
        label: "phone".into(),
        sign_count: 0,
        now_unix_ms: gunnar_core::now_ms(),
    };
    let registered = plane.web_register(registration(b"kim-phone")).expect("redeemed");
    assert_eq!(registered.principal.id, "kim");
    assert_eq!(registered.server_level, gunnar_proto::GrantLevel::Write);
    assert!(plane
        .may("kim", &gunnar_grpc::Access::Server, gunnar_proto::GrantLevel::Write)
        .expect("asked"));
    assert!(plane.invites().expect("listed").is_empty(), "the invite was not consumed");
    let again = plane.web_register(registration(b"kim-tablet")).expect_err("once");
    assert!(matches!(again, gunnar_grpc::ControlError::PermissionDenied(_)), "{again:?}");
    assert_eq!(plane.credentials(Some("kim")).expect("listed").len(), 1);
}

/// ★ **Revoking a passkey revokes the session keys its logins enrolled**, in
/// the real plane, through the gate's own key lookup.
#[test]
fn revoking_a_passkey_revokes_its_session_keys() {
    use gunnar_grpc::Identity as _;
    let dir = tempfile::tempdir().expect("tempdir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
    person(&stores, "kim", gunnar_core::PrincipalKind::User);
    let plane = CoreControlPlane::new(Arc::clone(&stores));
    for (id, seed) in [(&b"kim-yubikey"[..], 8), (&b"kim-phone"[..], 9)] {
        plane.add_credential("kim", id, &passkey(seed), "k", 0).expect("passkey");
    }
    let login = |id: &[u8]| {
        plane
            .web_login(gunnar_grpc::WebLoginCommit {
                credential_id: id.to_vec(),
                sign_count: 0,
                now_unix_ms: gunnar_core::now_ms(),
                session_openssh: session_key().public_key().to_openssh().expect("openssh"),
                comment: "web session".into(),
                expires_at_unix_ms: gunnar_core::now_ms() + 60_000,
            })
            .expect("logged in")
            .fingerprint
    };
    let (one, two, other) = (login(b"kim-yubikey"), login(b"kim-yubikey"), login(b"kim-phone"));
    plane.revoke_credential(b"kim-yubikey").expect("revoked").expect("it was there");
    assert!(plane.registered_key(&one).expect("asked").is_none());
    assert!(plane.registered_key(&two).expect("asked").is_none());
    assert!(plane.registered_key(&other).expect("asked").is_some(), "the phone's session went too");
}

fn approval(machine: &str, id: &[u8], seed: u8) -> gunnar_grpc::MachineApproval {
    gunnar_grpc::MachineApproval {
        machine: machine.to_owned(),
        server_level: gunnar_proto::GrantLevel::Admin,
        credential_id: id.to_vec(),
        public_key: passkey(seed),
        label: format!("installer kit on {}", machine.trim_start_matches("kit-")),
        sign_count: 0,
        now_unix_ms: gunnar_core::now_ms(),
        step_up: None,
    }
}

/// ★ **An approved machine enrolment is ONE update on the real plane**
/// (2026-10-05): the MACHINE principal made, its server grant, its credential
/// — read back after a restart, logging in as the machine — while a person's
/// id under the machine's name is refused and nothing lands.
#[test]
fn an_approved_machine_enrolment_makes_a_machine_with_its_credential_and_survives_a_restart() {
    use gunnar_grpc::Identity as _;
    let dir = tempfile::tempdir().expect("tempdir");
    {
        let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
        person(&stores, "kim", gunnar_core::PrincipalKind::User);
        let plane = CoreControlPlane::new(Arc::clone(&stores));
        plane.add_credential("kim", b"kim-yubikey", &passkey(1), "YubiKey", 5).expect("kim's passkey");
        // kim's step-up presented a counter that went backwards: refused, nothing made.
        let mut backwards = approval("kit-dwarf", b"kit-dwarf-key", 3);
        backwards.step_up = Some((b"kim-yubikey".to_vec(), 4));
        let refused = plane.approve_machine_enrolment(backwards).expect_err("a rolled-back counter");
        assert!(matches!(refused, gunnar_grpc::ControlError::PermissionDenied(_)), "{refused:?}");
        assert!(plane.principal("kit-dwarf").expect("read").is_none());
        let mut fresh = approval("kit-dwarf", b"kit-dwarf-key", 3);
        fresh.step_up = Some((b"kim-yubikey".to_vec(), 6));
        let approved = plane.approve_machine_enrolment(fresh).expect("approved");
        assert!(approved.created);
        assert_eq!(approved.principal.kind, gunnar_proto::PrincipalKind::Machine as i32);
        assert_eq!(approved.credential.principal_id, "kit-dwarf");
        assert_eq!(plane.credential(b"kim-yubikey").expect("read").expect("there").credential.sign_count, 6, "the step-up's counter is kept");
        let again = plane.approve_machine_enrolment(approval("kit-dwarf", b"kit-dwarf-key", 3)).expect_err("an id once");
        assert!(matches!(again, gunnar_grpc::ControlError::AlreadyExists(_)), "{again:?}");
    }
    let stores = Arc::new(ServerStores::open(dir.path()).expect("stores, reopened"));
    let plane = CoreControlPlane::new(Arc::clone(&stores));
    let machine = plane.principal("kit-dwarf").expect("read").expect("the machine is still there");
    assert_eq!(machine.kind, gunnar_proto::PrincipalKind::Machine as i32);
    assert!(plane.may("kit-dwarf", &gunnar_grpc::Access::Server, gunnar_proto::GrantLevel::Admin).expect("asked"));
    assert_eq!(plane.credentials(Some("kit-dwarf")).expect("listed").len(), 1);
    assert!(plane.credentials(Some("kim")).expect("listed").iter().all(|c| c.credential_id != b"kit-dwarf-key".to_vec()));
    let session = plane
        .web_login(gunnar_grpc::WebLoginCommit {
            credential_id: b"kit-dwarf-key".to_vec(),
            sign_count: 0,
            now_unix_ms: gunnar_core::now_ms(),
            session_openssh: session_key().public_key().to_openssh().expect("openssh"),
            comment: "web session".into(),
            expires_at_unix_ms: gunnar_core::now_ms() + 60_000,
        })
        .expect("the machine logs in");
    assert_eq!(session.principal.id, "kit-dwarf");

    person(&stores, "kit-person", gunnar_core::PrincipalKind::User);
    let refused = plane.approve_machine_enrolment(approval("kit-person", b"other-key", 4)).expect_err("a person under the id");
    assert!(matches!(refused, gunnar_grpc::ControlError::FailedPrecondition(_)), "{refused:?}");
    assert!(plane.credential(b"other-key").expect("read").is_none());
}

/// ★ **An approved machine never touches the owner's first invite** (the
/// stage-L constraint, 2026-10-05: the break-glass token IS his first invite).
/// The invite stays pending under its hash, the feed renews it rather than
/// calling it spent, and the operator holds no credential.
#[test]
fn an_approved_machine_leaves_the_owners_first_invite_pending() {
    let dir = tempfile::tempdir().expect("tempdir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
    let plane = CoreControlPlane::new(Arc::clone(&stores));
    plane.claim_operator_named(Some("rickard"), "").expect("the operator");
    let hash = gunnar_grpc::webauthn::sha256_hex(b"break-glass");
    assert_eq!(plane.operator_bootstrap_invite("rickard", &hash, gunnar_core::now_ms(), gunnar_grpc::seam::OPERATOR_INVITE_TTL_MS).expect("recorded"), gunnar_grpc::BootstrapInvite::Recorded);
    plane.approve_machine_enrolment(approval("kit-fresh", b"kit-fresh-key", 5)).expect("approved");
    assert!(plane.invites().expect("listed").iter().any(|i| i.invite_id == gunnar_grpc::seam::OPERATOR_INVITE_ID), "the owner's first invite is still pending");
    assert_eq!(plane.operator_bootstrap_invite("rickard", &hash, gunnar_core::now_ms(), gunnar_grpc::seam::OPERATOR_INVITE_TTL_MS).expect("asked"), gunnar_grpc::BootstrapInvite::Renewed);
    assert!(plane.credentials(Some("rickard")).expect("listed").is_empty(), "nothing on the operator");
}