Issue Credentials
Create a tenant and issue, list and revoke credentials with horoctl.
Every producer and reader needs a credential of its own. horoctl manages them directly in
Horopter's database: no daemon needs to be running, and horoctl needs no credential of its
own, because its authority is access to the database. This guide is for Horopter
v0.14.2.
Goal
A tenant, and one credential for each producer and reader that posts to it or reads from it, each with the narrowest role that does its job.
Prerequisites
- A Horopter database that
horopter-migratehas migrated. horoctlat the same release, from the release's archives or itsghcr.io/horopter-dev/horoctlimage.DATABASE_URLset to the database's connection string. Everyhoroctlcommand reads it, or takes--dsn.- Credentials and Capabilities, for what each role grants.
The compose stack from Try Horopter does not publish Postgres on
your machine. Run horoctl there through its bootstrap service, which has DATABASE_URL
set: replace horoctl below with docker compose run --rm bootstrap. That stack already has
a default tenant, whose id horoctl admin keys list shows.
Steps
Create a Tenant
horoctl admin tenant create --display-name acmeIt prints the new tenant's id on stdout. Keep it: every credential below is scoped to it.
Issue a Producer Credential
Name the system the credential is for with --source:
horoctl admin keys issue --tenant <tenant-id> --role producer --source githubThe full key is printed on stdout once, and cannot be recovered afterwards: put it straight into the producer's secret store. The prefix, role, tenant and locus are printed on stderr.
The locus defaults to the source, which keeps this producer's changes apart from every other
source's. To let two producers add events to the same changes, such as CI starting a deploy
that a cluster controller completes, issue both credentials the same --locus; see
Locus.
To limit the subjects a producer may post to, add --subject-pattern, such as
--subject-pattern 'acme/*'. The pattern is a glob over the whole subject: * matches any run
of characters but /, ? one character but /, [...] one character of a class, and \
escapes the next character, so acme/* covers acme/api but not acme/eu/api. It has no
{a,b} alternation, unlike the subject filter on reads. A post with a subject outside the
pattern, or with no subject, is refused with 403.
horoctl stores the pattern without checking it. A malformed one, such as an unclosed [,
refuses every post the credential makes; revoke it and issue another.
Issue a Reader Credential
horoctl admin keys issue --tenant <tenant-id> --role reader --source grafanaGive each tool that reads its own reader credential, so you can revoke one without the others.
List and Revoke
List the credentials scoped to a tenant, by prefix:
horoctl admin keys list --tenant <tenant-id>Revoke one by its prefix:
horoctl admin keys revoke --prefix <prefix>Verification
Post an event with the producer key, and read it back with the reader key:
curl -fsS -X POST "$HOROPTER_INGRESS_URL/v1/events" \
-H "Authorization: Bearer $PRODUCER_KEY" -H 'Content-Type: application/json' \
-d '{"type":"deployment.started","subject":"acme/prod","occurred_at":"2026-01-01T00:00:00Z"}'
curl -fsS -H "Authorization: Bearer $READER_KEY" \
"$HOROPTER_EGRESS_URL/v1/changes?from=2026-01-01T00:00:00Z&to=2026-01-02T00:00:00Z"The first answers with the event's ref; the second lists the change it started. The reader key
on the first command, or the producer key on the second, is answered 403.
After a revocation, the key is answered 401 within 30 seconds.
Notes
- Omit
--tenantfor a global credential, one that names its tenant on every read. Give one only to a tool that serves several tenants; see Tenants. --subject-patternis accepted only for append-only roles such asproducer; see Roles and Capabilities.