Skip to main content

Crate gunnar_core

Crate gunnar_core 

Source
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:

questionanswer
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 implementationhere
path sandbox → (namespace, repo)sandbox::resolve_repo_path
key fingerprintauth::fingerprint_of_blob → Fingerprint
host keyhost_key::ensure_host_key
principal + grant lookupAuthTable

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 Read principal refused a write, an Admin allowed 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::SharedControlStore;
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::doc puts 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, beside auth’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 tracing targets, 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 locked Cargo.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.