Skip to main content

Tenant RBAC

Cosmonic Control is a Kubernetes-native platform, so multi-tenant access control uses standard Kubernetes Role and RoleBinding objects. This page covers how to set up namespace-scoped RBAC for tenant teams, what the Cosmonic Control operator requires cluster-wide, and how to restrict team access to their own namespaces.

How it works​

Cosmonic Control installs one ClusterRole that the operator service account uses to manage CRDs and read core resources across all namespaces. Tenant teams are separate from the operator — they are given narrower, namespace-scoped Role objects that only permit them to manage their own workloads.

Cluster level:
  operator ServiceAccount  →  ClusterRole (cosmonic-control)
                               manages all CRDs, reads ConfigMaps/Secrets/Namespaces

Namespace level (per team):
  team-a ServiceAccount/User  →  Role (in namespace team-a)
                                  manage HTTPTrigger, Artifact

Operator RBAC​

The operator uses one ClusterRole for resources it needs cluster-wide, plus a pair of namespace-scoped Role objects in cosmonic-system for control-plane internals.

Cluster-wide permissions​

API GroupResourcesVerbs
control.cosmonic.iohttptriggers, projectenvironmentsfull CRUD
control.cosmonic.iohttptriggers/status, projectenvironments/statusget, update, patch
runtime.wasmcloud.devartifacts, hosts, workloads, workloadreplicasets, workloaddeploymentsfull CRUD
runtime.wasmcloud.dev*/statusget, update, patch
runtime.wasmcloud.dev*/finalizersupdate
"" (core)servicesget, list, watch
"" (core)podsget, list, patch, watch
"" (core)configmaps, secrets, namespacesget, list, watch

The operator reads configmaps and secrets cluster-wide to deliver configuration to components (see Component Configuration). Secrets in any namespace are readable by the operator service account. Use namespace-level RBAC to control which humans and CI systems can create or read Secrets in each namespace.

Namespaced runtime.wasmcloud.dev RBAC​

Setting operator.watchNamespaces to a non-empty list switches the workload-side RBAC from cluster-wide to per-namespace. In scoped mode the operator ClusterRole and ClusterRoleBinding are not rendered; instead, a Role + RoleBinding pair is installed in each watched namespace covering artifacts, workloads, workloadreplicasets, workloaddeployments (plus their /status and /finalizers subresources), and the core resources the operator reads. This lets a namespace-scoped install be performed with namespace-admin RBAC for the workload resources rather than cluster-admin. Leaving watchNamespaces empty preserves the cluster-wide RBAC shown in the table above.

The pods permission drives host finalizer cleanup: when a wasmCloud host pod terminates, the operator patches its finalizer so workloads reschedule immediately instead of waiting for the reconciliation loop.

Namespace-scoped permissions​

Two Role objects are installed in cosmonic-system:

RoleResourcesVerbsPurpose
cosmonic-control-<release>-leader-electioncoordination.k8s.io/leasesfull CRUDoperator leader election
cosmonic-control-<release>-host-finalizer""/podsget, list, patch, watchhost finalizer reconciliation scoped to the control-plane namespace

Optional cluster-wide metrics RBAC​

By default the chart serves the operator's /metrics endpoint as plain HTTP with no auth, and no additional ClusterRole is installed. Set metrics.secure: true in your values file to switch the endpoint to HTTPS with delegated authn/authz. In that mode the chart also installs two cluster-scoped ClusterRole objects (*-metrics-reader, *-metrics-auth) and the binding required for TokenReview / SubjectAccessReview against the API server. These APIs and the /metrics non-resource URL are not namespaced, so this RBAC cannot be scoped to a namespace — disabling secure metrics is the only way to avoid the cluster-scope grant.

Setting up a tenant namespace​

The typical pattern for onboarding a team:

  1. Create a namespace for the team
  2. Create a Role granting them access to Cosmonic Control resources in that namespace
  3. Create a RoleBinding linking the role to the team's user, group, or service account

1. Create the namespace​

kubectl create namespace team-a

2. Create a Role​

A standard developer role grants full access to HTTPTrigger and Artifact resources — the two resources developers interact with directly. Read access to the managed resources (WorkloadDeployment, Workload, etc.) is useful for debugging.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cosmonic-developer
  namespace: team-a
rules:
  # Create and manage workload entrypoints
  - apiGroups: ["control.cosmonic.io"]
    resources: ["httptriggers"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: ["control.cosmonic.io"]
    resources: ["httptriggers/status"]
    verbs: ["get", "list", "watch"]

  # Manage artifact pre-fetch (enterprise)
  - apiGroups: ["runtime.wasmcloud.dev"]
    resources: ["artifacts"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

  # Read-only access to managed resources for debugging
  - apiGroups: ["runtime.wasmcloud.dev"]
    resources:
      - workloaddeployments
      - workloadreplicasets
      - workloads
      - hosts
    verbs: ["get", "list", "watch"]

  # Manage own ConfigMaps and Secrets for component config
  - apiGroups: [""]
    resources: ["configmaps", "secrets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
kubectl apply -f cosmonic-developer-role.yaml

3. Create a RoleBinding​

Bind the role to a user, group, or service account:

# Bind to a specific user
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: team-a-developers
  namespace: team-a
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: cosmonic-developer
subjects:
  - kind: User
    name: alice@corp.com
    apiGroup: rbac.authorization.k8s.io
  - kind: User
    name: bob@corp.com
    apiGroup: rbac.authorization.k8s.io

For CI/CD pipelines, bind to a service account instead:

subjects:
  - kind: ServiceAccount
    name: team-a-ci
    namespace: team-a
kubectl apply -f team-a-rolebinding.yaml

Read-only role​

For users who need visibility without write access (e.g., on-call engineers, security auditors):

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cosmonic-viewer
  namespace: team-a
rules:
  - apiGroups:
      - control.cosmonic.io
      - runtime.wasmcloud.dev
    resources: ["*"]
    verbs: ["get", "list", "watch"]

Verifying access​

Test that a user or service account has the expected permissions with kubectl auth can-i:

# Can alice create HTTPTriggers in team-a?
kubectl auth can-i create httptriggers.control.cosmonic.io -n team-a --as alice@corp.com

# Can alice read Secrets in team-b? (should be no)
kubectl auth can-i get secrets -n team-b --as alice@corp.com

# List all permissions a user has in a namespace
kubectl auth can-i --list -n team-a --as alice@corp.com

Isolation guarantees​

Namespace-scoped RBAC controls who can manage Cosmonic Control resources. The isolation between running workloads is enforced by the WebAssembly sandbox — components running in different namespaces cannot communicate unless explicitly linked, and a component cannot access host resources (filesystem, network, environment) outside of what is declared in its localResources and allowedHosts manifest fields.

This means a compromised component cannot escalate to read another namespace's Secrets or ConfigMaps through the Wasm interface — the component model itself enforces the boundary. The RBAC layer ensures that the teams managing the manifests are also properly separated.