3.4. Custom Policies

Until now we learned best practices on how to secure our workload in Kubernetes, but as a cluster admin. How can we enforce certain regulations for resources in our cluster? Sometimes we need to be more specific than the 3 PSP this is where policy engines like Open Policy Agent (OPA) or Kyverno come into play.

Kyverno

Kyverno is an open-source policy engine designed specifically for Kubernetes. It allows you to manage and enforce security and compliance policies for your Kubernetes resources using custom resource definitions (CRDs). We use it to write our own Policies as Code.

Remember when we did patch a namespace in a previous lab. Patching and allowing the users to change labels can have real security consequences because it allows users to change the behavior of algorithms like network policies which use labels to filter out resources. This is why a Policy was set in place that you could only change certain labels of a namespace.

Task 3.4.1: Install Kyverno on our kind cluster

We will install a standalone version of Kyverno on our kind cluster using helm to test out its functionality:

helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace

We see that a new Webhook was created which calls Kyverno on CREATE and UPDATE operations:

kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io kyverno-policy-validating-webhook-cfg -oyaml

Kyverno itself runs in the kyverno namespace:

kubectl get deployments.apps -n kyverno

Task 3.4.2: Apply a policy

Let us create a namespace called test-kyverno and apply our own policy for this namespace, we want to start soft and warn users if they did not add the requirement that a container must drop ALL capabilities.

Create the namespace first:

kubectl create ns test-kyverno

Now let us create a pod which passes the PSS Level baseline:

cat <<EOF >> alpine-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: alpine-pod
  namespace: test-kyverno
  labels:
    app: alpine
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
  containers:
    - name: alpine
      image: alpine
      command: ["sleep", "infinity"]
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
      resources:
        limits:
          cpu: "100m"
          memory: "128Mi"
EOF
kubectl apply -f alpine-pod.yaml

We see this works.

The baseline does not monitor if file systems are mounted read-only but the restricted profile is not flexibel enough. Let us apply a Kyverno policy for that.

Kyvernos has a so called Custom Resourced called policy for that, create a file named read-only.yaml with this content:

cat <<\EOF >> read-only.yaml
apiVersion: kyverno.io/v1
kind: Policy
metadata:
  name: enforce-readonly-filesystem
  namespace: test-kyverno
  annotations:
    policies.kyverno.io/title: Enforce read-only fs
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/severity: medium
    policies.kyverno.io/minversion: 1.6.0
    policies.kyverno.io/subject: Pod
spec:
  validationFailureAction: Audit
  background: true
  rules:
    - name: check-readonly-root-filesystem
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: "The root filesystem must be mounted as read-only for all containers."
        pattern:
          spec:
            containers:
              - securityContext:
                  readOnlyRootFilesystem: true
EOF

Apply the policy now

kubectl apply -f read-only.yaml

Now let us delete, edit and rerun the pod

kubectl delete -f alpine-pod.yaml

Switch readOnlyRootFilesystem to false.

nano alpine-pod.yaml

And then apply the pod again:

kubectl apply -f alpine-pod.yaml

Because the Action is only set to Audit noncompliant resources are only logged, check here:

kubectl -n test-kyverno describe policies.kyverno.io enforce-readonly-filesystem

Let us now enforce our policy, switch the Audit to Enforce in read-only.yaml and apply it again:

nano read-only.yaml
kubectl apply -f read-only.yaml

Now test the same pod again:

kubectl delete -f alpine-pod.yaml
kubectl apply -f alpine-pod.yaml

We were successful in implementing and enforcing our policy for newly started pods. We still need this pod running so change it back to a working mode:

Let us now enforce our policy, switch the Audit to Enforce in drop-policy.yaml and apply it again:

nano alpine-pod.yaml
kubectl apply -f alpine-pod.yaml

Task 3.4.3: (Advanced) Private Container Registries

To improve security many companies run private container registries nowadays. The goal is to enforce that you can only run images from your certain registries.

Create a Kyverno Policy with “Audit” Level which checks if your images (i.e. quay.io/jitesoft/alpine ) are pulled from quay.io and creates a log audit entry if that is the case. Then test this policy by running a pod with an image from quay.io.

Last modified November 6, 2024: fix typos (2aa51fa)