Manage authentication
All write operations on imgsrv require a bearer principal with the
appropriate role. This page is the operational reference for managing those
principals, their API tokens, and the OIDC provisioning rules that grant
short-lived workload identities the content-writer role.
For the conceptual frame, see Auth model.
Bootstrap
On startup against a fresh PostgreSQL-backed deployment with no auth-manager
principal, imgsrv creates a one-time auth-manager principal and prints a
single bootstrap API token to standard output. The token is printed exactly
once.
imgsrv bootstrap token (one-time): imgsrv_at_…
Capture this token immediately. Use it as a bearer credential against
/v1/auth/* to perform the initial setup. The default token lifetime is 24
hours; issue a longer-lived auth-manager token before it expires.
If the bootstrap token is lost before being used: with no live
auth-manager principal present, restarting imgsrv against the same
database mints another bootstrap token. The earlier principal record remains
in place; the new run only adds another token.
Create a content-writer principal
Using the bootstrap token as Authorization: Bearer <token>:
# 1. Create the service principal.
curl -sf -X POST https://imgsrv.example.com/v1/auth/principals \
-H "Authorization: Bearer $BOOTSTRAP_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"kind": "service", "display_name": "ci-publisher"}'
# Response includes the principal id. Capture it as $PRINCIPAL_ID.
# 2. Look up the content-writer role id.
curl -sf https://imgsrv.example.com/v1/auth/roles \
-H "Authorization: Bearer $BOOTSTRAP_TOKEN"
# 3. Assign the role to the principal.
curl -sf -X PUT \
"https://imgsrv.example.com/v1/auth/principals/$PRINCIPAL_ID/roles/$ROLE_ID" \
-H "Authorization: Bearer $BOOTSTRAP_TOKEN"
# 4. Issue an API token for the principal. expires_at is required.
curl -sf -X POST \
"https://imgsrv.example.com/v1/auth/principals/$PRINCIPAL_ID/api-tokens" \
-H "Authorization: Bearer $BOOTSTRAP_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "ci-publisher-2026q2", "expires_at": "2026-09-01T00:00:00Z"}'
The plaintext token is returned exactly once in the response body. Store it in the publishing system's secret store.
Rotate an API token
Issue a new token first, deploy it, then revoke the old one:
# Issue a new token under the same principal.
curl -sf -X POST \
"https://imgsrv.example.com/v1/auth/principals/$PRINCIPAL_ID/api-tokens" \
-H "Authorization: Bearer $AUTH_MANAGER_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "ci-publisher-2026q3", "expires_at": "2026-12-01T00:00:00Z"}'
# After the new token is in production, revoke the previous one.
curl -sf -X DELETE \
"https://imgsrv.example.com/v1/auth/api-tokens/$OLD_TOKEN_ID" \
-H "Authorization: Bearer $AUTH_MANAGER_TOKEN"
Token IDs (not the plaintext) are returned by
GET /v1/auth/principals/{principal_id}/api-tokens.
Configure an OIDC publisher
OIDC provisioning rules grant content-writer to workload identities that
present a JWT from a trusted issuer. The rule names the issuer, the expected
audience, the JWT claims to forward into the CEL condition, and the
expression that determines whether the identity is accepted.
Every OIDC provisioning rule grants content-writer to identities that match
it. There is no per-rule role selection; the role is fixed.
A rule that lets the meigma/imgsrv repo publish from its master branch
via GitHub Actions:
curl -sf -X POST https://imgsrv.example.com/v1/auth/oidc-provisioning-rules \
-H "Authorization: Bearer $AUTH_MANAGER_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"display_name": "GitHub Actions — meigma/imgsrv master",
"issuer_url": "https://token.actions.githubusercontent.com",
"audience": "https://imgsrv.example.com",
"forwarded_claims": ["repository", "ref", "actor"],
"condition": "claims.repository == \"meigma/imgsrv\" && claims.ref == \"refs/heads/master\"",
"enabled": true
}'
The audience is enforced before the CEL expression runs; rules cannot bypass
audience verification. Forwarded claims are the only JWT fields exposed to
the CEL expression — claims not listed are not visible to condition.
To preview which existing principals a rule edit would affect, call
POST /v1/auth/oidc-provisioning-rules/{rule_id}/reconciliation with a
preview body before applying.
Disable or remove an OIDC rule
Disabling a rule keeps the principal records but prevents the rule from matching new tokens:
curl -sf -X PUT \
"https://imgsrv.example.com/v1/auth/oidc-provisioning-rules/$RULE_ID" \
-H "Authorization: Bearer $AUTH_MANAGER_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"enabled": false, …}'
Deleting a rule with DELETE removes the rule and, when followed by a
reconciliation, unassigns the roles from principals provisioned by it. Use
the reconciliation preview to see the affected principals before applying.
Recover when the auth-manager is locked out
If every API token under every auth-manager principal has been revoked or
lost, there is no in-band path to add a new one. Recovery requires direct
PostgreSQL access:
- Delete the existing
auth-managerprincipal rows (or the principal-role rows assigning them the role) to drop the database back into the "no live auth-manager" state. - Restart
imgsrv. Startup will print a fresh bootstrap token. - Re-create operator principals as needed.
This is intentionally not an in-band recovery: the database is the trust boundary, and a service-path recovery would defeat its purpose. See Trust model.