weebo-forgejo

The allow-list

ForgejoNamespacePolicy decides which namespace may create what, in which organization, for whom.

When several teams share a cluster, you don't want team A's namespace creating repositories in team B's organization, or minting a token for the admin account. ForgejoNamespacePolicy (short name fjnp) is the allow-list that prevents this.

What it governs

KindsGoverned by
Cluster-scoped: instances, namespace policiesKubernetes RBAC only
Namespaced, bound to a shared ForgejoInstance: users, user keys, organizations, teams, repositories, webhooks, deploy keys, OAuth2 applications, access tokensRBAC and the allow-list
Namespaced, bound to a ForgejoLocalInstanceRBAC only: the namespace's own token bounds them

No policy means everything is allowed

While no ForgejoNamespacePolicy exists, every namespace may create any namespaced kind, referencing any organization or user. As soon as one policy exists, the default becomes deny.

Administrators always need an explicit rule

An access token, an SSH key or an OAuth2 application for a site admin acts with site-admin rights, and a ForgejoUser with admin: true makes one. Such a CR is refused unless a rule explicitly lists that user in allowedUsers for the namespace and kind, even while no policy exists. An unset allowedUsers doesn't count. The reconcilers also ask Forgejo whether the account is a site admin, whatever its CR says. See Security model.

Write a policy

With at least one policy in place, a CR is accepted only if:

  1. an Allow rule matches its namespace and kind;
  2. that rule covers every organization, user and template the CR references; and
  3. no Deny rule matches. A Deny always wins.
apiVersion: forgejo.weebo.io/v1alpha1
kind: ForgejoNamespacePolicy
metadata:
  name: teams
spec:
  rules:
    # Every team-* namespace may manage repos, hooks and deploy keys in "weebo".
    - namespaces: ["team-*"]            # exact name, "*", or a prefix glob
      allowedKinds: [ForgejoRepository, ForgejoWebhook, ForgejoDeployKey]
      allowedOrganizations: [weebo]     # unset = any organization
    # Only team-a may mint tokens, and only for its own bot.
    - namespaces: [team-a]
      allowedKinds: [ForgejoAccessToken]
      allowedUsers: [team-a-bot]        # unset = ANY user. Set it.
    # Nobody may mint tokens or OAuth2 apps for admin-bot.
    - namespaces: ["*"]
      allowedKinds: [ForgejoAccessToken, ForgejoOAuth2Application]
      allowedUsers: [admin-bot]
      effect: Deny

allowedOrganizations and allowedUsers list Forgejo names — an organization's name, a user's login — not CR names: a CR name is only unique in its namespace, and a remoteName reference has none. They cover what a CR manages (a ForgejoUser's own login, a ForgejoOrganization's own name) and everything it references (organizationRef, userRef, ownerRef, memberRefs), whether through a CR or a remoteName. A user referenced by remoteName is treated as a site admin by the administrators rule above: the operator can't read its flags there, so its credentials need an explicit allowedUsers entry.

Templates

allowedTemplates restricts the template repositories a namespace may generate repositories from by Forgejo name (fromTemplate.repository). It lists owner/name patterns: an exact name, *, or a prefix glob such as batlehub/*. Unset means any template.

    - namespaces: ["team-*"]
      allowedKinds: [ForgejoRepository]
      allowedOrganizations: [weebo]
      allowedTemplates: ["batlehub/*"]  # only the shared templates

A template referenced as a ForgejoRepository of the same namespace (fromTemplate.repositoryRef) isn't governed by this list: the namespace already manages it.

Restrict templates on a shared cluster

The operator generates repositories with its admin token, which can read every repository, private ones included. Without allowedTemplates, a namespace allowed to create repositories can copy the content of any repository marked as a template into its own. While no policy exists, that is the case for every namespace.

What each kind references

A rule must cover what the CR points at:

KindReferences
ForgejoRepositoryIts owner: the organization (organizationRef) or the user (userRef); and the template it is generated from by name (fromTemplate.repository)
ForgejoWebhook on an organizationThat organization
ForgejoWebhook on a repositoryNothing beyond the repository, which must be in the same namespace and is checked itself
ForgejoDeployKeyNothing beyond the repository, as above
ForgejoOAuth2ApplicationIts owner (ownerRef), or @instance-admin without one
ForgejoAccessTokenIts user (userRef)

@instance-admin. An OAuth2 application without ownerRef belongs to the operator's own admin account. For the allow-list, it references the special user @instance-admin. A rule that restricts allowedUsers only allows such an application if it lists @instance-admin.

Site-admin credentials. A token, SSH key or OAuth2 application of a site admin — a ForgejoUser with admin: true, a user observed (managementPolicy: Observe) or referenced by remoteName, an account Forgejo reports as admin, or @instance-admin — and a ForgejoUser with admin: true itself, always need an Allow rule naming that user in allowedUsers — even while no policy exists. A token, OAuth2 application or SSH key already issued for an account that became a site admin is revoked (deleted in Forgejo). See Security model.

Where it's enforced

  • At apply time, by the admission webhook, on create and update.
    • It never blocks a delete. It also always allows an update that leaves spec unchanged, such as a label edit or the operator removing its finalizer. So revoking a namespace's access never prevents the cleanup of what it already created.
    • With the chart's default failurePolicy: Fail, these kinds can't be written while the webhook is down.
  • Before every change, by each reconciler. Tightening a policy also stops existing CRs (Ready: False, reason NamespaceNotAllowed), and a policy change re-checks them right away.

A refused kubectl apply reads (the message uses kind names without the Forgejo prefix):

admission webhook "validate.forgejo.weebo.io" denied the request:
no ForgejoNamespacePolicy rule allows AccessToken in namespace "team-b" with users ["admin-bot"]

Next

Monitoring & troubleshooting.

On this page