Expand description
gunnar’s repository model, identity table, ACL and path sandbox. The
layer gunnar-ssh, gunnar-grpc and gunnar-cli all sit on (the ACL model, multi-tenancy).
§What this crate answers
An SSH connection arrives with a public key and a command line. Four questions have to be answered before a single byte of git protocol is spoken, and this crate answers all four:
| question | answer |
|---|---|
| who is this? | AuthTable::principal_by_fingerprint, on the key’s SHA256: fingerprint, looked up in gunnar’s own table |
| what did they ask for? | resolve_repo_path: the sandbox, and the security core of the server |
| does it exist? | RepoCatalog::open |
| may they? | AuthTable::may. upload-pack needs Level::Read, receive-pack needs Level::Write |
use gunnar_core::{resolve_repo_path, Level};
// Already unquoted: git wraps this in single quotes on the wire and
// `gunnar_ssh::command` takes them off. The sandbox does not know SSH
// exists (the one-sandbox rule).
let path = resolve_repo_path("team/sub/project.git").expect("a legal path");
assert_eq!(path.namespace(), "team/sub");
assert_eq!(path.name(), "project");
assert_eq!(path.key(), "team/sub/project");
// The escape does not resolve, and never becomes a filesystem path at all.
assert!(resolve_repo_path("ns/../../etc/passwd").is_err());
assert!(Level::Admin.permits(Level::Read));
assert!(!Level::Read.permits(Level::Write));§One sandbox, one fingerprint
gunnar-ssh was written concurrently with this crate and grew its own
path sandbox, key fingerprint, host-key loader and auth lookup. The two
sandboxes disagreed: one enforced exactly two path components, this one
permits nested namespaces. Two security checks that disagree are worse than
either alone. gunnar-ssh now depends on this crate and its copies are
deleted. What survives lives here:
| the one implementation | here |
|---|---|
path sandbox → (namespace, repo) | sandbox::resolve_repo_path |
| key fingerprint | auth::fingerprint_of_blob → Fingerprint |
| host key | host_key::ensure_host_key |
| principal + grant lookup | AuthTable |
The fingerprint is defined over bytes, not over an ssh-key type,
because russh pins ssh-key =0.7.0-rc.10 and this crate links the
workspace’s 0.6; see auth::fingerprint_of_blob.
§No OS underneath
There are no OS users. No /etc/passwd, no home directories, no
~/.ssh/authorized_keys, no ssh-keygen subprocess. gunnar owns its own
identity table, mints its own host key in-process, and works when this
binary is PID 1 in a distroless image (the no-OS server rule). The one function that
touches a filesystem, RepoRef::path_under, exists for the fs backend
and is where the symlink check lives; nothing else in this crate knows what
a path is.
§Where the state lives
In the control store (control) and, for the host key, on the
gunnar_store seam — and on nothing else. The catalog and the identity
table are JSON documents behind a name in a redb database, updated by a real
compare-and-swap; the host key is a SecretStore
entry. Until 2026-08-09 the documents were git blobs behind git references,
spelled in gunnar’s own ObjectStore/RefStore traits; both are gone (the
storage contract is GitOps now, and it takes objects as packs). See
control for why they moved and doc for what the shape costs.
§Verification
Measure, never assert, concretely, in tests/:
- the sandbox’s escape table is asserted to go red against a deliberately broken resolver, and its legitimate-path table is asserted to still resolve, because a sandbox that rejects everything is the identity-value trap;
- fingerprints are pinned against known answers from the real OpenSSH binary, not against gunnar recomputing its own arithmetic;
- the ACL is asserted in both directions: a
Readprincipal refused a write, anAdminallowed one; - the host key is asserted byte-identical across a simulated restart, and two fresh mints are asserted to differ, so the first assertion cannot pass on a constant;
- both sha1 and sha256 repositories are exercised.
Re-exports§
pub use gate::AccountGate;pub use gate::GateReason;pub use gate::ADMINISTRATIVE_HOLDER;pub use gate::PURGE_LEASE_MS;pub use activity::PrincipalActivity;pub use activity::Seen as ActivitySeen;pub use activity::ACTIVITY_FILE;pub use audit::AuditChain;pub use audit::AuditEntry;pub use audit::AuditError;pub use audit::AuditEvent;pub use audit::Recorded;pub use audit::AUDIT_FILE;pub use auth::operator_access::OperatorAccess;pub use auth::operator_access::OperatorAccessState;pub use auth::operator_access::Shield;pub use auth::operator_access::OPERATOR_ACCESS_DEFAULT_MINUTES;pub use auth::operator_access::OPERATOR_ACCESS_MAX_MINUTES;pub use auth::operator_access::OPERATOR_REQUEST_TTL_MS;pub use auth::fingerprint_of_blob;pub use auth::mint_token_secret;pub use auth::token_hash;pub use auth::AuthTable;pub use auth::Bootstrap;pub use auth::BootstrapRecord;pub use auth::InviteStanding;pub use auth::RedeemedInvite;pub use auth::REDEEMED_INVITE_KEEP_MS;pub use auth::BulkUser;pub use auth::EmployeeRoom;pub use auth::Enrolled;pub use auth::Fingerprint;pub use auth::Readiness;pub use auth::RoomAnswer;pub use auth::RoomAsk;pub use auth::RoomGrant;pub use auth::SeatLot;pub use auth::SsoLogin;pub use auth::SsoLoginDone;pub use auth::SsoProvision;pub use auth::SsoUserPatch;pub use auth::AUTH_REF;pub use auth::BOOTSTRAP_ADMIN_ID;pub use auth::LAST_USED_GRANULARITY;pub use auth::SHA256_PREFIX;pub use auth::AccountRoom;pub use auth::Clan;pub use auth::ClanInvite;pub use auth::ClanMember;pub use auth::ClanRefusal;pub use auth::ClanRole;pub use auth::ClanRepoOffer;pub use catalog::point_head_at;pub use catalog::NewRepo;pub use catalog::RepoCatalog;pub use catalog::CATALOG_REF;pub use control::memory_control;pub use control::memory_control_store;pub use control::open_control;pub use control::open_control_store;pub use control::open_control_with_db;pub use control::BlobId;pub use control::ControlStore;pub use control::ControlDb;pub use control::ControlView;pub use control::DocVersion;pub use control::RamCopy;pub use control::RedbControlStore;pub use control::ResumeReport;pub use control::BLOB_ID_LEN;pub use control::RAM_COPY_CEILING_BYTES;pub use control::TWIN_APPLIED;pub use error::human_bytes;pub use error::Error;pub use error::Result;pub use flow::day_of;pub use flow::iso8601_utc;pub use flow::midnight_of;pub use flow::Fleet;pub use flow::FlowDay;pub use flow::FlowLedger;pub use flow::FlowObserver;pub use flow::FlowStore;pub use flow::Glance;pub use flow::NamespaceFlow;pub use flow::Refusal;pub use flow::Transfer;pub use flow::DAY_MS;pub use flow::FLOW_REF;pub use flow::PRINCIPAL_CEILING;pub use flow::RETENTION_DAYS;pub use forge::Event;pub use forge::ForgeEvent;pub use forge::Review;pub use forge::SignedEvent;pub use forge::FORGE_NAMESPACE;pub use membership::fold as fold_membership;pub use membership::Membership;pub use membership::MembershipAction;pub use membership::MembershipEvent;pub use membership::SignedMembershipEvent;pub use membership::MEMBERSHIP_NAMESPACE;pub use key_attestation::AttestationProblem;pub use key_attestation::AttestingKey;pub use key_attestation::KeyAttestation;pub use key_attestation::SignedKeyAttestation;pub use key_attestation::KEY_ATTESTATION_NAMESPACE;pub use sshsig::EventProblem;pub use history::read_sealed;pub use history::History;pub use history::RefTransaction;pub use history::SealReport;pub use host_key::ensure_host_key;pub use host_key::mint_ed25519;pub use host_key::os_rng;pub use host_key::HostKey;pub use issuer::Issued;pub use issuer::Issuer;pub use issuer::IssuerOrigin;pub use issuer::Pki;pub use issuer::TrustedCa;pub use issuer::DEFAULT_ISSUER_DAYS;pub use issuer::DEFAULT_LEAF_DAYS;pub use issuer::ISSUER_REF;pub use mirror_store::credential_secret_key;pub use mirror_store::MirrorCredentialKind;pub use mirror_store::MirrorRecord;pub use mirror_store::MirrorStore;pub use mirror_store::MAX_RESUMES;pub use mirror_store::MIRROR_REF;pub use model::now_ms;pub use model::AccessToken;pub use model::Grant;pub use model::HashKind;pub use model::Level;pub use model::Namespace;pub use model::Principal;pub use model::PrincipalKind;pub use model::PublicKey;pub use model::Invite;pub use model::Repo;pub use model::Scope;pub use model::UnixMillis;pub use model::Visibility;pub use model::WebCredential;pub use model::ANONYMOUS_PRINCIPAL_ID;pub use observe::InFlightGuard;pub use observe::MetricsSnapshot;pub use observe::RequestId;pub use observe::ServerMetrics;pub use observe::TARGETS;pub use observe::TARGET_AUDIT;pub use observe::TARGET_GIT;pub use observe::TARGET_METRICS;pub use observe::TARGET_POLICY;pub use observe::TARGET_SSH;pub use observe::TARGET_STORE;pub use policy_store::PolicyStore;pub use policy_store::POLICY_REF;pub use provenance::BuildRecord;pub use provenance::SignedBuild;pub use provenance::Subject;pub use provenance::PROVENANCE_NAMESPACE;pub use pulls::PullRecord;pub use pulls::PullState;pub use pulls::Pulls;pub use pulls::Transition as PullTransition;pub use pulls::PULLS_REF;pub use sandbox::resolve_repo_path;pub use sandbox::PathReject;pub use sandbox::RepoRef;pub use seal::is_sealed;pub use seal::seal;pub use seal::unseal;pub use seal::Kek;pub use seal::PlaintextPolicy;pub use seal::SealedSecretStore;pub use seal::KEK_ENV;pub use seal::KEK_FILE;pub use seal::SEAL_MAGIC;pub use secrets::legacy_secret_log;pub use secrets::MigratingSecretStore;pub use secrets::RedbSecretStore;pub use secrets::LEGACY_SECRET_DIRS;pub use transparency::SignedTreeHead;pub use transparency::TreeHead;pub use transparency::STH_NAMESPACE;pub use webhook::sign_delivery;pub use webhook::verify_delivery;pub use webhook::DeliveryProblem;pub use webhook::SignedDelivery;pub use webhook::WEBHOOK_NAMESPACE;pub use gunnar_store;pub use ssh_key;
Modules§
- account_
audit - The account admin’s audit log: per account, hash-chained, a year (2026-10-05). The account admin’s audit log — every security-relevant event in ONE account, append-only, hash-chained, kept per account, a year by default (owner’s enterprise list, 2026-10-05).
- activity
- When a principal last signed in and last changed something (2026-10-21). When a principal last signed in, and when it last changed something — the two answers an administrator reads in the console’s user list (owner, 2026-10-21).
- arrow_
log - Secrets as an Arrow IPC log: there is no database.
- audit
- The control plane’s audit trail, made tamper-evident — an append-only hash chain over the decisions the gRPC gate makes.
- auth
- The identity table and the ACL (the ACL model).
- catalog
- The repository catalog: namespaces and repositories, keyed by
<namespace>/<repo>. - control
- Where the control plane’s own state lives — the catalog, the identity
table, and every document
crate::docputs behind a name. - doc
- How the catalog and the identity table are actually stored.
- encryption_
key - People’s encryption keys (
refs/gunnar/encryption-keys, CRYPT-YUBI 2026-10-26): one key pair per person for every encrypted repository. - error
- The one error type this crate reports through.
- expiry
- How close a certificate is to bricking the box it protects. An appliance has no shell and its only door is the mutual listener, so this is graded and REFUSED early rather than reported. An expired certificate on a box with no shell is a brick, and this is the thing that refuses before it becomes one.
- flow
- The flow ledger: what MOVED through a namespace, per UTC day — bytes in and out, pushes, fetches, refusals, and when the tenant was last seen. Every other meter gunnar keeps is a STOCK meter and reads the same for a busy tenant and a silent one. The flow ledger: what MOVED through a namespace, per UTC day.
- forge
- The forge’s evidence spine: signed, append-only issue and review events. The review half of a pull request: its evidence (roadmap phase 1).
- gate
- The account gate: the row that says a user may do nothing, and the lease that keeps a dead purge from bricking an account. The account gate: one row that says a user may do nothing.
- history
- The reference history, read.
- host_
key - The SSH host key (the secret-store seam).
- issuer
- The server is its own certificate issuer (the certificate-identity plan).
- key_
attestation - Where a repository’s pull-mirror registration lives (
refs/gunnar/mirrors). Group membership as signed, append-only evidence — the signing half of punkt 9, besideauth’s live table. A key added to a principal is attested by that principal (2026-09-12). - membership
- Group membership as signed, append-only evidence (punkt 9, the signing half).
- mirror_
store - Where a repository’s pull-mirror registration lives (
refs/gunnar/mirrors). - model
- The ACL model: principals, keys, namespaces, repositories, grants.
- name_
filter - The name filter: offensive, abusive and reserved names (2026-10-05). The name filter — offensive, abusive and reserved names, refused where names are made (owner’s enterprise list, 2026-10-05; the 2026-10-03 model: “gunnar-server has process to filter out perverted names etc and address it directly or to the accounts admin”).
- observe
- What an operator can see: the
tracingtargets, the request id, and the server’s counters. - policy_
store - Where a repository’s policy document lives (
refs/gunnar/policy). Where a repository’s policy document lives (refs/gunnar/policy). - provenance
- Build provenance, signed — links 5 and 6 of the chain. Build provenance, signed — links 5 and 6 of the chain.
- pulls
- Pull requests: the record half.
- sandbox
- The path sandbox: the security core of the server.
- sbom
- A CycloneDX SBOM out of a locked
Cargo.lock. A CycloneDX SBOM out of a lockedCargo.lock(roadmap phase 3). - seal
- Sealing secrets at rest.
- secrets
- Where the server’s secrets live — the SSH host key and the issuer private key, in the control database beside the catalog and the identity table.
- settings
- The server’s generic settings (
refs/gunnar/settings, owner decision 2026-10-26). - sshsig
- One signer and one verifier for every signed event log (one implementation). One signer and one verifier for gunnar’s signed event logs (one implementation).
- sso
- Company single sign-on: the identity providers, the directories, SCIM. Company single sign-on: the rows (owner, 2026-10-05: “SSO for companies in gunnar, OIDC and SAML, so a company’s people log in with their own identity system” — and SCIM provisioning beside it).
- transparency
- The transparency log’s arithmetic: a Merkle tree and a signed tree head. The transparency log’s arithmetic: a Merkle tree over records, and a signed tree head over the tree.
- txlog
- The transaction log — every mutation of durable state on the primary, in one strictly increasing sequence, appended before the state changes, fsynced per commit, torn tail detected and cut on open.
- webhook
- Outbound webhook delivery signatures: the sshsig over each delivered body. Outbound webhook delivery signatures (the event stream, the outbound leg).
Constants§
- DEFAULT_
BRANCH - The default branch a repository gets when none is named.