Skip to content

Commit 7267d65

Browse files
authored
Merge pull request #40392 from craigbox/cel-blog-nits
Bring forward article "Kubernetes Validating Admission Policies: A Practical Example" / fix nits
2 parents 8163b49 + 8945736 commit 7267d65

File tree

2 files changed

+235
-158
lines changed

2 files changed

+235
-158
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
---
2+
layout: blog
3+
title: "Kubernetes Validating Admission Policies: A Practical Example"
4+
date: 2023-03-30T00:00:00+0000
5+
slug: kubescape-validating-admission-policy-library
6+
---
7+
8+
**Authors**: Craig Box (ARMO), Ben Hirschberg (ARMO)
9+
10+
Admission control is an important part of the Kubernetes control plane, with several internal
11+
features depending on the ability to approve or change an API object as it is submitted to the
12+
server. It is also useful for an administrator to be able to define business logic, or policies,
13+
regarding what objects can be admitted into a cluster. To better support that use case, [Kubernetes
14+
introduced external admission control in
15+
v1.7](https://kubernetes.io/blog/2017/06/kubernetes-1-7-security-hardening-stateful-application-extensibility-updates/).
16+
17+
In addition to countless custom, internal implementations, many open source projects and commercial
18+
solutions implement admission controllers with user-specified policy, including
19+
[Kyverno](https://github.com/kyverno/kyverno) and Open Policy Agent’s
20+
[Gatekeeper](https://github.com/open-policy-agent/gatekeeper).
21+
22+
While admission controllers for policy have seen adoption, there are blockers for their widespread
23+
use. Webhook infrastructure must be maintained as a production service, with all that entails. The
24+
failure case of an admission control webhook must either be closed, reducing the availability of the
25+
cluster; or open, negating the use of the feature for policy enforcement. The network hop and
26+
evaluation time makes admission control a notable component of latency when dealing with, for
27+
example, pods being spun up to respond to a network request in a "serverless" environment.
28+
29+
## Validating admission policies and the Common Expression Language
30+
31+
Version 1.26 of Kubernetes introduced, in alpha, a compromise solution. [Validating admission
32+
policies](/docs/reference/access-authn-authz/validating-admission-policy/) are a declarative,
33+
in-process alternative to admission webhooks. They use the [Common Expression
34+
Language](https://github.com/google/cel-spec) (CEL) to declare validation rules.
35+
36+
CEL was developed by Google for security and policy use cases, based on learnings from the Firebase
37+
real-time database. Its design allows it to be safely embedded into applications and executed in
38+
microseconds, with limited compute and memory impact. [Validation rules for
39+
CRDs](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules)
40+
introduced CEL to the Kubernetes ecosystem in v1.23, and at the time it was noted that the language
41+
would suit a more generic implementation of validation by admission control.
42+
43+
## Giving CEL a roll - a practical example
44+
45+
[Kubescape](https://github.com/kubescape/kubescape) is a CNCF project which has become one of the
46+
most popular ways for users to improve the security posture of a Kubernetes cluster and validate its
47+
compliance. Its [controls](https://github.com/kubescape/regolibrary) — groups of tests against API
48+
objects — are built in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/), the
49+
policy language of Open Policy Agent.
50+
51+
Rego has a reputation for complexity, based largely on the fact that it is a declarative query
52+
language (like SQL). It [was
53+
considered](https://github.com/kubernetes/enhancements/blob/499e28/keps/sig-api-machinery/2876-crd-validation-expression-language/README.md#alternatives)
54+
for use in Kubernetes, but it does not offer the same sandbox constraints as CEL.
55+
56+
A common feature request for the project is to be able to implement policies based on Kubescape’s
57+
findings and output. For example, after scanning pods for [known paths to cloud credential
58+
files](https://hub.armosec.io/docs/c-0020), users would like the ability to enforce policy that
59+
these pods should not be admitted at all. The Kubescape team thought this would be the perfect
60+
opportunity to try and port our existing controls to CEL and apply them as admission policies.
61+
62+
### Show me the policy
63+
64+
It did not take us long to convert many of our controls and build a [library of validating admission
65+
policies](https://github.com/kubescape/cel-admission-library). Let’s look at one as an example.
66+
67+
Kubescape’s [control C-0017](https://hub.armosec.io/docs/c-0017) covers the requirement for
68+
containers to have an immutable (read-only) root filesystem. This is a best practice according to
69+
the [NSA Kubernetes hardening
70+
guidelines](/blog/2021/10/05/nsa-cisa-kubernetes-hardening-guidance/#immutable-container-filesystems),
71+
but is not currently required as a part of any of the [pod security
72+
standards](/docs/concepts/security/pod-security-standards/).
73+
74+
Here's how we implemented it in CEL:
75+
76+
```yaml
77+
apiVersion: admissionregistration.k8s.io/v1alpha1
78+
kind: ValidatingAdmissionPolicy
79+
metadata:
80+
name: "kubescape-c-0017-deny-resources-with-mutable-container-filesystem"
81+
spec:
82+
failurePolicy: Fail
83+
matchConstraints:
84+
resourceRules:
85+
- apiGroups: [""]
86+
apiVersions: ["v1"]
87+
operations: ["CREATE", "UPDATE"]
88+
resources: ["pods"]
89+
- apiGroups: ["apps"]
90+
apiVersions: ["v1"]
91+
operations: ["CREATE", "UPDATE"]
92+
resources: ["deployments","replicasets","daemonsets","statefulsets"]
93+
- apiGroups: ["batch"]
94+
apiVersions: ["v1"]
95+
operations: ["CREATE", "UPDATE"]
96+
resources: ["jobs","cronjobs"]
97+
validations:
98+
- expression: "object.kind != 'Pod' || object.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.readOnlyRootFilesystem) && container.securityContext.readOnlyRootFilesystem == true)"
99+
message: "Pods having containers with mutable filesystem not allowed! (see more at https://hub.armosec.io/docs/c-0017)"
100+
- expression: "['Deployment','ReplicaSet','DaemonSet','StatefulSet','Job'].all(kind, object.kind != kind) || object.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.readOnlyRootFilesystem) && container.securityContext.readOnlyRootFilesystem == true)"
101+
message: "Workloads having containers with mutable filesystem not allowed! (see more at https://hub.armosec.io/docs/c-0017)"
102+
- expression: "object.kind != 'CronJob' || object.spec.jobTemplate.spec.template.spec.containers.all(container, has(container.securityContext) && has(container.securityContext.readOnlyRootFilesystem) && container.securityContext.readOnlyRootFilesystem == true)"
103+
message: "CronJob having containers with mutable filesystem not allowed! (see more at https://hub.armosec.io/docs/c-0017)"
104+
```
105+
106+
Match constraints are provided for three possible API groups: the `core/v1` group for Pods, the
107+
`apps/v1` workload controllers, and the `batch/v1` job controllers.
108+
109+
{{< note >}} `matchConstraints` will convert the API object to the matched version for you. If, for
110+
example, an API request was for `apps/v1beta1` and you match `apps/v1` in matchConstraints, the API
111+
request will be converted from `apps/v1beta1` to `apps/v1` and then validated. This has the useful
112+
property of making validation rules secure against the introduction of new versions of APIs, which
113+
would otherwise allow API requests to sneak past the validation rule by using the newly introduced
114+
version. {{< /note >}}
115+
116+
The `validations` include the CEL rules for the objects. There are three different expressions,
117+
catering for the fact that a Pod `spec` can be at the root of the object (a [naked
118+
pod](https://kubernetes.io/docs/concepts/configuration/overview/#naked-pods-vs-replicasets-deployments-and-jobs)),
119+
under `template` (a workload controller or a Job), or under `jobTemplate` (a CronJob).
120+
121+
In the event that any `spec` does not have `readOnlyRootFilesystem` set to true, the object will not
122+
be admitted.
123+
124+
{{< note >}} In our initial release, we have grouped the three expressions into the same policy
125+
object. This means they can be enabled and disabled atomically, and thus there is no chance that a
126+
user will accidentally leave a compliance gap by enabling policy for one API group and not the
127+
others. Breaking them into separate policies would allow us access to improvements targeted for the
128+
1.27 release, including type checking. We are talking to SIG API Machinery about how to best address
129+
this before the APIs reach `v1`. {{< /note >}}
130+
131+
### Using the CEL library in your cluster
132+
133+
Policies are provided as Kubernetes objects, which are then bound to certain resources by a
134+
[selector](/docs/concepts/overview/working-with-objects/labels/#label-selectors).
135+
136+
[Minikube](https://minikube.sigs.k8s.io/docs/) is a quick and easy way to install and configure a
137+
Kubernetes cluster for testing. To install Kubernetes v1.26 with the `ValidatingAdmissionPolicy`
138+
[feature gate](/docs/reference/command-line-tools-reference/feature-gates/) enabled:
139+
140+
```shell
141+
minikube start --kubernetes-version=1.26.1 --extra-config=apiserver.runtime-config=admissionregistration.k8s.io/v1alpha1 --feature-gates='ValidatingAdmissionPolicy=true'
142+
```
143+
144+
To install the policies in your cluster:
145+
146+
```shell
147+
# Install configuration CRD
148+
kubectl apply -f https://github.com/kubescape/cel-admission-library/releases/latest/download/policy-configuration-definition.yaml
149+
# Install basic configuration
150+
kubectl apply -f https://github.com/kubescape/cel-admission-library/releases/latest/download/basic-control-configuration.yaml
151+
# Install policies
152+
kubectl apply -f https://github.com/kubescape/cel-admission-library/releases/latest/download/kubescape-validating-admission-policies.yaml
153+
```
154+
155+
To apply policies to objects, create a `ValidatingAdmissionPolicyBinding` resource. Let’s apply the
156+
above Kubescape C-0017 control to any namespace with the label `policy=enforced`:
157+
158+
```shell
159+
# Create a binding
160+
kubectl apply -f - <<EOT
161+
apiVersion: admissionregistration.k8s.io/v1alpha1
162+
kind: ValidatingAdmissionPolicyBinding
163+
metadata:
164+
name: c0017-binding
165+
spec:
166+
policyName: kubescape-c-0017-deny-mutable-container-filesystem
167+
matchResources:
168+
namespaceSelector:
169+
matchLabels:
170+
policy: enforced
171+
EOT
172+
173+
# Create a namespace for running the example
174+
kubectl create namespace policy-example
175+
kubectl label namespace policy-example 'policy=enforced'
176+
```
177+
178+
Now, if you attempt to create an object without specifying a `readOnlyRootFilesystem`, it will not
179+
be created.
180+
181+
```shell
182+
# The next line should fail
183+
kubectl -n policy-example run nginx --image=nginx --restart=Never
184+
```
185+
186+
The output shows our error:
187+
188+
```
189+
The pods "nginx" is invalid: : ValidatingAdmissionPolicy 'kubescape-c-0017-deny-mutable-container-filesystem' with binding 'c0017-binding' denied request: Pods having containers with mutable filesystem not allowed! (see more at https://hub.armosec.io/docs/c-0017)
190+
```
191+
192+
### Configuration
193+
194+
Policy objects can include configuration, which is provided in a different object. Many of the
195+
Kubescape controls require a configuration: which labels to require, which capabilities to allow or
196+
deny, which registries to allow containers to be deployed from, etc. Default values for those
197+
controls are defined in [the ControlConfiguration
198+
object](https://github.com/kubescape/cel-admission-library/blob/main/configuration/basic-control-configuration.yaml).
199+
200+
To use this configuration object, or your own object in the same format, add a `paramRef.name` value
201+
to your binding object:
202+
203+
```yaml
204+
apiVersion: admissionregistration.k8s.io/v1alpha1
205+
kind: ValidatingAdmissionPolicyBinding
206+
metadata:
207+
name: c0001-binding
208+
spec:
209+
policyName: kubescape-c-0001-deny-forbidden-container-registries
210+
paramRef:
211+
name: basic-control-configuration
212+
matchResources:
213+
namespaceSelector:
214+
matchLabels:
215+
policy: enforced
216+
```
217+
218+
## Summary
219+
220+
Converting our controls to CEL was simple, in most cases. We cannot port the whole Kubescape
221+
library, as some controls check for things outside a Kubernetes cluster, and some require data that
222+
is not available in the admission request object. Overall, we are happy to contribute this library
223+
to the Kubernetes community and will continue to develop it for Kubescape and Kubernetes users
224+
alike. We hope it becomes useful, either as something you use yourself, or as examples for you to
225+
write your own policies.
226+
227+
As for the validating admission policy feature itself, we are very excited to see this native
228+
functionality introduced to Kubernetes. We look forward to watching it move to Beta and then GA,
229+
hopefully by the end of the year. It is important to note this feature is currently in Alpha, which
230+
means this is the perfect opportunity to play around with it in environments like Minikube and give
231+
a test drive. However, it is not yet considered production-ready and stable, and will not be enabled
232+
on most managed Kubernetes environments. We will not recommend Kubescape users use these policies in
233+
production until the underlying functionality becomes stable. Keep an eye on [the
234+
KEP](https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/3488-cel-admission-control/README.md),
235+
and of course this blog, for an eventual release announcement.

0 commit comments

Comments
 (0)