Part 2: Guide to Installing Kyverno and Enforcing Restrictions.
This guide is for demonstration purposes only, and not actual step-by-step guide on how to do this. Knowledge of Kubernetes is assumed, so some steps might not be documented in this guide.
Installing Kyverno
Before we do anything, we want to install the policy engine that will enforce the requirements and restrictions that we set for our environment. We use Helm for this, and we'll add in a special bit of configuration to install Kyverno using a values file.
backgroundController:
rbac:
clusterRole:
extraResources:
- apiGroups: ['*']
resources: ['*']
verbs: ['*']
- nonResourceURLs: ['*']
verbs: ['*']
This configuration is needed, because Kyverno is only allowed to assign the permissions it itself holds. If it were possible to assign permissions to users that it does not hold, it would be a possibility for privilege escalation.
Software engineer basic permissions
We want software engineers to have full control over their own namespace, including the creation of it. So we'll need to create a ClusterRole
, and a ClusterRoleBinding
to allow them to create their own namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-creator
rules:
- apiGroups: ['']
resources: [namespaces]
verbs: [get,create]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: software-engineers:namespace-creator
subjects:
- kind: Group
name: software-engineers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: namespace-creator
apiGroup: rbac.authorization.k8s.io
This configuration alone poses two problems for software engineers:
- they still do not have any permission within their own namespace
- they can create multiple namespaces, violating our requirements
Let's first deal with the multiple namespaces problem.
Restricting to a single namespace
To restrict users to a single namespace, we need to identify what user owns which namespace. This can be accomplished using a Kyverno ClusterPolicy
that annotates a namespace on creation, with the username of the creator. The relevant pieces of code to accomplish this are as follows (based on this ClusterPolicy
).
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: patch-creation-details
spec:
background: false
rules:
- name: add-userinfo
match:
any:
- resources:
kinds: [Namespace]
preconditions:
any:
- key: "{{ request.operation || 'BACKGROUND'}}"
operator: Equals
value: CREATE
mutate:
patchStrategicMerge:
metadata:
annotations:
kyverno.io/created-by: "{{request.userInfo.username}}"
Now we have a record of ownership for namespaces, we can check if a user is allowed to create a namespace.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: guard-restrict-namespace-count-per-user
spec:
validationFailureAction: Enforce
background: false
rules:
- name: restrict-namespace-count
match:
any:
- resources:
kinds:
- Namespace
context:
- name: nscounts
apiCall:
urlPath: "/api/v1/namespaces"
jmesPath: items[?metadata.annotations."kyverno.io/created-by"=='{{ request.userInfo.username }}'] | length(@)
preconditions:
all:
- key: "{{request.operation || 'BACKGROUND'}}"
operator: Equals
value: "CREATE"
- key: "{{request.userInfo.groups}}"
operator: AnyIn
value:
- software-engineers
validate:
message: "A maximum of 1 Namespaces are allowed for user {{ request.userInfo.username }}. Found {{ nscounts }}."
deny:
conditions:
any:
- key: "{{nscounts}}"
operator: GreaterThanOrEquals
value: 1
There's a few things happening in this policy. Again, we match for the namespace kind, and look for the CREATE
operation. Additionally, we have a precondition that checks whether the user that is attempting to create a namespace belongs to the group software-engineers
. We also inject extra context, by doing an API call to retrieve namespaces, then filtering for namespaces that are owned by the requesting user, then returning the amount of namespaces that are owned by the user.
Finally, we have a validate condition that denies requests to create a new namespace if the amount of namespaces retrieved from the context is greather than or equal to 1. If it is denied, we display a message to the user that only a single namespace is allowed, and the amount of namespaces that are owned by the user.