2 - Namespaces
Assign Namespace to tenants
Alice, once logged with her credentials, can create a new Namespace in her Tenant, as simply issuing:
kubectl create ns solar-production
Alice started the name of the Namespace prepended by the name of the Tenant: this is not a strict requirement but it is highly suggested because it is likely that many different Tenants would like to call their Namespaces production, test, or demo, etc. The enforcement of this naming convention is optional and can be controlled by the cluster administrator with forceTenantPrefix option.
Alice can deploy any resource in any of the Namespaces. That is because she is the owner of the tenant solar and therefore she has full control over all Namespaces assigned to that Tenant.
kubectl -n solar-development run nginx --image=docker.io/nginx
kubectl -n solar-development get pods
Every Namespaces assigned to a Tenant has an owner reference pointing to the Tenant object itself. In Addition each Namespaces has a label capsule.clastix.io/tenant=<tenant_name> identifying the Tenant it belongs to (Read More).
The Namespaces are tracked as part of the Tenant status:
$ kubectl get tnt solar -o yaml
...
status:
...
# Simplie list of namespaces
namespaces:
- solar-dev
- solar-prod
- solar-test
# Size (Amount of namespaces)
size: 3
# Detailed information about each namespace
spaces:
- conditions:
- lastTransitionTime: "2025-12-04T10:23:17Z"
message: reconciled
reason: Succeeded
status: "True"
type: Ready
- lastTransitionTime: "2025-12-04T10:23:17Z"
message: not cordoned
reason: Active
status: "False"
type: Cordoned
metadata: {}
name: solar-prod
uid: ad8ea663-9457-4b00-ac67-0778c4160171
- conditions:
- lastTransitionTime: "2025-12-04T10:23:25Z"
message: reconciled
reason: Succeeded
status: "True"
type: Ready
- lastTransitionTime: "2025-12-04T10:23:25Z"
message: not cordoned
reason: Active
status: "False"
type: Cordoned
metadata: {}
name: solar-test
uid: 706e3d30-af2b-4acc-9929-acae7b887ab9
- conditions:
- lastTransitionTime: "2025-12-04T10:23:33Z"
message: reconciled
reason: Succeeded
status: "True"
type: Ready
- lastTransitionTime: "2025-12-04T10:23:33Z"
message: not cordoned
reason: Active
status: "False"
type: Cordoned
metadata: {}
name: solar-dev
uid: e4af5283-aad8-43ef-b8b8-abe7092e25d0
By default the following rules apply for namespaces:
- A
Namespace can not be moved from a Tenant to another one (or anywhere else). Namespaces are deleted when the Tenant is deleted.
If you feel like these rules are too restrictive, you must implement your own custom logic to handle these cases, for example, with Finalizers for Namespaces.
If namespaces are not correctly assigned to tenants, make sure to evaluate your Capsule Users Configuration.
Multiple Tenants
A single team is likely responsible for multiple lines of business. For example, in our sample organization Acme Corp., Alice is responsible for both the Solar and Green lines of business. It’s more likely that Alice requires two different Tenants, for example, solar and green to keep things isolated.
By design, the Capsule operator does not permit a hierarchy of Tenants, since all Tenants are at the same levels. However, we can assign the ownership of multiple Tenants to the same user or group of users.
Bill, the cluster admin, creates multiple Tenants having alice as owner:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
and
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: green
spec:
owners:
- name: alice
kind: User
Alternatively, the ownership can be assigned to a group called solar-and-green for both Tenants:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: solar-and-green
kind: Group
See Ownership for more details on how to assign ownership to a group of users.
The two tenants remain isolated from each other in terms of resources assignments, e.g. ResourceQuotas, Nodes, StorageClasses and IngressClasses, and in terms of governance, e.g. NetworkPolicies, PodSecurityPolicies, Trusted Registries, etc.
When Alice logs in, she has access to all namespaces belonging to both the solar and green Tenants.
Tenant Prefix
We recommend to use the forceTenantPrefix for production environments.
If the forceTenantPrefix option is enabled, which is not the case by default, the Namespaces are automatically assigned to the right tenant by Capsule because the operator does a lookup on the tenant names.
For example, Alice creates a Namespace called solar-production and green-production:
kubectl create ns solar-production
kubectl create ns green-production
And they are assigned to the Tenant based on their prefix:
$ kubectl get tnt
NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR AGE
green Active 1 3m26s
solar Active 1 3m26s
However alice can create any Namespace, which does not have a prefix of any of the Tenants she owns, for example production:
$ kubectl create ns production
Error from server (Forbidden): admission webhook "owner.namespace.capsule.clastix.io" denied the request: The Namespace prefix used doesn't match any available Tenant
Label
The default behavior, if the forceTenantPrefix option is not enabled, Alice needs to specify the Tenant name as a label capsule.clastix.io/tenant=<desired_tenant> in the Namespace manifest:
kind: Namespace
apiVersion: v1
metadata:
name: solar-production
labels:
capsule.clastix.io/tenant: solar
If not specified, Capsule will deny with the following message: Unable to assign Namespace to Tenant:
$ kubectl create ns solar-production
Error from server (Forbidden): admission webhook "owner.namespace.capsule.clastix.io" denied the request: Please use capsule.clastix.io/tenant label when creating a namespace
Cordon
It is possible to cordon a Namespace from a Tenant, preventing anything from being changed within this Namespace. This is useful for production Namespaces where you want to avoid any accidental changes or if you have some sort of change freeze period.
This action can be performed by the TenantOwner by adding the label projectcapsule.dev/cordoned=true to the Namespace:
kubectl patch namespace solar-production --patch '{"metadata": {"labels": {"projectcapsule.dev/cordoned": "true"}}}' --as alice --as-group projectcapsule.dev
To uncordon the Namespace, simply remove the label or set it to false:
kubectl patch namespace solar-production --patch '{"metadata": {"labels": {"projectcapsule.dev/cordoned": "false"}}}' --as alice --as-group projectcapsule.dev
Note: If the entire Tenant is cordoned all Namespaces within the Tenant will be cordoned as well. Meaning a single Namespace can not be uncordoned if the Tenant is cordoned.
3 - Permissions
Grant permissions for tenants
Ownership
Capsule introduces the principal, that tenants must have owners (Tenant Owners). The owner of a tenant is a user or a group of users that have the right to create, delete, and manage the tenant’s namespaces and other tenant resources. However an owner does not have the permissions to manage the tenants they are owner of. This is still done by cluster-administrators.
At any time you are able to verify which users or groups are owners of a tenant by checking the owners field of the Tenant status subresource:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
...
status:
owners:
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: Group
name: oidc:org:devops:a
- clusterRoles:
- admin
- capsule-namespace-deleter
- mega-admin
- controller
kind: ServiceAccount
name: system:serviceaccount:capsule:controller
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: User
name: alice
To explain these entries, let’s inspect one of them:
kind: It can be User, Group or ServiceAccountname: Is the reference name of the user, group or serviceaccount we want to bindclusterRoles: ClusterRoles which are bound for each namespace of teh tenant to the owner. By default, Capsule assigns admin and capsule-namespace-deleter roles to each owner, but you can customize them as explained in Owner Roles section.
With this information available you
Tenant Owners
Tenant Owners can be declared as dedicated cluster scoped Resources called TenantOwner. This allows the cluster admin to manage the ownership of tenants in a more flexible way, for example by adding labels and annotations to the TenantOwner resources.
apiVersion: capsule.clastix.io/v1beta2
kind: TenantOwner
metadata:
labels:
team: devops
name: devops
spec:
kind: Group
name: "oidc:org:devops:a"
This TenantOwner can now be matched by any tenant. Essentially we define on a per tenant basis which TenantOwners should be owners of the tenant (Each item under spec.permissions.matchOwners is understood as OR selection.):
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
labels:
kubernetes.io/metadata.name: solar
name: solar
spec:
permissions:
matchOwners:
- matchLabels:
team: devops
- matchLabels:
customer: x
Since the ownership is now loosely coupled, all TenantOwners matching the given labels will be owners of the tenant. We can verify this via the .status.owners field of the Tenant resource:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
...
status:
owners:
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: Group
name: oidc:org:devops:a
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: User
name: alice
This can also be combined with direct owner declarations. In the example, both alice user and all TenantOwners with label team: devops and TenantOwners with label customer: x will be owners of the solar tenant.
```yaml
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
labels:
kubernetes.io/metadata.name: oil
name: solar
spec:
owners:
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: User
name: alice
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: ServiceAccount
name: system:serviceaccount:capsule:controller
permissions:
matchOwners:
- matchLabels:
team: devops
- matchLabels:
customer: x
If we create a TenantOwner where the .spec.name and .spec.kind matches one of the owners declared in the tenant, the entries wille be merged. That’s mainly relevant for the clusterRoles:
apiVersion: capsule.clastix.io/v1beta2
kind: TenantOwner
metadata:
labels:
customer: x
name: controller
spec:
kind: ServiceAccount
name: "system:serviceaccount:capsule:controller"
clusterRoles:
- "mega-admin"
- "controller"
Again we can verify the resulting owners via the .status.owners field of the Tenant resource:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
...
status:
owners:
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: Group
name: oidc:org:devops:a
- clusterRoles:
- admin
- capsule-namespace-deleter
- mega-admin
- controller
kind: ServiceAccount
name: system:serviceaccount:capsule:controller
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: User
name: alice
We. can see that the system:serviceaccount:capsule:controller ServiceAccount now has additional mega-admin and controller roles assigned.
Users
Bill, the cluster admin, receives a new request from Acme Corp’s CTO asking for a new Tenant to be onboarded and Alice user will be the TenantOwner. Bill then assigns Alice’s identity of alice in the Acme Corp. identity management system. Since Alice is a TenantOwner, Bill needs to assign alice the Capsule group defined by –capsule-user-group option, which defaults to projectcapsule.dev.
To keep things simple, we assume that Bill just creates a client certificate for authentication using X.509 Certificate Signing Request, so Alice’s certificate has "/CN=alice/O=projectcapsule.dev".
Bill creates a new Tenant solar in the CaaS management portal according to the Tenant’s profile:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
Bill checks if the new Tenant is created and operational:
kubectl get tenant solar
NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR AGE
solar Active 0 33m
Note that namespaces are not yet assigned to the new Tenant. The Tenant owners are free to create their namespaces in a self-service fashion and without any intervention from Bill.
Once the new Tenant solar is in place, Bill sends the login credentials to Alice. Alice can log in using her credentials and check if she can create a namespace
kubectl auth can-i create namespaces
yes
or even delete the namespace
kubectl auth can-i delete ns -n solar-production
yes
However, cluster resources are not accessible to Alice
kubectl auth can-i get namespaces
no
kubectl auth can-i get nodes
no
kubectl auth can-i get persistentvolumes
no
including the Tenant resources
kubectl auth can-i get tenants
no
Groups
In the example above, Bill assigned the ownership of solar Tenant to alice user. If another user, e.g. Bob needs to administer the solar Tenant, Bill can assign the ownership of solar Tenant to such user too:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
- name: bob
kind: User
However, it’s more likely that Bill assigns the ownership of the solar Tenant to a group of users instead of a single one, especially if you use OIDC Authentication. Bill creates a new group account solar-users in the Acme Corp. identity management system and then he assigns Alice and Bob identities to the solar-users group.
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: solar-users
kind: Group
With the configuration above, any user belonging to the solar-users group will be the owner of the solar Tenant with the same permissions of Alice. For example, Bob can log in with his credentials and issue
kubectl auth can-i create namespaces
yes
All the groups you want to promot to TenantOwners must be part of the Group Scope. You have to add solar-users to the CapsuleConfiguration Group Scope to make it work.
ServiceAccounts
You can use the Group subject to grant ServiceAccounts the ownership of a Tenant. For example, you can create a group of ServiceAccounts and assign it to the Tenant:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: system:serviceaccount:tenant-system:robot
kind: ServiceAccount
Bill can create a ServiceAccount called robot, for example, in the tenant-system namespace and leave it to act as TenantOwner of the solar Tenant
kubectl --as system:serviceaccount:tenant-system:robot --as-group projectcapsule.dev auth can-i create namespaces
yes
since each service account in a namespace is a member of following group:
system:serviceaccounts:{service-account-namespace}
You have to add system:serviceaccounts:{service-account-namespace} to the CapsuleConfiguration Group Scope or system:serviceaccounts:{service-account-namespace}:{service-account-name} to the CapsuleConfiguration User Scope to make it work.
Within a Tenant, a ServiceAccount can be promoted to a TenantOwner. For example, Alice can create a ServiceAccount called robot in the solar Tenant and promote it to be a TenantOwner (This requires Alice to be an owner of the Tenant as well):
kubectl label sa gitops-reconcile -n green-test owner.projectcapsule.dev/promote=true --as alice --as-group projectcapsule.dev
Now the ServiceAccount robot can create namespaces in the solar Tenant:
kubectl create ns green-valkey--as system:serviceaccount:green-test:gitops-reconcile
To revoke the promotion, Alice can just remove the label:
kubectl label sa gitops-reconcile -n green-test owner.projectcapsule.dev/promote- --as alice --as-group projectcapsule.dev
This feature must be enabled in the CapsuleConfiguration.
Owner Roles
By default, all TenantOwners will be granted with two ClusterRole resources using the RoleBinding API:
admin: the Kubernetes default one, admin, that grants most of the namespace scoped resourcescapsule-namespace-deleter: a custom clusterrole, created by Capsule, allowing to delete the created namespaces
You can observe this behavior when you get the Tenant solar:
$ kubectl get tnt solar -o yaml
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
labels:
kubernetes.io/metadata.name: solar
name: solar
spec:
ingressOptions:
hostnameCollisionScope: Disabled
limitRanges: {}
networkPolicies: {}
owners:
# -- HERE -- #
- clusterRoles:
- admin
- capsule-namespace-deleter
kind: User
name: alice
labels:
projectcapsule.dev/sample: "true"
annotations:
projectcapsule.dev/sample: "true"
resourceQuotas:
scope: Tenant
status:
namespaces:
- solar-production
- solar-system
size: 2
state: Active
In the example below, assuming the TenantOwner creates a namespace solar-production in Tenant solar, you’ll see the Role Bindings giving the TenantOwner full permissions on the Tenant namespaces:
$ kubectl get rolebinding -n solar-production
NAME ROLE AGE
capsule-solar-0-admin ClusterRole/admin 111m
capsule-solar-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 111m
When Alice creates the namespaces, the Capsule controller assigns to Alice the following permissions, so that Alice can act as the admin of all the Tenant namespaces:
$ kubectl get rolebinding -n solar-production -o yaml
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: "2024-02-25T14:02:36Z"
labels:
capsule.clastix.io/role-binding: 8fb969aaa7a67b71
capsule.clastix.io/tenant: solar
projectcapsule.dev/sample: "true"
annotations:
projectcapsule.dev/sample: "true"
name: capsule-solar-0-admin
namespace: solar-production
ownerReferences:
- apiVersion: capsule.clastix.io/v1beta2
blockOwnerDeletion: true
controller: true
kind: Tenant
name: solar
uid: 1e6f11b9-960b-4fdd-82ee-7cd91a2db052
resourceVersion: "2980"
uid: 939da5ae-7fec-4300-8db2-223d3049b43f
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: "2024-02-25T14:02:36Z"
labels:
capsule.clastix.io/role-binding: b8822dde20953fb1
capsule.clastix.io/tenant: solar
projectcapsule.dev/sample: "true"
annotations:
projectcapsule.dev/sample: "true"
name: capsule-solar-1-capsule-namespace-deleter
namespace: solar-production
ownerReferences:
- apiVersion: capsule.clastix.io/v1beta2
blockOwnerDeletion: true
controller: true
kind: Tenant
name: solar
uid: 1e6f11b9-960b-4fdd-82ee-7cd91a2db052
resourceVersion: "2982"
uid: bbb4cd79-ce0d-41b0-a52d-dbed71a9b48a
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: capsule-namespace-deleter
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
kind: List
metadata:
resourceVersion: ""
In some cases, the cluster admin needs to narrow the range of permissions assigned to TenantOwners by assigning a Cluster Role with less permissions than above. Capsule supports the dynamic assignment of any ClusterRole resources for each TenantOwner.
For example, assign user Joe the Tenant ownership with only view permissions on Tenant namespaces:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
- name: joe
kind: User
clusterRoles:
- view
you’ll see the new Role Bindings assigned to Joe:
$ kubectl get rolebinding -n solar-production
NAME ROLE AGE
capsule-solar-0-admin ClusterRole/admin 114m
capsule-solar-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 114m
capsule-solar-2-view ClusterRole/view 1s
so that Joe can only view resources in the Tenant namespaces:
kubectl --as joe --as-group projectcapsule.dev auth can-i delete pods -n solar-production
no
Please, note that, despite created with more restricted permissions, a TenantOwner can still create namespaces in the Tenant because he belongs to the projectcapsule.dev group. If you want a user not acting as TenantOwner, but still operating in the Tenant, you can assign additional RoleBindings without assigning him the Tenant ownership.
Custom ClusterRoles are also supported. Assuming the cluster admin creates:
kubectl apply -f - << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: tenant-resources
rules:
- apiGroups: ["capsule.clastix.io"]
resources: ["tenantresources"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
EOF
These permissions can be granted to Joe
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
- name: joe
kind: User
clusterRoles:
- view
- tenant-resources
For the given configuration, the resulting RoleBinding resources are the following ones:
$ kubectl -n solar-production get rolebindings
NAME ROLE AGE
capsule-solar-0-admin ClusterRole/admin 90s
capsule-solar-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 90s
capsule-solar-2-view ClusterRole/view 90s
capsule-solar-3-tenant-resources ClusterRole/prometheus-servicemonitors-viewer 25s
Role Aggregation
Sometimes the admin role is missing certain permissions. You can aggregate the admin role with a custom role, for example, gateway-resources:
kubectl apply -f - << EOF
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: gateway-resources
labels:
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["*"]
EOF
Proxy Owner Authorization
This feature will be deprecated in a future release of Capsule. Instead use ProxySettings
When you are using the Capsule Proxy, the tenant owner can list the cluster-scoped resources. You can control the permissions to cluster scoped resources by defining proxySettings for a tenant owner.
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: joe
kind: User
clusterRoles:
- view
- tenant-resources
Additional Rolebindings
With Tenant rolebindings you can distribute namespaced rolebindings to all namespaces which are assigned to a namespace. Essentially it is then ensured the defined rolebindings are present and reconciled in all namespaces of the Tenant. This is useful if users should have more insights on Tenant basis. Let’s look at an example.
Assuming a cluster-administrator creates the following clusterRole:
kubectl apply -f - << EOF
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: prometheus-servicemonitors-viewer
rules:
- apiGroups: ["monitoring.coreos.com"]
resources: ["servicemonitors"]
verbs: ["get", "list", "watch"]
EOF
Now the cluster-administrator creates wants to bind this clusterRole in each namespace of the solar Tenant. He creates a tenantRoleBinding:
kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
additionalRoleBindings:
- clusterRoleName: 'prometheus-servicemonitors-viewer'
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: joe
labels:
projectcapsule.dev/sample: "true"
annotations:
projectcapsule.dev/sample: "true"
EOF
As you can see the subjects is a classic rolebinding subject. This way you grant permissions to the subject user Joe, who only can list and watch servicemonitors in the solar tenant namespaces, but has no other permissions.
Custom Resources
Capsule grants admin permissions to the TenantOwners but is only limited to their namespaces. To achieve that, it assigns the ClusterRole admin to the TenantOwner. This ClusterRole does not permit the installation of custom resources in the namespaces.
In order to leave the TenantOwner to create Custom Resources in their namespaces, the cluster admin defines a proper Cluster Role. For example:
kubectl apply -f - << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argoproj-provisioner
rules:
- apiGroups:
- argoproj.io
resources:
- applications
- appprojects
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
EOF
Bill can assign this role to any namespace in the Alice’s Tenant by setting it in the Tenant manifest:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
- name: joe
kind: User
additionalRoleBindings:
- clusterRoleName: 'argoproj-provisioner'
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
- apiGroup: rbac.authorization.k8s.io
kind: User
name: joe
With the given specification, Capsule will ensure that all Alice’s namespaces will contain a RoleBinding for the specified Cluster Role. For example, in the solar-production namespace, Alice will see:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: capsule-solar-argoproj-provisioner
namespace: solar-production
subjects:
- kind: User
apiGroup: rbac.authorization.k8s.io
name: alice
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argoproj-provisioner
With the above example, Capsule is leaving the TenantOwner to create namespaced custom resources.
Take Note: a TenantOwner having the admin scope on its namespaces only, does not have the permission to create Custom Resources Definitions (CRDs) because this requires a cluster admin permission level. Only Bill, the cluster admin, can create CRDs. This is a known limitation of any multi-tenancy environment based on a single shared control plane.
Administrators
Administrators are users that have full control over all Tenants and their namespaces. They are typically cluster administrators or operators who need to manage the entire cluster and all its Tenants. However as administrator you are automatically Owner of all Tenants.Tenants This means that administrators can create, delete, and manage namespaces and other resources within any Tenant, given you are using label assignments for tenants.
4 - Quotas
Strategies on granting quotas on tenant-basis
With help of Capsule, Bill, the cluster admin, can set and enforce resources quota and limits for Alice’s Tenant.
Resource Quota
Deprecated
This feature will be deprecated in a future release of Capsule. Instead use
Resource Pools to handle any cases around distributed ResourceQuotas
With help of Capsule, Bill, the cluster admin, can set and enforce resources quota and limits for Alice’s Tenant. Set resources quota for each Namespace in the Alice’s Tenant by defining them in the Tenant spec:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
namespaceOptions:
quota: 3
resourceQuotas:
scope: Tenant
items:
- hard:
limits.cpu: "8"
limits.memory: 16Gi
requests.cpu: "8"
requests.memory: 16Gi
- hard:
pods: "10"
The resource quotas above will be inherited by all the Namespaces created by Alice. In our case, when Alice creates the Namespace solar-production, Capsule creates the following resource quotas:
kind: ResourceQuota
apiVersion: v1
metadata:
name: capsule-solar-0
namespace: solar-production
labels:
tenant: solar
spec:
hard:
limits.cpu: "8"
limits.memory: 16Gi
requests.cpu: "8"
requests.memory: 16Gi
---
kind: ResourceQuota
apiVersion: v1
metadata:
name: capsule-oil-1
namespace: solar-production
labels:
tenant: solar
spec:
hard:
pods : "10"
Alice can create any resource according to the assigned quotas:
kubectl -n solar-production create deployment nginx --image nginx:latest --replicas 4
At Namespaces solar-production level, Alice can see the used resources by inspecting the status in ResourceQuota:
kubectl -n solar-production get resourcequota capsule-solar-1 -o yaml
...
status:
hard:
pods: "10"
services: "50"
used:
pods: "4"
When defining ResourceQuotas you might want to consider distributing LimitRanges via Tenant Replications:
apiVersion: capsule.clastix.io/v1beta2
kind: TenantResource
metadata:
name: solar-limitranges
namespace: solar-system
spec:
resyncPeriod: 60s
resources:
- namespaceSelector:
matchLabels:
capsule.clastix.io/tenant: solar
rawItems:
- apiVersion: v1
kind: LimitRange
metadata:
name: cpu-resource-constraint
spec:
limits:
- default: # this section defines default limits
cpu: 500m
defaultRequest: # this section defines default requests
cpu: 500m
max: # max and min define the limit range
cpu: "1"
min:
cpu: 100m
type: Container
Tenant Scope
By setting enforcement at Tenant level, i.e. spec.resourceQuotas.scope=Tenant, Capsule aggregates resources usage for all Namespaces in the Tenant and adjusts all the ResourceQuota usage as aggregate. In such case, Alice can check the used resources at the Tenant level by inspecting the annotations in ResourceQuota object of any Namespace in the Tenant:
kubectl -n solar-production get resourcequotas capsule-solar-1 -o yaml
apiVersion: v1
kind: ResourceQuota
metadata:
annotations:
quota.capsule.clastix.io/used-pods: "4"
quota.capsule.clastix.io/hard-pods: "10"
...
or
kubectl -n solar-development get resourcequotas capsule-solar-1 -o yaml
apiVersion: v1
kind: ResourceQuota
metadata:
annotations:
quota.capsule.clastix.io/used-pods: "4"
quota.capsule.clastix.io/hard-pods: "10"
...
When the aggregate usage for all Namespaces crosses the hard quota, then the native ResourceQuota Admission Controller in Kubernetes denies Alice’s request to create resources exceeding the quota:
kubectl -n solar-development create deployment nginx --image nginx:latest --replicas 10
Alice cannot schedule more pods than the admitted at Tenant aggregate level.
kubectl -n solar-development get pods
NAME READY STATUS RESTARTS AGE
nginx-55649fd747-6fzcx 1/1 Running 0 12s
nginx-55649fd747-7q6x6 1/1 Running 0 12s
nginx-55649fd747-86wr5 1/1 Running 0 12s
nginx-55649fd747-h6kbs 1/1 Running 0 12s
nginx-55649fd747-mlhlq 1/1 Running 0 12s
nginx-55649fd747-t48s5 1/1 Running 0 7s
and
kubectl -n solar-production get pods
NAME READY STATUS RESTARTS AGE
nginx-55649fd747-52fsq 1/1 Running 0 22m
nginx-55649fd747-9q8n5 1/1 Running 0 22m
nginx-55649fd747-r8vzr 1/1 Running 0 22m
nginx-55649fd747-tkv7m 1/1 Running 0 22m
Namespace Scope
By setting enforcement at the Namespace level, i.e. spec.resourceQuotas.scope=Namespace, Capsule does not aggregate the resources usage and all enforcement is done at the Namespace level.
Namespace Quotas
The cluster admin, can control how many Namespaces Alice, creates by setting a quota in the Tenant manifest spec.namespaceOptions.quota:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
namespaceOptions:
quota: 3
Alice can create additional Namespaces according to the quota:
kubectl create ns solar-development
kubectl create ns solar-test
While Alice creates Namespaces, the Capsule controller updates the status of the Tenant so Bill, the cluster admin, can check the status:
$ kubectl describe tenant solar
...
status:
Namespaces:
solar-development
solar-production
solar-test
Size: 3 # current namespace count
State: Active
...
Once the Namespace quota assigned to the tenant has been reached, Alice cannot create further Namespaces:
$ kubectl create ns solar-training
Error from server (Cannot exceed Namespace quota: please, reach out to the system administrators):
admission webhook "namespace.capsule.clastix.io" denied the request.
The enforcement on the maximum number of Namespaces per Tenant is the responsibility of the Capsule controller via its Dynamic Admission Webhook capability.
Custom Resources
This feature is still in an alpha stage and requires a high amount of computing resources due to the dynamic client requests.
Kubernetes offers by default ResourceQuota resources, aimed to limit the number of basic primitives in a Namespace.
Capsule already provides the sharing of these constraints across the Tenant Namespaces, however, limiting the amount of namespaced Custom Resources instances is not upstream-supported.
Starting from Capsule v0.1.1, this can be done using a special annotation in the Tenant manifest.
Imagine the case where a Custom Resource named mysqls in the API group databases.acme.corp/v1 usage must be limited in the Tenant solar: this can be done as follows.
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
annotations:
quota.resources.capsule.clastix.io/mysqls.databases.acme.corp_v1: "3"
spec:
additionalRoleBindings:
- clusterRoleName: mysql-namespace-admin
subjects:
- kind: User
name: alice
owners:
- name: alice
kind: User
The Additional Role Binding referring to the Cluster Role mysql-namespace-admin is required to let Alice manage their Custom Resource instances.
The pattern for the quota.resources.capsule.clastix.io annotation is the following:
quota.resources.capsule.clastix.io/${PLURAL_NAME}.${API_GROUP}_${API_VERSION}
You can figure out the required fields using kubectl api-resources.
When alice will create a MySQL instance in one of their Tenant Namespace, the Cluster Administrator can easily retrieve the overall usage.
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
annotations:
quota.resources.capsule.clastix.io/mysqls.databases.acme.corp_v1: "3"
used.resources.capsule.clastix.io/mysqls.databases.acme.corp_v1: "1"
spec:
owners:
- name: alice
kind: User
Node Pools
Bill, the cluster admin, can dedicate a pool of worker nodes to the oil Tenant, to isolate the Tenant applications from other noisy neighbors. To achieve this approach use NodeSelectors.
5 - Enforcement
Configure policies and restrictions on tenant-basis
Scheduling
LimitRanges
This feature will be deprecated in a future release of Capsule. Instead use TenantReplications
Bill, the cluster admin, can also set Limit Ranges for each Namespace in Alice’s Tenant by defining limits for pods and containers in the Tenant spec:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
...
limitRanges:
items:
- limits:
- type: Pod
min:
cpu: "50m"
memory: "5Mi"
max:
cpu: "1"
memory: "1Gi"
- limits:
- type: Container
defaultRequest:
cpu: "100m"
memory: "10Mi"
default:
cpu: "200m"
memory: "100Mi"
min:
cpu: "50m"
memory: "5Mi"
max:
cpu: "1"
memory: "1Gi"
- limits:
- type: PersistentVolumeClaim
min:
storage: "1Gi"
max:
storage: "10Gi"
Limits will be inherited by all the Namespaces created by Alice. In our case, when Alice creates the Namespace solar-production, Capsule creates the following:
apiVersion: v1
kind: LimitRange
metadata:
name: capsule-solar-0
namespace: solar-production
spec:
limits:
- max:
cpu: "1"
memory: 1Gi
min:
cpu: 50m
memory: 5Mi
type: Pod
---
apiVersion: v1
kind: LimitRange
metadata:
name: capsule-solar-1
namespace: solar-production
spec:
limits:
- default:
cpu: 200m
memory: 100Mi
defaultRequest:
cpu: 100m
memory: 10Mi
max:
cpu: "1"
memory: 1Gi
min:
cpu: 50m
memory: 5Mi
type: Container
---
apiVersion: v1
kind: LimitRange
metadata:
name: capsule-solar-2
namespace: solar-production
spec:
limits:
- max:
storage: 10Gi
min:
storage: 1Gi
type: PersistentVolumeClaim
Note: being the limit range specific of single resources, there is no aggregate to count.
Alice doesn’t have permission to change or delete the resources according to the assigned RBAC profile.
kubectl -n solar-production auth can-i patch resourcequota
no
kubectl -n solar-production auth can-i delete resourcequota
no
kubectl -n solar-production auth can-i patch limitranges
no
kubectl -n solar-production auth can-i delete limitranges
no
LimitRange Distribution with TenantReplications
In the future Cluster-Administrators must distribute LimitRanges via TenantReplications. This is a more flexible and powerful way to distribute LimitRanges, as it allows to distribute any kind of resource, not only LimitRanges. Here’s an example of how to distribute a LimitRange to all the Namespaces of a tenant:
apiVersion: capsule.clastix.io/v1beta2
kind: TenantResource
metadata:
name: solar-limitranges
namespace: solar-system
spec:
resyncPeriod: 60s
resources:
- namespaceSelector:
matchLabels:
capsule.clastix.io/tenant: solar
rawItems:
- apiVersion: v1
kind: LimitRange
metadata:
name: cpu-resource-constraint
spec:
limits:
- default: # this section defines default limits
cpu: 500m
defaultRequest: # this section defines default requests
cpu: 500m
max: # max and min define the limit range
cpu: "1"
min:
cpu: 100m
type: Container
PriorityClasses
Pods can have priority. Priority indicates the importance of a Pod relative to other Pods. If a Pod cannot be scheduled, the scheduler tries to preempt (evict) lower priority Pods to make scheduling of the pending Pod possible. See Kubernetes documentation.
In a multi-tenant cluster, not all users can be trusted, as a tenant owner could create Pods at the highest possible priorities, causing other Pods to be evicted/not get scheduled.
To prevent misuses of Pod PriorityClass, Bill, the cluster admin, can enforce the allowed Pod PriorityClass at tenant level:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
priorityClasses:
matchLabels:
env: "production"
With the said Tenant specification, Alice can create a Pod resource if spec.priorityClassName equals to:
- Any
PriorityClass which has the label env with the value production
If a Pod is going to use a non-allowed PriorityClass, it will be rejected by the Validation Webhook enforcing it.
Assign Pod PriorityClass as tenant default
Note: This feature supports type PriorityClass only on API version scheduling.k8s.io/v1
This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (globalDefault=true) that acts only at the cluster level.
It’s possible to assign each Tenant a PriorityClass which will be used, if no PriorityClass is set on pod basis:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
priorityClasses:
default: "tenant-default"
matchLabels:
env: "production"
Let’s create a PriorityClass which is used as the default:
kubectl apply -f - << EOF
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: tenant-default
labels:
env: "production"
value: 1313
preemptionPolicy: Never
globalDefault: false
description: "This is the default PriorityClass for the solar-tenant"
EOF
Note the globalDefault: false which is important to avoid the PriorityClass to be used as the default for all the Tenants. If a Pod has no value for spec.priorityClassName, the default value for PriorityClass (tenant-default) will be used.
RuntimeClasses
Pods can be assigned different RuntimeClasses. With the assigned runtime you can control Container Runtime Interface (CRI) is used for each pod. See Kubernetes documentation for more information.
To prevent misuses of Pod RuntimeClasses, Bill, the cluster admin, can enforce the allowed PodRuntimeClasses at Tenant level:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
runtimeClasses:
matchLabels:
env: "production"
With the said Tenant specification, Alice can create a Pod resource if spec.runtimeClassName equals to:
- Any
RuntimeClass which has the label env with the value production
If a Pod is going to use a non-allowed RuntimeClass, it will be rejected by the Validation Webhook enforcing it.
Assign Runtime Class as tenant default
This feature allows specifying a custom default value on a Tenant basis- It’s possible to assign each tenant a Runtime which will be used, if no Runtime is set on pod basis:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
runtimeClasses:
default: "tenant-default"
matchLabels:
env: "production"
Let’s create a RuntimeClass which is used as the default:
kubectl apply -f - << EOF
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: tenant-default
labels:
env: "production"
handler: myconfiguration
EOF
If a Pod has no value for spec.runtimeclass, the default value for RuntimeClass (tenant-default) will be used.
NodeSelector
Bill, the cluster admin, can dedicate a pool of worker nodes to the solar Tenant, to isolate the tenant applications from other noisy neighbors.
These nodes are labeled by Bill as pool=renewable
kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
...
worker06.acme.com Ready worker 8d v1.25.2 pool=renewable
worker07.acme.com Ready worker 8d v1.25.2 pool=renewable
worker08.acme.com Ready worker 8d v1.25.2 pool=renewable
PodNodeSelector
This approach requires PodNodeSelector Admission Controller plugin to be active. If the plugin is not active, the pods will be scheduled to any node. If your distribution does not support this feature, you can use Expression Node Selectors.
The label pool=renewable is defined as .spec.nodeSelector in the Tenant manifest:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
nodeSelector:
pool: renewable
kubernetes.io/os: linux
The Capsule controller makes sure that any Namespace created in the Tenant has the annotation: scheduler.alpha.kubernetes.io/node-selector: pool=renewable. This annotation tells the scheduler of Kubernetes to assign the node selector pool=renewable to all the Pods deployed in the Tenant. The effect is that all the Pods deployed by Alice are placed only on the designated pool of nodes.
Multiple node selector labels can be defined as in the following snippet:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
nodeSelector:
pool: renewable
kubernetes.io/os: linux
kubernetes.io/arch: amd64
hardware: gpu
Any attempt of Alice to change the selector on the Pods will result in an error from the PodNodeSelector Admission Controller plugin.
kubectl auth can-i edit ns -n solar-production
no
Dynamic resource allocation (DRA)
Dynamic Resource Allocation (DRA) is a Kubernetes capability that allows Pods to request and use shared resources, typically external devices such as hardware accelerators.
See Kubernetes documentation for more information.
Bill can assign a set of dedicated DeviceClasses to tell the solar Tenant what devices they can request.
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: gpu.example.com
labels:
env: "production"
spec:
selectors:
- cel:
expression: device.driver == 'gpu.example.com' && device.attributes['gpu.example.com'].type
== 'gpu'
extendedResourceName: example.com/gpu
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
deviceClasses:
matchLabels:
env: "production"
With the said Tenant specification, Alice can create a ResourceClaim or ResourceClaimTemplate resource if spec.devices.requests[].deviceClassName ( ResourceClaim) or spec.spec.devices.requests[].deviceClassName ( ResourceClaimTemplate) equals to:
- Any DeviceClass, which has the label env with the value production
If any of the devices in the ResourceClaim or ResourceClaimTemplate spec is going to use a non-allowed DeviceClass, the entire request will be rejected by the Validation Webhook enforcing it.
Alice now can create a ResourceClaim using only an allowed DeviceClass:
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
name: example-resource-claim
namespace: solar-production
spec:
devices:
requests:
- name: gpu-request
exactly:
deviceClassName: 'gpu.example.com'
Connectivity
Services
ExternalIPs
Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed, which is recommended in multi-tenant environments (can be misused for traffic hijacking):
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
serviceOptions:
externalIPs:
allowed: []
Deny labels and annotations
By default, capsule allows Tenant owners to add and modify any label or annotation on their Services.
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
serviceOptions:
forbiddenAnnotations:
denied:
- loadbalancer.class.acme.net
deniedRegex: .*.acme.net
forbiddenLabels:
denied:
- loadbalancer.class.acme.net
deniedRegex: .*.acme.net
Deny Service Types
Bill, the cluster admin, can prevent the creation of Services with specific Service types.
NodePort
When dealing with a shared multi-tenant scenario, multiple NodePort services can start becoming cumbersome to manage. The reason behind this could be related to the overlapping needs by the Tenant owners, since a NodePort is going to be open on all nodes and, when using hostNetwork=true, accessible to any Pod although any specific NetworkPolicy.
Bill, the cluster admin, can block the creation of Services with NodePort service type for a given Tenant
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
serviceOptions:
allowedServices:
nodePort: false
With the above configuration, any attempt of Alice to create a Service of type NodePort is denied by the Validation Webhook enforcing it. Default value is true.
ExternalName
Service with the type of ExternalName has been found subject to many security issues. To prevent TenantOwners to create services with the type of ExternalName, the cluster admin can prevent a tenant to create them:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
serviceOptions:
allowedServices:
externalName: false
With the above configuration, any attempt of Alice to create a Service of type externalName is denied by the Validation Webhook enforcing it. Default value is true.
LoadBalancer
Same as previously, the Service of type of LoadBalancer could be blocked for various reasons. To prevent TenantOwners to create these kinds of Services, the cluster admin can Tenant a tenant to create them:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
serviceOptions:
allowedServices:
loadBalancer: false
With the above configuration, any attempt of Alice to create a Service of type LoadBalancer is denied by the Validation Webhook enforcing it. Default value is true.
GatewayClasses
Note: This feature is offered only by API type GatewayClass in group gateway.networking.k8s.io version v1.
GatewayClass is cluster-scoped resource defined by the infrastructure provider. This resource represents a class of Gateways that can be instantiated. Read More
Bill can assign a set of dedicated GatewayClasses to the solar Tenant to force the applications in the solar Tenant to be published only by the assigned Gateway Controller:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
gatewayOptions:
allowedClasses:
matchLabels:
env: "production"
With the said Tenant specification, Alice can create a Gateway resource if spec.gatewayClassName equals to:
- Any
GatewayClass which has the label env with the value production
If an Gateway is going to use a non-allowed GatewayClass, it will be rejected by the Validation Webhook enforcing it.
Alice can create an Gateway using only an allowed GatewayClass:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
namespace: solar-production
spec:
gatewayClassName: customer-class
listeners:
- name: http
protocol: HTTP
port: 80
Any attempt of Alice to use a non-valid GatewayClass, or missing it, is denied by the Validation Webhook enforcing it.
Assign GatewayClass as tenant default
Note: The Default GatewayClass must have a label which is allowed within the tenant. This behavior is only implemented this way for the GatewayClass default.
This feature allows specifying a custom default value on a Tenant basis. Currently there is no global default feature for a GatewayClass. Each Gateway must have a spec.gatewayClassName set.
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
gatewayOptions:
allowedClasses:
default: "tenant-default"
matchLabels:
env: "production"
Here’s how the Tenant default GatewayClass could look like:
kubectl apply -f - << EOF
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: tenant-default
labels:
env: "production"
spec:
controllerName: example.com/gateway-controller
EOF
If a Gateway has no value for spec.gatewayClassName, the tenant-default GatewayClass is automatically applied to the Gateway resource.
Ingresses
Assign Ingress Hostnames
Bill can control ingress hostnames in the solar Tenant to force the applications to be published only using the given hostname or set of hostnames:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
ingressOptions:
allowedHostnames:
allowed:
- solar.acmecorp.com
allowedRegex: ^.*acmecorp.com$
The Capsule controller assures that all Ingresses created in the Tenant can use only one of the valid hostnames. Alice can create an Ingress using any allowed hostname:
kubectl apply -f - << EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
namespace: solar-production
spec:
ingressClassName: solar
rules:
- host: web.solar.acmecorp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
EOF
Any attempt of Alice to use a non-valid hostname is denied by the Validation Webhook enforcing it.
Control Hostname collision in Ingresses
In a multi-tenant environment, as more and more ingresses are defined, there is a chance of collision on the hostname leading to unpredictable behavior of the Ingress Controller. Bill, the cluster admin, can enforce hostname collision detection at different scope levels:
- Cluster
- Tenant
- Namespace
- Disabled (default)
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
- name: joe
kind: User
ingressOptions:
hostnameCollisionScope: Tenant
When a TenantOwner creates an Ingress resource, Capsule will check the collision of hostname in the current ingress with all the hostnames already used, depending on the defined scope.
For example, Alice, one of the TenantOwners, creates an Ingress:
kubectl apply -f - << EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
namespace: solar-production
spec:
rules:
- host: web.solar.acmecorp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
EOF
Another user, Joe creates an Ingress having the same hostname:
kubectl apply -f - << EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
namespace: solar-development
spec:
rules:
- host: web.solar.acmecorp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
EOF
When a collision is detected at scope defined by spec.ingressOptions.hostnameCollisionScope, the creation of the Ingress resource will be rejected by the Validation Webhook enforcing it. When spec.ingressOptions.hostnameCollisionScope=Disabled (default), no collision detection is made at all.
Deny Wildcard Hostname in Ingresses
Bill, the cluster admin, can deny the use of wildcard hostname in Ingresses. Let’s assume that Acme Corp. uses the domain acme.com.
As a TenantOwner of solar, Alice creates an Ingress with the host like - host: "*.acme.com". That can lead problems for the water tenant because Alice can deliberately create ingress with host: water.acme.com.
To avoid this kind of problems, Bill can deny the use of wildcard hostnames in the following way:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
ingressOptions:
allowWildcardHostnames: false
Doing this, Alice will not be able to use *.water.acme.com, being the tenant owner of solar and green only.
IngressClasses
An Ingress Controller is used in Kubernetes to publish services and applications outside of the cluster. An Ingress Controller can be provisioned to accept only Ingresses with a given IngressClass.
Bill can assign a set of dedicated IngressClass to the solar Tenant to force the applications in the solar tenant to be published only by the assigned Ingress Controller:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
ingressOptions:
allowedClasses:
matchLabels:
env: "production"
With the said Tenant specification, Alice can create a Ingress resource if spec.ingressClassName or metadata.annotations."kubernetes.io/ingress.class" equals to:
- Any
IngressClass which has the label env with the value production
If an Ingress is going to use a non-allowed IngressClass, it will be rejected by the Validation Webhook enforcing it.
Alice can create an Ingress using only an allowed IngressClass:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
namespace: solar-production
spec:
ingressClassName: legacy
rules:
- host: solar.acmecorp.com
http:
paths:
- backend:
service:
name: nginx
port:
number: 80
path: /
pathType: ImplementationSpecific
Any attempt of Alice to use a non-valid Ingress Class, or missing it, is denied by the Validation Webhook enforcing it.
Assign Ingress Class as tenant default
Note: This feature is offered only by API type IngressClass in group networking.k8s.io version v1. However, resource Ingress is supported in networking.k8s.io/v1 and networking.k8s.io/v1beta1
This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (with the annotation metadata.annotations.ingressclass.kubernetes.io/is-default-class=true) that acts only at the cluster level. More information: Default IngressClass
It’s possible to assign each Tenant an IngressClass which will be used, if a class is not set on Ingress basis:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
ingressOptions:
allowedClasses:
default: "tenant-default"
matchLabels:
env: "production"
Here’s how the Tenant default IngressClass could look like:
kubectl apply -f - << EOF
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
labels:
env: "production"
app.kubernetes.io/component: controller
name: tenant-default
annotations:
ingressclass.kubernetes.io/is-default-class: "false"
spec:
controller: k8s.io/customer-nginx
EOF
If an Ingress has no value for spec.ingressClassName or metadata.annotations."kubernetes.io/ingress.class", the tenant-default IngressClass is automatically applied to the Ingress resource.
NetworkPolicies
Deprecated
This feature will be deprecated in a future release of Capsule. Instead use TenantReplications. This is also true if you would like other NetworkPolicy implementation like Cilium.
Kubernetes network policies control network traffic between Namespaces and between pods in the same Namespace. Bill, the cluster admin, can enforce network traffic isolation between different Tenants while leaving to Alice, the TenantOwner, the freedom to set isolation between Namespaces in the same Tenant or even between pods in the same Namespace.
To meet this requirement, Bill needs to define network policies that deny pods belonging to Alice’s Namespaces to access pods in Namespaces belonging to other Tenants, e.g. Bob’s Tenant water, or in system Namespaces, e.g. kube-system.
Keep in mind, that because of how the NetworkPolicies API works, the users can still add a policy which contradicts what the Tenant has set, resulting in users being able to circumvent the initial limitation set by the Tenant admin. Two options can be put in place to mitigate this potential privilege escalation: 1. providing a restricted role rather than the default admin one 2. using Calico’s GlobalNetworkPolicy, or Cilium’s CiliumClusterwideNetworkPolicy which are defined at the cluster-level, thus creating an order of packet filtering.
Also, Bill can make sure pods belonging to a Tenant Namespace cannot access other network infrastructures like cluster nodes, load balancers, and virtual machines running other services.
Bill can set network policies in the Tenant manifest, according to the requirements:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
networkPolicies:
items:
- policyTypes:
- Ingress
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 192.168.0.0/16
ingress:
- from:
- namespaceSelector:
matchLabels:
capsule.clastix.io/tenant: water
- podSelector: {}
- ipBlock:
cidr: 192.168.0.0/16
podSelector: {}
The Capsule controller, watching for Namespace creation, creates the Network Policies for each Namespace in the Tenant.
Alice has access to network policies:
kubectl -n solar-production get networkpolicies
NAME POD-SELECTOR AGE
capsule-solar-0 <none> 42h
Alice can create, patch, and delete additional network policies within her Namespaces:
kubectl -n solar-production auth can-i get networkpolicies
yes
kubectl -n solar-production auth can-i delete networkpolicies
yes
kubectl -n solar-production auth can-i patch networkpolicies
yes
For example, she can create:
kubectl apply -f - << EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
name: production-network-policy
namespace: solar-production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
EOF
Check all the network policies
kubectl -n solar-production get networkpolicies
NAME POD-SELECTOR AGE
capsule-solar-0 <none> 42h
production-network-policy <none> 3m
And delete the Namespace network policies:
kubectl -n solar-production delete networkpolicy production-network-policy
Any attempt of Alice to delete the Tenant network policy defined in the tenant manifest is denied by the Validation Webhook enforcing it. Any deletion by a cluster-administrator will cause the network policy to be recreated by the Capsule controller.
NetworkPolicy Distribution with TenantReplications
In the future Cluster-Administrators must distribute NetworkPolicies via TenantReplications. This is a more flexible and powerful way to distribute NetworkPolicies, as it allows to distribute any kind of resource. Here’s an example of how to distribute a CiliumNetworkPolicy to all the Namespaces of a Tenant:
apiVersion: capsule.clastix.io/v1beta2
kind: TenantResource
metadata:
name: solar-limitranges
namespace: solar-system
spec:
resyncPeriod: 60s
resources:
- namespaceSelector:
matchLabels:
capsule.clastix.io/tenant: solar
rawItems:
- apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "l3-rule"
spec:
endpointSelector:
matchLabels:
role: backend
ingress:
- fromEndpoints:
- matchLabels:
role: frontend
Storage
PersistentVolumes
Any Tenant owner is able to create a PersistentVolumeClaim that, backed by a given StorageClass, will provide volumes for their applications.
In most cases, once a PersistentVolumeClaim is deleted, the bounded PersistentVolume will be recycled due.
However, in some scenarios, the StorageClass or the provisioned PersistentVolume itself could change the retention policy of the volume, keeping it available for recycling and being consumable for another Pod.
In such a scenario, Capsule enforces the Volume mount only to the Namespaces belonging to the Tenant on which it’s been consumed, by adding a label to the Volume as follows.
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: rancher.io/local-path
creationTimestamp: "2022-12-22T09:54:46Z"
finalizers:
- kubernetes.io/pv-protection
labels:
capsule.clastix.io/tenant: solar
name: pvc-1b3aa814-3b0c-4912-9bd9-112820da38fe
resourceVersion: "2743059"
uid: 9836ae3e-4adb-41d2-a416-0c45c2da41ff
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 10Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: melange
namespace: caladan
resourceVersion: "2743014"
uid: 1b3aa814-3b0c-4912-9bd9-112820da38fe
Once the PeristentVolume become available again, it can be referenced by any PersistentVolumeClaim in the solar Tenant Namespace resources.
If another Tenant, like green, tries to use it, it will get an error:
$ kubectl describe pv pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d
Name: pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d
Labels: capsule.clastix.io/tenant=solar
Annotations: pv.kubernetes.io/provisioned-by: rancher.io/local-path
Finalizers: [kubernetes.io/pv-protection]
StorageClass: standard
Status: Available
...
$ cat /tmp/pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: melange
namespace: green-energy
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
volumeName: pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d
$ kubectl apply -f /tmp/pvc.yaml
Error from server: error when creating "/tmp/pvc.yaml": admission webhook "pvc.capsule.clastix.io" denied the request: PeristentVolume pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d cannot be used by the following Tenant, preventing a cross-tenant mount
StorageClasses
Persistent storage infrastructure is provided to Tenants. Different types of storage requirements, with different levels of QoS, eg. SSD versus HDD, are available for different tenants according to the Tenant’s profile. To meet these different requirements, Bill, the cluster admin can provision different StorageClasses and assign them to the tenant:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
storageClasses:
matchLabels:
env: "production"
With the said Tenant specification, Alice can create a Persistent Volume Claims if spec.storageClassName equals to:
- Any
StorageClass which has the label env with the value production
Capsule assures that all PersistentVolumeClaims created by Alice will use only one of the valid storage classes. Assume the StorageClass ceph-rbd has the label env: production:
kubectl apply -f - << EOF
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc
namespace: solar-production
spec:
storageClassName: ceph-rbd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 12Gi
EOF
If a PersistentVolumeClaim is going to use a non-allowed Storage Class, it will be rejected by the Validation Webhook enforcing it.
Assign Storage Class as tenant default
Note: This feature supports type StorageClass only on API version storage.k8s.io/v1
This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (.metadata.annotations.storageclass.kubernetes.io/is-default-class=true) that acts only at the cluster level. See the Default Storage Class section on Kubernetes documentation.
It’s possible to assign each tenant a StorageClass which will be used, if no value is set on PersistentVolumeClaim basis:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
storageClasses:
default: "tenant-default"
matchLabels:
env: "production"
Here’s how the new StorageClass could look like:
kubectl apply -f - << EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: tenant-default
labels:
env: production
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
EOF
If a PersistentVolumeClaim has no value for spec.storageClassName the tenant-default value will be used on new PersistentVolumeClaim resources.
Images
PullPolicy
Bill is a cluster admin providing a Container as a Service platform using shared nodes.
Alice, a TenantOwner, can start container images using private images: according to the Kubernetes architecture, the kubelet will download the layers on its cache.
Bob, an attacker, could try to schedule a Pod on the same node where Alice is running her Pods backed by private images: they could start new Pods using ImagePullPolicy=IfNotPresent and be able to start them, even without required authentication since the image is cached on the node.
To avoid this kind of attack, Bill, the cluster admin, can force Alice, the TenantOwner, to start her Pods using only the allowed values for ImagePullPolicy, enforcing the Kubelet to check the authorization first.
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
imagePullPolicies:
- Always
Allowed values are: Always, IfNotPresent, Never. As defined by the Kubernetes API
Any attempt of Alice to use a disallowed imagePullPolicies value is denied by the Validation Webhook enforcing it.
Images Registries
Bill, the cluster admin, can set a strict policy on the applications running into Alice’s Tenant: he’d like to allow running just images hosted on a list of specific container registries.
The spec.containerRegistries addresses this task and can provide a combination with hard enforcement using a list of allowed values.
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
containerRegistries:
allowed:
- docker.io
- quay.io
allowedRegex: 'internal.registry.\\w.tld'
In case of Pod running non-FQCI (non fully qualified container image) containers, the container registry enforcement will disallow the execution. If you would like to run a bbusybox:latest container that is commonly hosted on Docker Hub, the TenantOwner has to specify its name explicitly, like docker.io/library/busybox:latest.
A Pod running internal.registry.foo.tld/capsule:latest as registry will be allowed, as well internal.registry.bar.tld since these are matching the regular expression.
A catch-all regex entry as .* allows every kind of registry, which would be the same result of unsetting .spec.containerRegistries at all.
Any attempt of Alice to use a not allowed .spec.containerRegistries value is denied by the Validation Webhook enforcing it.
6 - Metadata
Inherit additional metadata on Tenant resources.
Managed
By default all namespaced resources within a Namespace which are part of a Tenant labeled at admission with the capsule.clastix.io/tenant: <tenant-name> label.
Namespaces
Information
Starting from v0.10.8, it is possible to use templated values for labels and annotations.
Currently, {{ tenant.name }} and {{ namespace }} placeholders are available.apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
namespaceOptions:
additionalMetadataList:
- annotations:
templated-annotation: {{ tenant.name }}
labels:
templated-label: {{ namespace }}
The cluster admin can “taint” the namespaces created by tenant owners with additional metadata as labels and annotations. There is no specific semantic assigned to these labels and annotations: they will be assigned to the namespaces in the tenant as they are created. However you have the option to be more specific by selecting to which namespaces you want to assign what kind of metadata:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
namespaceOptions:
additionalMetadataList:
# An item without any further selectors is applied to all namspaces
- annotations:
storagelocationtype: s3
labels:
projectcapsule.dev/backup: "true"
# Select a subset of namespaces to apply metadata on
- namespaceSelector:
matchExpressions:
- key: projectcapsule.dev/low_security_profile
operator: NotIn
values: ["true"]
labels:
pod-security.kubernetes.io/enforce: baseline
- namespaceSelector:
matchExpressions:
- key: projectcapsule.dev/low_security_profile
operator: In
values: ["true"]
labels:
pod-security.kubernetes.io/enforce: privileged
Deprecated
This feature is deprecated and will be removed in a future release of Capsule. Migrate to using
AdditionalMetadataListThe cluster admin can “taint” the namespaces created by tenant owners with additional metadata as labels and annotations. There is no specific semantic assigned to these labels and annotations: they will be assigned to the namespaces in the tenant as they are created. This can help the cluster admin to implement specific use cases as, for example, leave only a given tenant to be backed up by a backup service.
Assigns additional labels and annotations to all namespaces created in the solar tenant:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
namespaceOptions:
additionalMetadata:
annotations:
storagelocationtype: s3
labels:
projectcapsule.dev/backup: "true"
When the tenant owner creates a namespace, it inherits the given label and/or annotation:
apiVersion: v1
kind: Namespace
metadata:
annotations:
storagelocationtype: s3
labels:
capsule.clastix.io/tenant: solar
kubernetes.io/metadata.name: solar-production
name: solar-production
projectcapsule.dev/backup: "true"
name: solar-production
ownerReferences:
- apiVersion: capsule.clastix.io/v1beta2
blockOwnerDeletion: true
controller: true
kind: Tenant
name: solar
spec:
finalizers:
- kubernetes
status:
phase: Active
Deny labels and annotations on Namespaces
By default, capsule allows tenant owners to add and modify any label or annotation on their namespaces.
But there are some scenarios, when tenant owners should not have an ability to add or modify specific labels or annotations (for example, this can be labels used in Kubernetes network policies which are added by cluster administrator).
Bill, the cluster admin, can deny Alice to add specific labels and annotations on namespaces:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
namespaceOptions:
forbiddenAnnotations:
denied:
- foo.acme.net
- bar.acme.net
deniedRegex: .*.acme.net
forbiddenLabels:
denied:
- foo.acme.net
- bar.acme.net
deniedRegex: .*.acme.net
owners:
- name: alice
kind: User
Nodes
Warning
Due to
CVE-2021-25735 this feature is only supported for Kubernetes version older than: v1.18.18, v1.19.10, v1.20.6, v1.21.0
When using capsule together with capsule-proxy, Bill can allow Tenant Owners to modify Nodes.
By default, it will allow tenant owners to add and modify any label or annotation on their nodes.
But there are some scenarios, when tenant owners should not have an ability to add or modify specific labels or annotations (there are some types of labels or annotations, which must be protected from modifications - for example, which are set by cloud-providers or autoscalers).
Bill, the cluster admin, can deny Tenant Owners to add or modify specific labels and annotations on Nodes:
apiVersion: capsule.clastix.io/v1beta2
kind: CapsuleConfiguration
metadata:
name: default
spec:
nodeMetadata:
forbiddenAnnotations:
denied:
- foo.acme.net
- bar.acme.net
deniedRegex: .*.acme.net
forbiddenLabels:
denied:
- foo.acme.net
- bar.acme.net
deniedRegex: .*.acme.net
userGroups:
- projectcapsule.dev
- system:serviceaccounts:default
Services
The cluster admin can “taint” the services created by the tenant owners with additional metadata as labels and annotations.
Assigns additional labels and annotations to all services created in the solar tenant:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
serviceOptions:
additionalMetadata:
annotations:
storagelocationtype: s3
labels:
projectcapsule.dev/backup: "true"
When the tenant owner creates a service in a tenant namespace, it inherits the given label and/or annotation:
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: solar-production
labels:
projectcapsule.dev/backup: "true"
annotations:
storagelocationtype: s3
spec:
ports:
- protocol: TCP
port: 80
targetPort: 8080
selector:
run: nginx
type: ClusterIP
Pods
The cluster admin can “taint” the pods created by the tenant owners with additional metadata as labels and annotations.
Assigns additional labels and annotations to all services created in the solar tenant:
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: solar
spec:
owners:
- name: alice
kind: User
podOptions:
additionalMetadata:
annotations:
storagelocationtype: s3
labels:
projectcapsule.dev/backup: "true"
When the tenant owner creates a service in a tenant namespace, it inherits the given label and/or annotation:
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: solar-production
labels:
projectcapsule.dev/backup: "true"
annotations:
storagelocationtype: s3
...