|
| 1 | +--- |
| 2 | +date: 2024-02-26 |
| 3 | +title: "Generating Kubernetes ValidatingAdmissionPolicies from Kyverno Policies" |
| 4 | +linkTitle: "Generating Kubernetes ValidatingAdmissionPolicies from Kyverno Policies" |
| 5 | +author: Mariam Fahmy |
| 6 | +description: "Generating Kubernetes ValidatingAdmissionPolicies from Kyverno Policies" |
| 7 | +--- |
| 8 | +In the [previous blog post](/blog/2023/11/13/using-cel-expressions-in-kyverno-policies/), we discussed writing [Common Expression Language (CEL)](https://github.com/google/cel-spec) expressions in Kyverno policies for resource validation. CEL was first introduced to Kubernetes for the Validation rules for CustomResourceDefinitions, and then it was used by Kubernetes ValidatingAdmissionPolicies in 1.26. |
| 9 | + |
| 10 | +ValidatingAdmissionPolicies offer a declarative, in-process alternative to validating admission webhooks. |
| 11 | + |
| 12 | +ValidatingAdmissionPolicies use the Common Expression Language (CEL) to declare the validation rules of a policy. Validation admission policies are highly configurable, enabling policy authors to define policies that can be parameterized and scoped to resources as needed by cluster administrators. |
| 13 | + |
| 14 | +This post will show you how to generate Kubernetes ValidatingAdmissionPolicies and their bindings from Kyverno policies. |
| 15 | + |
| 16 | +## Prerequisite |
| 17 | +Generating Kubernetes ValidatingAdmissionPolicies require the following: |
| 18 | +1. A cluster with Kubernetes 1.26 or higher. |
| 19 | +2. Enable the `ValidatingAdmissionPolicy` [feature gate](https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/). |
| 20 | +3. Enable the `admissionregistration.k8s.io/v1beta1` API for v1.28 and v1.29. |
| 21 | + OR |
| 22 | + Enable the `admissionregistration.k8s.io/v1alpha1` API for v1.26 and v1.27. |
| 23 | +4. Set the `--generateValidatingAdmissionPolicy` flag in the Kyverno admission controller. |
| 24 | +5. Grant the admission controller service account the required permissions to generate ValidatingAdmissionPolicies and their bindings. |
| 25 | + |
| 26 | +In this post, we will use the beta version of Kubernetes 1.29. |
| 27 | + |
| 28 | +## Installation & Setup |
| 29 | +1. Create a local cluster |
| 30 | + |
| 31 | +```bash |
| 32 | +kind create cluster --image "kindest/node:v1.28.0" --config - <<EOF |
| 33 | +kind: Cluster |
| 34 | +apiVersion: kind.x-k8s.io/v1alpha4 |
| 35 | +featureGates: |
| 36 | + ValidatingAdmissionPolicy: true |
| 37 | +runtimeConfig: |
| 38 | + admissionregistration.k8s.io/v1beta1: true |
| 39 | + admissionregistration.k8s.io/v1alpha1: true |
| 40 | +nodes: |
| 41 | + - role: control-plane |
| 42 | + - role: worker |
| 43 | +EOF |
| 44 | +``` |
| 45 | + |
| 46 | +2. Add the Kyverno Helm repository. |
| 47 | + |
| 48 | +```bash |
| 49 | +helm repo add kyverno https://kyverno.github.io/kyverno/ |
| 50 | +helm repo update |
| 51 | +``` |
| 52 | + |
| 53 | +3. Create a new file that overrides the values in the chart. |
| 54 | + |
| 55 | +```bash |
| 56 | +cat << EOF > new-values.yaml |
| 57 | +features: |
| 58 | + generateValidatingAdmissionPolicy: |
| 59 | + enabled: true |
| 60 | +
|
| 61 | +admissionController: |
| 62 | + rbac: |
| 63 | + clusterRole: |
| 64 | + extraResources: |
| 65 | + - apiGroups: |
| 66 | + - admissionregistration.k8s.io |
| 67 | + resources: |
| 68 | + - validatingadmissionpolicies |
| 69 | + - validatingadmissionpolicybindings |
| 70 | + verbs: |
| 71 | + - create |
| 72 | + - update |
| 73 | + - delete |
| 74 | + - list |
| 75 | +EOF |
| 76 | +``` |
| 77 | + |
| 78 | +4. Deploy Kyverno |
| 79 | + |
| 80 | +```bash |
| 81 | +helm install kyverno kyverno/kyverno -n kyverno --create-namespace --version v3.1.4 --values new-values.yaml |
| 82 | +``` |
| 83 | + |
| 84 | +We are now ready to generate Kubernetes ValidatingAdmissionPolicies from Kyverno policies. |
| 85 | + |
| 86 | +## Generating Kubernetes ValidatingAdmissionPolicies |
| 87 | +In this section, we will create a Kyverno policy that ensures no hostPath volumes are in use for Deployments, and then we will have a look at the generated ValidatingAdmissionPolicy and its binding. Finally, we will create a Deployment that violates the policy. |
| 88 | + |
| 89 | +Let’s start with creating the Kyverno policy. |
| 90 | + |
| 91 | +```bash |
| 92 | +kubectl apply -f - <<EOF |
| 93 | +apiVersion: kyverno.io/v1 |
| 94 | +kind: ClusterPolicy |
| 95 | +metadata: |
| 96 | + name: disallow-host-path |
| 97 | +spec: |
| 98 | + validationFailureAction: Enforce |
| 99 | + background: false |
| 100 | + rules: |
| 101 | + - name: host-path |
| 102 | + match: |
| 103 | + any: |
| 104 | + - resources: |
| 105 | + kinds: |
| 106 | + - Deployment |
| 107 | + validate: |
| 108 | + cel: |
| 109 | + expressions: |
| 110 | + - expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))" |
| 111 | + message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset." |
| 112 | +EOF |
| 113 | +``` |
| 114 | + |
| 115 | +You can check whether a ValidatingAdmissionPolicy is generated or not from the Kyverno policy status. |
| 116 | + |
| 117 | +```bash |
| 118 | +$ kubectl get cpol disallow-host-path -o jsonpath='{.status}' |
| 119 | + |
| 120 | +{ |
| 121 | + "autogen":{ |
| 122 | + |
| 123 | + }, |
| 124 | + "conditions":[ |
| 125 | + { |
| 126 | + "lastTransitionTime":"2023-09-12T11:42:13Z", |
| 127 | + "message":"Ready", |
| 128 | + "reason":"Succeeded", |
| 129 | + "status":"True", |
| 130 | + "type":"Ready" |
| 131 | + } |
| 132 | + ], |
| 133 | + "ready":true, |
| 134 | + "rulecount":{ |
| 135 | + "generate":0, |
| 136 | + "mutate":0, |
| 137 | + "validate":1, |
| 138 | + "verifyimages":0 |
| 139 | + }, |
| 140 | + "validatingadmissionpolicy":{ |
| 141 | + "generated":true, |
| 142 | + "message":"" |
| 143 | + } |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +Let’s try getting the ValidatingAdmissionPolicy and its binding. |
| 148 | + |
| 149 | +```bash |
| 150 | +$ kubectl get validatingadmissionpolicy |
| 151 | +NAME VALIDATIONS PARAMKIND AGE |
| 152 | +disallow-host-path 1 <unset> 8m12s |
| 153 | + |
| 154 | +$ kubectl get validatingadmissionpolicybindings |
| 155 | +NAME POLICYNAME PARAMREF AGE |
| 156 | +disallow-host-path-binding disallow-host-path <unset> 8m30s |
| 157 | +``` |
| 158 | + |
| 159 | +You may notice that the ValidatingAdmissionPolicy and the ValidatingAdmissionPolicyBinding share the same name as the Kyverno policy they originate from, with the binding having a "-binding" suffix. |
| 160 | + |
| 161 | +Let’s have a look at the ValidatingAdmissionPolicy and its binding in detail. |
| 162 | + |
| 163 | +```bash |
| 164 | +$ kubectl get validatingadmissionpolicy disallow-host-path -o yaml |
| 165 | +apiVersion: admissionregistration.k8s.io/v1beta1 |
| 166 | +kind: ValidatingAdmissionPolicy |
| 167 | +metadata: |
| 168 | + creationTimestamp: "2023-09-12T11:42:13Z" |
| 169 | + generation: 1 |
| 170 | + labels: |
| 171 | + app.kubernetes.io/managed-by: kyverno |
| 172 | + name: disallow-host-path |
| 173 | + ownerReferences: |
| 174 | + - apiVersion: kyverno.io/v1 |
| 175 | + kind: ClusterPolicy |
| 176 | + name: disallow-host-path |
| 177 | + uid: e540d96b-c683-4380-a84f-13411384241a |
| 178 | + resourceVersion: "11294" |
| 179 | + uid: 9f3e0161-d010-4a6f-bd28-bf9c87151795 |
| 180 | +spec: |
| 181 | + failurePolicy: Fail |
| 182 | + matchConstraints: |
| 183 | + matchPolicy: Equivalent |
| 184 | + namespaceSelector: {} |
| 185 | + objectSelector: {} |
| 186 | + resourceRules: |
| 187 | + - apiGroups: |
| 188 | + - apps |
| 189 | + apiVersions: |
| 190 | + - v1 |
| 191 | + operations: |
| 192 | + - CREATE |
| 193 | + - UPDATE |
| 194 | + resources: |
| 195 | + - deployments |
| 196 | + scope: '*' |
| 197 | + validations: |
| 198 | + - expression: '!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, |
| 199 | + !has(volume.hostPath))' |
| 200 | + message: HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath |
| 201 | + must be unset. |
| 202 | + variables: null |
| 203 | +status: |
| 204 | + observedGeneration: 1 |
| 205 | + typeChecking: {} |
| 206 | +``` |
| 207 | + |
| 208 | +```bash |
| 209 | +$ kubectl get validatingadmissionpolicybindings disallow-host-path-binding -o yaml |
| 210 | +apiVersion: admissionregistration.k8s.io/v1beta1 |
| 211 | +kind: ValidatingAdmissionPolicyBinding |
| 212 | +metadata: |
| 213 | + creationTimestamp: "2023-09-12T11:42:13Z" |
| 214 | + generation: 1 |
| 215 | + labels: |
| 216 | + app.kubernetes.io/managed-by: kyverno |
| 217 | + name: disallow-host-path-binding |
| 218 | + ownerReferences: |
| 219 | + - apiVersion: kyverno.io/v1 |
| 220 | + kind: ClusterPolicy |
| 221 | + name: disallow-host-path |
| 222 | + uid: e540d96b-c683-4380-a84f-13411384241a |
| 223 | + resourceVersion: "11292" |
| 224 | + uid: 2fec35c3-8a8c-42a7-8a02-a75e8882a01e |
| 225 | +spec: |
| 226 | + policyName: disallow-host-path |
| 227 | + validationActions: |
| 228 | + - Deny |
| 229 | +``` |
| 230 | + |
| 231 | +Now, let’s try deploying an app that uses a hostPath: |
| 232 | + |
| 233 | +```bash |
| 234 | +kubectl apply -f - <<EOF |
| 235 | +apiVersion: apps/v1 |
| 236 | +kind: Deployment |
| 237 | +metadata: |
| 238 | + name: nginx |
| 239 | +spec: |
| 240 | + replicas: 2 |
| 241 | + selector: |
| 242 | + matchLabels: |
| 243 | + app: nginx |
| 244 | + template: |
| 245 | + metadata: |
| 246 | + labels: |
| 247 | + app: nginx |
| 248 | + spec: |
| 249 | + containers: |
| 250 | + - name: nginx-server |
| 251 | + image: nginx |
| 252 | + volumeMounts: |
| 253 | + - name: udev |
| 254 | + mountPath: /data |
| 255 | + volumes: |
| 256 | + - name: udev |
| 257 | + hostPath: |
| 258 | + path: /etc/udev |
| 259 | +EOF |
| 260 | +``` |
| 261 | + |
| 262 | +As expected, the deployment creation is rejected by the API server and not by the Kyverno admission controller. |
| 263 | + |
| 264 | +```bash |
| 265 | +The deployments "nginx" is invalid: ValidatingAdmissionPolicy 'disallow-host-path' with binding 'disallow-host-path-binding' denied request: HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset. |
| 266 | +``` |
| 267 | + |
| 268 | +If either the ValidatingAdmissionPolicy or the binding is deleted/updated for some reason, the controller is responsible for reverting it. |
| 269 | + |
| 270 | +Let’s try deleting the ValidatingAdmissionPolicy. |
| 271 | + |
| 272 | +```bash |
| 273 | +$ kubectl delete validatingadmissionpolicy disallow-host-path |
| 274 | +validatingadmissionpolicy.admissionregistration.k8s.io "disallow-host-path" deleted |
| 275 | + |
| 276 | +$ kubectl get validatingadmissionpolicy |
| 277 | +NAME VALIDATIONS PARAMKIND AGE |
| 278 | +disallow-host-path 1 <unset> 11s |
| 279 | +``` |
| 280 | + |
| 281 | +In addition, you can update the Kyverno policy, and the controller will re-generate the ValidatingAdmissionPolicy accordingly. For example, you can change the Kyverno policy to match statefulsets too. |
| 282 | + |
| 283 | +patch.yaml: |
| 284 | +```yaml |
| 285 | +spec: |
| 286 | + rules: |
| 287 | + - name: host-path |
| 288 | + match: |
| 289 | + any: |
| 290 | + - resources: |
| 291 | + kinds: |
| 292 | + - Deployment |
| 293 | + - StatefulSet |
| 294 | + validate: |
| 295 | + cel: |
| 296 | + expressions: |
| 297 | + - expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))" |
| 298 | + message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset." |
| 299 | +``` |
| 300 | +
|
| 301 | +```bash |
| 302 | +kubectl patch cpol disallow-host-path --type merge --patch-file patch.yaml |
| 303 | +``` |
| 304 | + |
| 305 | +The ValidatingAdmissionPolicy will be updated to match StatefulSets too. |
| 306 | + |
| 307 | +```yaml |
| 308 | +apiVersion: admissionregistration.k8s.io/v1beta1 |
| 309 | +kind: ValidatingAdmissionPolicy |
| 310 | +metadata: |
| 311 | + creationTimestamp: "2023-09-12T12:54:48Z" |
| 312 | + generation: 2 |
| 313 | + labels: |
| 314 | + app.kubernetes.io/managed-by: kyverno |
| 315 | + name: disallow-host-path |
| 316 | + ownerReferences: |
| 317 | + - apiVersion: kyverno.io/v1 |
| 318 | + kind: ClusterPolicy |
| 319 | + name: disallow-host-path |
| 320 | + uid: e540d96b-c683-4380-a84f-13411384241a |
| 321 | + resourceVersion: "29208" |
| 322 | + uid: 9325e2b7-9131-4ff4-9e56-244129cb625e |
| 323 | +spec: |
| 324 | + failurePolicy: Fail |
| 325 | + matchConstraints: |
| 326 | + matchPolicy: Equivalent |
| 327 | + namespaceSelector: {} |
| 328 | + objectSelector: {} |
| 329 | + resourceRules: |
| 330 | + - apiGroups: |
| 331 | + - apps |
| 332 | + apiVersions: |
| 333 | + - v1 |
| 334 | + operations: |
| 335 | + - CREATE |
| 336 | + - UPDATE |
| 337 | + resources: |
| 338 | + - deployments |
| 339 | + - statefulsets |
| 340 | + scope: '*' |
| 341 | + validations: |
| 342 | + - expression: '!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, |
| 343 | + !has(volume.hostPath))' |
| 344 | + message: HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath |
| 345 | + must be unset. |
| 346 | + variables: null |
| 347 | +status: |
| 348 | + observedGeneration: 2 |
| 349 | + typeChecking: {} |
| 350 | +``` |
| 351 | +
|
| 352 | +## Conclusion |
| 353 | +In this blog, we discussed how to generate Kubernetes ValidatingAdmissionPolicies from Kyverno policies. You can use CEL expressions in Kyverno policies to validate resources through either the Kyverno engine or the API server. In the next blog, we will discuss how to generate BackgroundScan reports for ValidatingAdmissionPolicies. |
| 354 | +
|
0 commit comments