Credentials and Capabilities
What a credential grants, how it is scoped to a tenant, and what Horopter leaves to you.
Every route on Horopter's two daemons, except the health probes, needs a credential. This page explains what a credential is, what each role may do, and which protections are yours to provide. It describes Horopter v0.14.2.
Credentials
A credential is a key a producer or reader presents as a bearer token:
Authorization: Bearer horo_<prefix>_<secret>. Horopter stores only a hash of the secret, so a
key is shown once, when it is issued, and cannot be recovered afterwards. The prefix names the
key in logs and is what you give to revoke it.
A credential carries, besides its role:
- a tenant, or none: see Tenants;
- a source, the name of the system it was issued for, such as
github; - a locus, the first part of every ref it writes, which defaults to the source: see What Is Horopter;
- optionally a subject pattern, which limits the subjects its events may name.
Credentials are issued, listed and revoked with horoctl, directly against Horopter's
database; see Issue Credentials. A revoked credential is refused by
new requests within 30 seconds, the time a daemon caches a credential it has checked.
Roles and Capabilities
A credential is issued as a role, and a role is a stored set of capabilities. A route needs one capability:
| capability | lets a credential |
|---|---|
append | post events to ingress |
read | read changes and events from egress, through its own API and its adapters |
admin | repair a change, by re-folding it from its events |
issue | reserved for issuing credentials over the API, which Horopter does not have yet |
Four roles are seeded:
| role | capabilities | for |
|---|---|---|
producer | append | a system that records changes, such as a CI job |
reader | read | a tool that shows changes, such as Grafana |
tenant_admin | read, admin | an operator of one tenant |
superuser | append, read, admin, issue | an operator of every tenant |
Give each producer and each reader its own credential with the narrowest role that does its
job. A producer key that leaks lets its holder post events; a reader key lets its holder read
a tenant's changes. Only horoctl, with access to the database, issues credentials.
A subject pattern narrows append only: an event naming a subject outside the pattern is
refused. Reading and repairing changes are whole-tenant, so horoctl refuses a subject pattern
on any role that grants more than append.
Tenants
A tenant is a partition of Horopter's data: one tenant's events and changes are never served to another's credentials. A credential is scoped to one tenant, or to none:
- A tenant-scoped credential acts in its own tenant only. A producer's events land in its
credential's tenant, and an event that names a tenant is refused. A reader may name its own
tenant with
?tenant=and no other. - A global credential has no tenant, and names the tenant it reads with
?tenant=on every request.
Refusals
| answer | when |
|---|---|
401 | no credential, or one that is malformed, unknown, revoked or expired |
403 | a valid credential without the route's capability, or naming another tenant |
Every 401 has the same body, whatever the reason. Telling a revoked key from an unknown one
would let a caller probe for valid prefixes.
A read that fails inside Horopter is answered 500 with a generic body. The underlying error,
which can name tables and the database host, goes to the daemon's log instead.
What Horopter Leaves to You
- The database is the root of trust. Whoever can reach Postgres can read and write the
event log and the credential table, past every check the daemons make.
horoctlneeds no key of its own for the same reason: its authority is database access. Keep the database reachable only by Horopter and its operators. - Floods of requests. Nothing in Horopter limits how many requests arrive; see Read Admission. Put a WAF or equivalent in front of both daemons.
- The metrics listener is unauthenticated. It binds
localhostby default. Bound to another interface, it shows metric names, route paths and yourOTEL_RESOURCE_ATTRIBUTESto anyone who reaches it, though no tenant, event or database identity. - The health probes are open.
/healthzand/readyzneed no credential, so an orchestrator can probe them. Point a liveness probe at/healthzand a readiness probe at/readyz: the images have no shell, and carry noHEALTHCHECK. - Development stacks are for one machine. The compose file a release ships issues a
producer and a reader key that everyone knows, and never a
superuser. Do not run it anywhere its ports are reachable from another machine, or reuse its keys.