Authentication & access control¶
Community Edition — OIDC / SSO is Enterprise
Pluggable authentication ships in the Community Edition; OIDC / SSO is part of the Enterprise Edition. See Editions.
vornik's control-plane API and UI can require authentication, and once a user is authenticated their role and project scope decide what they can see and do. This page covers turning auth on, the ways a request can be authenticated, and the role/group model behind access control.
Turning auth on¶
vornikctl doctor feature enable auth
This sets api.auth_enabled and restarts the daemon. Have an admin key in
place before you enable it — once on, unauthenticated requests to control-plane
routes are rejected, and an admin key is your way back in.
To roll it out cautiously, api.auth_dry_run evaluates and logs what would
happen on each request without actually enforcing — useful for confirming your
keys and groups are right before you flip enforcement on.
Ways to authenticate¶
Requests are checked against an ordered chain of backends; the first one that recognises a credential wins, and the chain fails closed on any unexpected error. The shipped backends:
- API keys — static keys from config, and per-project database-backed keys (see below).
- Browser sign-in (SSO) — GitHub OAuth. A user clicks "Sign in with GitHub", and vornik creates a session cookie. An optional GitHub-organisation requirement gates who may sign in.
- Break-glass admin keys — always-on key access for operators, so an identity-provider outage can never lock you out.
- Webhook signatures — HMAC verification for inbound webhooks.
Browser SSO ships with GitHub today. The backend is built around a provider interface so additional identity providers can slot in behind the same session and role machinery later.
Per-project API keys¶
Issue scoped keys bound to a single project. Each key is stored hashed, carries its own project, and can be limited to specific workflows:
vornikctl key create --project research-desk --name ci-bot --expires 90d
vornikctl key list --project research-desk
vornikctl key rotate <keyID> --project research-desk
vornikctl key revoke <keyID> --project research-desk
A per-project key can't be repointed at another project by a request header — its project is fixed at creation, which is what makes it safe to hand to an external caller.
Roles, groups, and project scope¶
Identities resolve to a principal with a role and a set of projects:
- Roles. There are two —
admin(instance-wide) anduser(project-scoped). - Groups. Users belong to groups; a group carries a role and a set of projects. Membership in any admin-role group makes a user an admin with access to everything. Otherwise a user's accessible projects are the union of their groups' projects.
- Project scope enforcement. A user's project set is stamped onto every
request and checked by the same guard that prevents cross-project access. A
freshly signed-in user who belongs to no group is authenticated but sees
nothing — every project-scoped route returns
403until they're added to a group.
The first admins are bootstrapped from configuration (auth.bootstrap_admins),
so there's always a way in on a fresh install. Users are provisioned
automatically on first sign-in.
Break-glass admin access¶
Admin access is governed by the admin config block:
admin.enabledturns the admin surface on. When it's off,/ui/admin/*URLs return404(not403) so they don't even reveal their existence to probes.admin.allowed_keysis the key-based break-glass path — admin keys are compared in constant time and keep working alongside SSO-based admins, so an identity-provider outage never locks the operator out.
Audit¶
Every admin-initiated mutation — config edits, integration changes, danger-zone
confirmations — writes an audit row before it returns, so the trail survives even
a crash mid-change. Review it at /ui/admin/audit.
Configuration reference¶
| Key | Meaning |
|---|---|
api.auth_enabled |
require authentication on control-plane routes |
api.auth_dry_run |
evaluate + log auth without enforcing |
server.accepted_hosts |
with auth off (or dry-run): extra Host names the daemon answers; any other name gets 403 (DNS-rebinding guard). IP addresses, localhost and the public base URL's host need no entry |
auth.bootstrap_admins |
identities seeded into the first admin group (channel:external_id) |
auth.external_base_url |
public base URL, required when a sign-in provider is set |
auth.session.lifetime / auth.session.idle_timeout |
session expiry controls |
auth.providers.github.client_id / client_secret_file |
GitHub OAuth credentials |
auth.providers.github.org |
restrict/soft-gate sign-in to a GitHub organisation |
auth.providers.github.org_member_role |
auto-grant org members this role on login (user/admin; empty = approve manually) |
auth.providers.github.org_member_projects |
projects an auto-granted user-role org member receives (* = all) |
admin.enabled / admin.allowed_keys |
admin surface + break-glass keys |