Horopter

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-migrate has migrated.
  • horoctl at the same release, from the release's archives or its ghcr.io/horopter-dev/horoctl image.
  • DATABASE_URL set to the database's connection string. Every horoctl command 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 acme

It 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 github

The 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 grafana

Give 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 --tenant for a global credential, one that names its tenant on every read. Give one only to a tool that serves several tenants; see Tenants.
  • --subject-pattern is accepted only for append-only roles such as producer; see Roles and Capabilities.

On this page