|
| 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