Blog home

Standardizing enforcement of security policies: Diving deep into Kubernetes Validating Admission Policy

Discover why Validating Admission Policy should standardize the policy enforcement in Kubernetes ecosystem. This article discusses benefits, motivation, and how to get started.

Author Avatar

Published by

Matheus Moraes

7 min read

June 23, 2023

Post Image

Introduction

Kubernetes 1.26 introduced the first alpha release of validating admission policies.

Validating admission policies offer a declarative, in-process alternative to validating admission webhooks. Validation rules for a policy are declared in Common Expression Language (CEL).

Motivation

The current approach to enforcing custom policies within Kubernetes involves use of admission webhooks. It is mainly done via external admission controllers in the ecosystem such as Kyverno and OPA/Gatekeeper.

Admission webhooks are HTTP callbacks that handle admission requests. There are 2 types, either validating or mutating. Mutating webhooks are able to modify objects before they are stored, whereas validating webhooks can reject requests to enforce custom policies.

The diagram below shows phases of the admission control process.

Kubernetes admission controller phases

While admission webhooks do offer great flexibility, they come with a few drawbacks when compared to in-process policy enforcement:

  • Additional infrastructure: required to host admission webhooks.
  • Latency: requires another network hop.
  • Less reliable: due to extra infrastructure dependencies.
  • "Failing closed or failing open" dilemma: reduce the cluster availability or limit the efficacy of policy enforcement?
  • Operationally burdensome: observability, security, and proper release/rollout/rollback plans.

Getting started with Validating Admission Policies

Let's see how validating admission policies work.

Since this feature is in alpha stage, the feature gate ValidatingAdmissionPolicy should be enabled.

We can create a Kubernetes cluster with Kind providing a configuration file that enables the feature gate.

kind create cluster --config kind-config.yaml

The content of kind-config.yaml file:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
"ValidatingAdmissionPolicy": true
runtimeConfig:
"admissionregistration.k8s.io/v1alpha1": "true"
nodes:
- role: control-plane
image: kindest/node:v1.27.2

The following command checks if the API is enabled:

kubectl api-resources | grep validating
validatingadmissionpolicies admissionregistration.k8s.io/v1alpha1 false ValidatingAdmissionPolicy
validatingadmissionpolicybindings admissionregistration.k8s.io/v1alpha1 false ValidatingAdmissionPolicyBinding
validatingwebhookconfigurations admissionregistration.k8s.io/v1 false ValidatingWebhookConfiguration

Once the ValidatingAdmissionPolicy is enabled, we are able to create our policies with CEL expressions.

Let's create a policy that enforces a tag different from latest in Pod images.

A policy is made up of at least two resources:

  • The ValidatingAdmissionPolicy describes the logic of a policy.
  • A ValidatingAdmissionPolicyBinding links the above resources together and provides scoping.

Here is our policy. The description of the most important fields are listed as comments, take a look.

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
name: image-tag-latest
spec:
failurePolicy: Fail # if an expression evaluates to false, the validation check is enforced according to this field
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
# the field below contains a CEL expression to validate the request
- expression: |
object.spec.containers.all(container,
container.image.contains(":") &&
[container.image.substring(container.image.lastIndexOf(":")+1)].all(image,
!image.contains("/") && !(image in ["latest", ""])
)
)
message: "Image tag 'latest' is not allowed. Use a tag from a specific version."
---
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: image-tag-latest
spec:
policyName: image-tag-latest # references a `ValidatingAdmissionPolicy` name
validationActions: [Deny] # `Deny` specifies that a validation failure results in a denied request
matchResources: {} # an empty `matchResources` means that all resources matched by the policy are validated by this binding

Now we can apply the policy:

kubectl apply -f vap.yaml

If you try to create a Pod with an untagged image, an error will return with the message we defined:

kubectl apply -f pod.yaml
The pods "nginx" is invalid: : ValidatingAdmissionPolicy 'image-tag-latest' with binding 'image-tag-latest' denied request: Image tag 'latest' is not allowed. Use a tag from a specific version.

The content of pod.yaml file is below.

apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
restartPolicy: Always

Different values can be substituted in the image field to test different cases:

ImageExpect
nginx@sha256:af296b188c7b7df99ba960ca614439c99cb7cf252ed7bbc23e90cfda59092305pass
nginx:1.25.0pass
nginx:latestfail
nginxfail

Comparing policies

See how the same policy is defined in external admission controllers.

apiVersion: admissionregistration.k8s.io/v1alpha1kind: ValidatingAdmissionPolicymetadata:  name: image-tag-latestspec:  failurePolicy: Fail # if an expression evaluates to false, the validation check is enforced according to this field  matchConstraints:    resourceRules:      - apiGroups:   [""]        apiVersions: ["v1"]        operations:  ["CREATE", "UPDATE"]        resources:   ["pods"]  validations:    # the field below contains a CEL expression to validate the request    - expression: |        object.spec.containers.all(container,          container.image.contains(":") &&          [container.image.substring(container.image.lastIndexOf(":")+1)].all(image,            !image.contains("/") && !(image in ["latest", ""])          )        )      message: "Image tag 'latest' is not allowed. Use a tag from a specific version."

Conclusion

Looking at the Kubernetes ecosystem as a whole, it is evident that there's a demand for opinionated policy frameworks. The existence of security regimes such as CIS Kubernetes Benchmarks emphasizes the importance of standardized controls.

Validating Admission Policy is a policy enforcement feature that fulfills a community need and should be the best alternative for the vast majority of validations due to reduced infrastructure footprint and the simplicity of CEL.

In-process admission control has fundamental advantages over webhooks: it is far safer to use in a "fail closed" mode because it removes the network as a possible failure domain.

According to the KEP (Kubernetes Enhancement Proposal) of this feature, it's not a goal currently to support mutations and to build an in-tree policy framework. Therefore, projects in the ecosystem should not be completely replaced when this feature is graduated. Instead, they should make use of these APIs' extensibility and configurability.

One of the goals is to provide core functionality as a library and enable other tools to run the same CEL validation checks that the API server does. This should popularize the use of CEL for policies and checks in the Kubernetes ecosystem.

Even though still in alpha stage, we can already observe the impact of Validating Admission Policy: Kyverno is developing a feature which supports CEL expressions in validations, as can be seen in the comparing policies section.

We should expect to see new projects emerging that use CEL in use cases beyond admissions control. One great example is Marvin, a CLI tool that scans Kubernetes clusters by performing CEL expressions to report potential issues. Marvin has 30+ built-in checks and also supports custom checks with CEL, allowing you to use virtually the same expression in both cases: Kubernetes ValidatingAdmissionPolicy (policy enforcement) and Marvin (cluster scanner).

References