Credentials
Issue OAuth2 client credentials and access tokens into Kubernetes Secrets or Vault, and rotate them.
Applications often need something from Forgejo: an OAuth2 client to let users "sign in with Forgejo", or an API token to push, comment or read. The operator creates these in Forgejo and writes the result straight into a Secret (or Vault) that the application mounts. Nobody copies a secret by hand.
| Kind | Short name | Gives you |
|---|---|---|
ForgejoOAuth2Application | fjoauth | FORGEJO_CLIENT_ID, FORGEJO_CLIENT_SECRET, FORGEJO_URL |
ForgejoAccessToken | fjtoken | FORGEJO_TOKEN, FORGEJO_USERNAME, FORGEJO_URL |
Before you begin
Restrict who can mint tokens for whom
Without a ForgejoNamespacePolicy that sets
allowedUsers, any namespace allowed to create ForgejoAccessTokens can
mint one for any non-admin user. Tokens, SSH keys and OAuth2
applications of site admins (as declared by their CR or as Forgejo reports
them) always need an explicit allowedUsers entry; those already issued
for an account that becomes a site admin are revoked. Set up the
allow-list before giving teams access to these kinds.
OAuth2 applications
For applications that let users sign in with Forgejo, such as Grafana or Argo CD:
apiVersion: forgejo.weebo.io/v1alpha1
kind: ForgejoOAuth2Application
metadata:
name: grafana
namespace: monitoring
spec:
redirectUris:
- https://grafana.example.com/login/generic_oauth
# ownerRef: { name: sso-bot } # optional, see belowThe grafana Secret in monitoring receives FORGEJO_CLIENT_ID,
FORGEJO_CLIENT_SECRET and FORGEJO_URL. FORGEJO_URL is the OIDC issuer
URL to give the application.
Owner. Without ownerRef, the application belongs to the operator's
admin account. Set ownerRef to the ForgejoUser CR of a dedicated account
(for example a bot called sso-bot) to keep these applications off the
admin's profile. Changing the owner later is a
disruptive change.
When the secret changes. Forgejo generates a new client secret on every update of an application and never shows it again. So the operator only updates the application when one of these happens:
- the spec really changed;
- a target lost the secret;
- you ask for a rotation:
kubectl -n monitoring annotate fjoauth grafana \
forgejo.weebo.io/rotate-secret=$(date +%s) --overwriteAccess tokens
apiVersion: forgejo.weebo.io/v1alpha1
kind: ForgejoAccessToken
metadata:
name: ci
namespace: team-a
spec:
userRef: { name: ci-bot }
scopes: [write:repository]
repositoryRefs: [{ name: app }] # optional: only these repositories
rotationPeriod: 720h # optional: re-issue every 30 daysThe ci Secret receives FORGEJO_TOKEN, FORGEJO_USERNAME and
FORGEJO_URL.
Repository-limited tokens. With repositoryRefs, Forgejo only accepts
the read: and write: variants of the issue and repository scopes.
Any other scope makes the CR report InvalidSpec.
When a new token is issued. The old token is revoked first, then a new one is written to the targets, when:
rotationPeriodhas elapsed since the last issue;- the
forgejo.weebo.io/rotate-secretannotation gets a new value; - a target lost the token;
scopesorrepositoryRefschange. Because that replaces a working token, it needs theforgejo.weebo.io/allow-disruptive-update: "true"annotation.
Secret targets
Every kind that generates a credential takes secretTargets, and the value
is written to each of them:
secretTargets:
- name: ci-token # Secret in the CR's own namespace
- name: ci-token
namespace: team-a # explicit: must be the CR's own namespace
- backend: vault
name: ci-token # Vault: <pathPrefix>/<namespace>/ci-token
- backend: vault
name: unused
path: weebo-forgejo/team-a/ci # Vault: an explicit KV v2 path, under
# <pathPrefix>/<namespace>/ unless the
# instance's pathGrants open it- No
secretTargets. One target named after the CR is used, on the instance's default backend. - Namespaced kinds may only write into their own namespace: a target naming another namespace is refused. To hand a credential to another namespace, create the CR there.
ForgejoUserandForgejoUserKeyare namespaced too: their targets default to, and stay in, their own namespace.- Kubernetes Secrets are created, then replaced whole on change (keys no longer emitted go), labelled
app.kubernetes.io/managed-by: weebo-forgejo, annotatedforgejo.weebo.io/ownerwith the CR (<Kind>/<namespace>/<name>), and deleted with their CR. The operator never overwrites a Secret it doesn't own that way: a target naming one fails withSecretTargetFailed. See Security model. - Vault writes are skipped when the stored value is unchanged, so routine reconciles don't pile up KV versions.