Running remote IDE's using DevPod (2 of 5)

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.

Continue in part 3 of Running remote IDE's using DevPod