trstctl /docs GitHub ↗

Platform & API — how you drive trstctl, and how it runs

What it is

This page covers the "platform plumbing" — the surfaces you use to operate trstctl and the properties of how it runs: the REST API, the CLI, the web UI, OIDC single sign-on, single-binary distribution, encrypted transport, multi-tenant topology, and federation. These aren't glamorous features, but they're what make trstctl usable, secure, and operable in a real organization.

The mental model: if the feature pages are the appliances, this is the wiring, the breaker box, the front door lock, and the meter — the infrastructure that lets everything else run safely on shared premises.

Why it exists

A control plane is only as good as how you interact with it and how safely it runs. You need a programmable API for automation, a CLI for scripts and CI, a UI for humans, SSO so people log in with existing accounts, encrypted channels so nothing is sniffable, hard tenant isolation so customers can't see each other, and a distribution simple enough to actually deploy. These are the table-stakes properties an enterprise checks before it trusts a platform with its credentials.

How it works

The REST API (F10)

The API is data-driven: every route is declared once in a single registry that simultaneously generates the served http.ServeMux, the OpenAPI 3.1 document (served at /api/v1/openapi.json), and the CLI command table — so the spec, the server, and the CLI can't drift apart. Errors are RFC 7807 application/problem+json. Every mutation requires an Idempotency-Key (AN-5) recorded in PostgreSQL so a retry returns the original result. Tenant comes from the authenticated principal (AN-1), pagination uses opaque cursors, and over-budget callers get 429 with Retry-After. Code: internal/api (routes(), guard, mutate). Served.

The CLI (F11)

trstctl-cli is the API's twin: every command is a row in a table that maps trstctl-cli <group> <verb> straight to an API route, so the CLI is provably at parity with the API and carries no bespoke logic. It auto-supplies idempotency keys on mutations. Command groups: owners, issuers, identities, certificates, profiles, audit, graph, risk, agents. Code: internal/cli, cmd/trstctl-cli. Served (binary).

The web UI (F12)

The UI is a React 18 + Vite + shadcn/ui single-page app, designed to be compiled into the binary as an embedded filesystem and served on the same port and TLS certificate as the API (real asset files served directly; deep links fall back to the SPA; /api/* is left to the API handler). No separate static server to run. Code: internal/webui, web/. Library / not yet served — the SPA is built and tested (Vitest/axe), but a clean build embeds a placeholder, so the running binary does not yet serve the console; wiring a real Vite bundle is EXC-WIRE-04. See Current limitations.

OIDC single sign-on (F13)

People log in through OIDC (OpenID Connect) against any standards-compliant provider. The authorization-code flow uses random state (CSRF protection) and a mandatory nonce (replay protection); the returned id_token is verified — signature (via JWKS through internal/crypto/jose, AN-3), issuer, audience, expiry, nonce — and on success trstctl mints a short-lived, HMAC-signed, HttpOnly+Secure session cookie. That session resolves to an RBAC principal, so a browser login authorizes API calls. CI/CD instead uses API tokens (trst_-prefixed, only the SHA-256 hash stored). Code: internal/auth, internal/api/auth.go. Library / not yet served — the flow is implemented and tested, but api.WithAuth is not wired into the served composition, so /auth/login, /auth/callback, /auth/me, and /auth/logout are not served by the running binary today (API tokens are the served auth path); serving the browser login is EXC-WIRE-01. See Current limitations.

Single-binary distribution (F14)

For evaluation, the one trstctl binary embeds and supervises its own datastores: a bundled PostgreSQL (downloaded once, checksum-pinned, run on loopback) and an embedded, file-backed NATS JetStream — zero external dependencies to try it. Even bundled, Postgres runs under the non-superuser trstctl_app role so row-level security still applies (AN-1 isn't relaxed for eval). The signing service is always a separate supervised child process, never in-process (AN-4). For production, flip Postgres/NATS to external. Code: cmd/trstctl, internal/server, internal/dist. Served (binary).

Encrypted control-plane transport (F15)

Every channel is encrypted. By default the signing service is reached over a Unix domain socket with SO_PEERCRED peer-uid authentication (a 0600 socket; a different uid is rejected); across nodes it is reached over mTLS (TLS 1.3, AEAD-only, the control plane and signer each pinning the other's certificate — SIGNER-005). Either way it has no HTTP server and no SQL driver (AN-4), and at startup it disables core dumps and ptrace (AN-8). The REST API/UI is served over TLS (self-signed by default for instant start, operator cert in production, TLS 1.3, AEAD-only). Agents connect over mTLS with short-lived, auto-rotated client certificates. All TLS/x509 code lives only in internal/crypto/mtls (AN-3). Code: internal/signing, internal/crypto/mtls, internal/server/serve.go. Served.

Note: the default signer channel is a peer-authenticated UDS (co-located/sidecar); the cross-node channel is mutually-authenticated, mutually-pinned mTLS (SIGNER-005).

Multi-tenant topology (F40)

Isolation between tenants is enforced by PostgreSQL itself, not by application code (AN-1). Every table carries a tenant_id and has row-level security that denies all rows when the tenant context is unset (fail-closed). WithTenant drops to the non-superuser role and sets the tenant for the transaction, so every query is confined automatically — and a custom build linter fails the build if any repository query omits the tenant filter. A single-company deployment simply runs one tenant. Code: internal/store (WithTenant, RLS migrations), tools/trstctllint/tenantfilter.

Federation (F41)

Cross-cluster / multi-region federation — replicating the event log across regions, placing credentials by residency, and replicating audit across regions — is planned, not yet built. There is no federation code in the platform today; the design is scoped for a future sprint (S21.2), and when built it will rest on the same tenant_id (AN-1) and event-log-replication (AN-2) foundations. We document it here, honestly, as roadmap rather than a shipped capability. See Current limitations.

Use it

# the API spec (no auth needed) — point your tooling at it
curl -s https://trstctl.example.com/api/v1/openapi.json

# drive it from the CLI
trstctl-cli certificates list --limit 50
trstctl-cli audit events --type cert.issued --since 2026-01-01T00:00:00Z

# one-binary evaluation: bundled datastores, supervised signer
TRSTCTL_POSTGRES_MODE=bundled TRSTCTL_NATS_MODE=embedded ./trstctl

The web console and browser /auth/login are built and tested but not yet served by the binary (EXC-WIRE-04 / EXC-WIRE-01); today you drive the running binary through the REST API and the CLI with scoped API tokens. See Current limitations, Install, and Configuration for production setup.

Pitfalls & limits

  • Federation (F41) is roadmap, not shipped — don't design a multi-region topology around it yet.
  • TLS defaults to self-signed for instant start; set an operator cert (TRSTCTL_SERVER_TLS_MODE=file) for production, and never use disabled outside local dev.
  • Bundled datastores are for evaluation; run external PostgreSQL and NATS in production.
  • The signer is a separate process by design — don't try to collapse it in; that isolation (AN-4) is a security boundary.

Reference

  • API: OpenAPI 3.1 at GET /api/v1/openapi.json; RFC 7807 errors; Idempotency-Key on mutations; cursor pagination; 429 + Retry-After.
  • CLI groups: owners, issuers, identities, certificates, profiles, audit, graph, risk, agents.
  • Auth: /auth/login, /auth/callback, /auth/me, /auth/logout (OIDC); API tokens prefixed trst_. Config: TRSTCTL_OIDC_ISSUER, TRSTCTL_OIDC_CLIENT_ID, TRSTCTL_OIDC_REDIRECT_URI.
  • Run modes: TRSTCTL_POSTGRES_MODE (bundled/external), TRSTCTL_NATS_MODE (embedded/external), TRSTCTL_SERVER_TLS_MODE (internal/file/disabled).
  • Federation (F41): planned (S21.2), not implemented.

See also

Policy & governance (RBAC + audit) · Install · Configuration · Signing-service design · Current limitations · glossary: idempotency, mTLS, RLS, multi-tenancy

Covers: F10, F11, F12, F13, F14, F15, F40, F41

Rendered live from github.com/ctlplne/trstctl — found a mistake? edit this page.