weebo-forgejo

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.

KindShort nameGives you
ForgejoOAuth2ApplicationfjoauthFORGEJO_CLIENT_ID, FORGEJO_CLIENT_SECRET, FORGEJO_URL
ForgejoAccessTokenfjtokenFORGEJO_TOKEN, FORGEJO_USERNAME, FORGEJO_URL

Before you begin

  • A connected ForgejoInstance.
  • For tokens, a ForgejoUser to issue them for, usually a bot.

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 below

The 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) --overwrite

Access 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 days

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

  • rotationPeriod has elapsed since the last issue;
  • the forgejo.weebo.io/rotate-secret annotation gets a new value;
  • a target lost the token;
  • scopes or repositoryRefs change. Because that replaces a working token, it needs the forgejo.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.
  • ForgejoUser and ForgejoUserKey are 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, annotated forgejo.weebo.io/owner with 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 with SecretTargetFailed. See Security model.
  • Vault writes are skipped when the stored value is unchanged, so routine reconciles don't pile up KV versions.

Next

The allow-list.

On this page