Authorization
Kokumi has no application-level permission model. Everything a user can
do, through the UI, the API, or kubectl, is decided by ordinary Kubernetes
Role/ClusterRole bindings on ServiceAccounts. A user’s permissions are
identical whether they use the kokumi UI or kubectl directly, and RBAC still
applies to users who bypass the UI entirely.
How it works
- Authentication - the server validates the OIDC token (or admin
credentials) and extracts stable identity claims:
sub,email,groups. - Mapping - the identity is mapped to one or more Kubernetes ServiceAccounts in the install namespace via annotations (below).
- Execution - every API operation the server performs on the user’s behalf is executed as the mapped ServiceAccount using Kubernetes impersonation. The API server evaluates plain RoleBindings against that ServiceAccount and returns real 403s, which the UI surfaces.
The cluster’s API server never needs to trust the OIDC provider: the kokumi server is the only audience of the identity provider, and it translates identities to ServiceAccounts that the cluster already understands.
Identity-to-ServiceAccount mapping
ServiceAccounts in the install namespace (e.g. kokumi) are mapped to users
via annotations:
apiVersion: v1
kind: ServiceAccount
metadata:
name: custom-sa
namespace: kokumi
annotations:
kokumi.dev/identity-sub: "1234567890" # matches the OIDC "sub" claim
kokumi.dev/identity-email: "admin@kokumi.dev" # matches the "email" claim
kokumi.dev/identity-groups: "platform,admins" # matches ANY of the token's groupsMatching rules:
- An identity matches a ServiceAccount when its
subequalskokumi.dev/identity-sub, itsemailequalskokumi.dev/identity-email, or any of itsgroupsappears in the comma-separatedkokumi.dev/identity-groupslist. - Multiple annotations on one ServiceAccount are OR-ed.
- A user may match multiple ServiceAccounts; reads (lists and gets) are merged across all of them, so the effective read access is the union. Writes are performed as the first ServiceAccount authorized for the operation.
- Users who match no ServiceAccount are authenticated but authorized for nothing (every operation returns 403).
Adding your own ServiceAccount
A ServiceAccount you add needs two grants besides its annotations.
1. Allow kokumi-server to impersonate it. The server may only impersonate
the ServiceAccounts named in the kokumi-server-impersonator Role, by default
the three built-in ones. This keeps it from acting as any other ServiceAccount
in the install namespace, such as the controller’s. Grant each additional
ServiceAccount in its own Role, so upgrades do not overwrite it:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kokumi-server-impersonate-custom-sa
namespace: kokumi
rules:
- apiGroups: [""]
resources: ["serviceaccounts"]
resourceNames: ["custom-sa"]
verbs: ["impersonate"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kokumi-server-impersonate-custom-sa
namespace: kokumi
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kokumi-server-impersonate-custom-sa
subjects:
- kind: ServiceAccount
name: kokumi-server
namespace: kokumiWithout this grant every check as custom-sa fails. The user keeps the access
of their other mapped ServiceAccounts.
2. Grant its permissions with ordinary RBAC, the same objects you would use for any other subject:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: custom-sa-editor
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kokumi-server-editor-role
subjects:
- kind: ServiceAccount
name: custom-sa
namespace: kokumiA RoleBinding in a single namespace is enforced for API calls, but the UI
does not use it: lists come from the live event stream, and the
UI capabilities are evaluated across all namespaces. Use a
ClusterRoleBinding for users of the UI.
Check both grants with kubectl:
kubectl -n kokumi auth can-i impersonate serviceaccounts/custom-sa \
--as=system:serviceaccount:kokumi:kokumi-server
kubectl auth can-i create orders.delivery.kokumi.dev --all-namespaces \
--as=system:serviceaccount:kokumi:custom-saBuilt-in ServiceAccounts
The installation ships three identity ServiceAccounts in the install namespace:
| ServiceAccount | Default binding | Default identity mapping | Purpose |
|---|---|---|---|
kokumi-admin | kokumi-server-role (full CRUD incl. kitchens) | kokumi.dev/identity-groups: "kokumi-admin" | Full access; also used by the built-in admin login |
kokumi-editor | kokumi-server-editor-role (CRUD, no kitchens) | kokumi.dev/identity-groups: "kokumi-editor" | Editors |
kokumi-viewer | kokumi-server-viewer-role (read-only) | kokumi.dev/identity-groups: "kokumi-viewer" | Read-only users |
None of the built-in roles grants access to Secrets. They are bound
cluster-wide, which would include the kokumi-signing namespace, and the key
that signs every Kokumi artifact is a Secret there: an identity that could
read or replace it could forge trusted signatures. See
Registry credentials for granting Secret access per
namespace, and Signing for how the key is protected.
The built-in admin login always acts as kokumi-admin, regardless of the
annotations. Additionally, OIDC users are matched against the default
kokumi.dev/identity-groups values: with the defaults, an OIDC identity whose
token carries the kokumi-admin group gets full access, kokumi-editor
editor access, and kokumi-viewer read-only access (multiple matches are
merged as described above).
To grant the devops OIDC group editor access:
kubectl -n kokumi annotate sa kokumi-editor \
kokumi.dev/identity-groups=devops --overwriteRegistry credentials
A Registry lends the credentials of its Secret to every Product and Order in its namespace that lists it, and the controller reads them with its own identity. Who may name a Secret in a Registry therefore decides who may use it. Three things guard that:
- The admission policy
registry-secret-accessadmits a Registry that names a Secret (secretReforcertSecretRef) only when its author maygetthat Secret. An update that keeps theurl, theschemeand both references needs no Secret access; changing theurlor switching toHTTPdoes, because either sends the credentials somewhere else. - The UI reads through a Registry with the user’s identity. Browsing a
Repository, a Product’s source or a Preparation’s artifact, and previews,
connect through the Registries of the object with the user’s own
geton their Secrets. Without it the server answers 403 and names the Registry and the Secret; it never falls back to an anonymous read. The server’s pull cache serves only content the caller could resolve with their own credentials. - The UI stores credentials as the user. A Registry saved with a username
and password gets a
kubernetes.io/dockerconfigjsonSecret<registry>-credentials, owned by the Registry. The server never overwrites a Secret of that name it did not create.
The built-in roles grant no Secret access. Platform admins grant it per tenant
namespace by binding the kokumi-registry-credentials ClusterRole (get,
create and patch on secrets) with a RoleBinding:
kubectl -n team-a create rolebinding kokumi-registry-credentials \
--clusterrole=kokumi-registry-credentials \
--serviceaccount=kokumi:kokumi-editorkokumi-registry-credentials with a ClusterRoleBinding, in the
kokumi-signing namespace, or in the install namespace. kokumi-signing holds
the artifact signing key. The install namespace holds the Secrets of the
Kitchen’s auth settings: the session token signing key, the admin password
hash and the OIDC client secret. Reading or patching them lets a user forge
sessions or take over login. Treat the install namespace as admin-only, as
Argo CD does with its own namespace, and keep Registries with credentials in
tenant namespaces.Without the binding, users can still create Registries without credentials
and list the Registries others created in Products and Orders; the
controller connects through them. The Registry form shows whether the user
may use credentials in the selected namespace, from the namespaced capability
registries.credentials:update.
Everyone who may edit Products or Orders in a namespace can use the
credentials of its Registries, as with imagePullSecrets. Keep credentials
of different trust levels in different namespaces.
UI capabilities
The UI decides which features to offer from a list of capabilities the
server reports for the signed-in user (GET /api/v1/whoami). Each capability
is derived from SelfSubjectAccessReviews on the mapped ServiceAccounts, so it
always follows your RBAC. Pages the user can never use (such as Settings) are
hidden; actions the user may not perform (such as Promote) are shown disabled
with an explanation.
Capabilities are display hints only. Every operation is still authorized by the API server, so a user who calls the API directly gets exactly the same answer.
| Capability | Granted when a mapped ServiceAccount may | UI |
|---|---|---|
settings.general:update | update the kitchens/default object in the install namespace | Settings, General tab |
settings.auth:update | update the kitchens/default object in the install namespace | Settings, Authentication tab |
settings.signing:update | update the kitchens/default object in the install namespace | Settings, Signing tab |
settings.scanning:update | update the kitchens/default object in the install namespace | Settings, Scanning tab |
orders:create / orders:update / orders:delete | create / update / delete orders | New, Edit, Delete Order; edits on rendered files |
products:create / products:update / products:delete | create / update / delete products | New, Edit, Delete Product |
registries:create / registries:update / registries:delete | create / patch / delete registries | New, Edit, Delete Registry |
repositories:create / repositories:update / repositories:delete | create / patch / delete repositories | New, Edit, Delete Repository |
preparations:promote | create and update servings | Promote and Rollback |
preparations:approve | approve preparations, and the user signed in with OIDC | Approve and Request changes |
orders:delete stops delivery without delete on servings: the garbage
collector deletes the Order’s Serving, and with it the Argo CD Application.
The deployed workloads keep running until you remove them.
One capability holds per namespace and is reported by
GET /api/v1/namespaces/{namespace}/capabilities:
| Capability | Granted when a mapped ServiceAccount may | UI |
|---|---|---|
registries.credentials:update | get, create and patch secrets in the namespace | Credentials and Secrets in the Registry form |
Except for the settings and the namespaced capability, capabilities are
evaluated across all namespaces:
a permission granted only by a namespaced RoleBinding does not enable the
action in the UI. This matches the live event stream, which only serves users
who may list resources cluster-wide. The settings capabilities share one RBAC
check today; they are reported separately so they can diverge later.
Results are cached for up to 90 seconds, and the UI refreshes them whenever the
browser window regains focus. A check that fails, for example on a mapped
ServiceAccount the server may not impersonate, does not fail the request: the
capability is granted if another mapped ServiceAccount allows it. Otherwise it
is reported as undetermined, and the UI keeps its previous answer for it, or
denies it on the first load.
The kokumi-server ServiceAccount
The server’s own ServiceAccount holds no write permissions on kokumi resources. It can only:
impersonatethe identity ServiceAccounts in the install namespace, by defaultkokumi-admin,kokumi-editorandkokumi-viewer(see Adding your own ServiceAccount). This is the trust boundary: treat access to this ServiceAccount as cluster-admin equivalent for kokumi resources,- read ServiceAccounts (mapping resolution),
- read Secrets in the install namespace (login/OIDC config). It cannot read
the signing key, which lives in the
kokumi-signingnamespace, - read (get/list/watch) kokumi resources to power the live UI event stream: the hub must be able to see all events in order to filter them per user (each connected user only receives events for resources their mapped ServiceAccounts may list). The server never writes kokumi resources with its own identity.
The kokumi-scanner ServiceAccount
The scanner has no Kubernetes identity: its ServiceAccount mounts no token and is bound to no role. The controller manager sends it the registry credentials of each scan with the request, and signs what it returns. See Vulnerability Scanning.
UI and kubectl parity
Because authorization is plain Kubernetes RBAC on the mapped ServiceAccount, the same view is available from kubectl:
kubectl auth can-i list orders \
--as=system:serviceaccount:kokumi:kokumi-editor
# Inspect exactly what the UI sees
kubectl get orders --as=system:serviceaccount:kokumi:kokumi-editorAudit logs on the API server record both identities: the kokumi-server ServiceAccount (the impersonator) and the mapped ServiceAccount (the impersonated user).