From d9272df1a62fa0381006a8622f581214e5ea89f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Fri, 27 Sep 2024 14:04:39 +0200 Subject: [PATCH 1/4] Add Policy Groups deep dive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Víctor Cuadrado Juan --- content/blog/2024/10/policy-groups.md | 276 ++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 content/blog/2024/10/policy-groups.md diff --git a/content/blog/2024/10/policy-groups.md b/content/blog/2024/10/policy-groups.md new file mode 100644 index 00000000..b3cb7f05 --- /dev/null +++ b/content/blog/2024/10/policy-groups.md @@ -0,0 +1,276 @@ +--- +title: Policy Groups deep dive +authors: + - Víctor Cuadrado Juan +date: 2024-11-04 +--- + +With [v1.17](https://www.kubewarden.io/blog/2024/10/kubewarden-1-17-release/), +we introduced a new powerful feature, Policy Groups, brought by 2 new Kubernetes +Custom Resources: + +- **AdmissionPolicyGroups**: Namespaced policy comprised of several policies. +- **ClusterAdmissionPolicyGroups**: Clusterwide policy comprised of several policies. + +These new Policy Groups resources define a policy comprised of several policies and +their policy settings, and they perform a combined evaluation of those multiple +policies using logical operators. + +Why are these useful? Because they reuse existing policies, reducing the need +for custom policy creation. And they provide complex logic while at the same +time reducing complexity as you have all the logic packed in 1 resource +definition. + +## Writing a Policy Group + +AdmissionPolicyGroups and ClusterAdmissionPolicyGroups are similar to the +policies you already knew. Let's start writing a ClusterAdmissionPolicyGroup +that ensures Service selectors are unique, or assigned to a specific +organization team. + +We write the usual metadata and rules for the policy: + +```yaml +apiVersion: policies.kubewarden.io/v1 +kind: ClusterAdmissionPolicyGroup +metadata: + name: unique-service-selector +spec: + rules: + - apiGroups: [""] + apiVersions: ["v1"] + resources: ["services"] + operations: ["CREATE", "UPDATE"] +``` + +### Policies field & context-aware + +Now, we define several policies and their settings. This is done in its +`spec.policies` map: + +```yaml +policies: + unique_service_selector: + module: registry://ghcr.io/kubewarden/policies/unique-service-selector-policy:v0.1.0 + contextAwareResources: + - apiVersion: v1 + kind: Service + settings: + app.kubernetes.io/name: MyApp + owned_by_quokkas_team: + module: registry://ghcr.io/kubewarden/policies/safe-annotations:v0.2.9 + settings: + mandatory_annotations: + - owner + constrained_annotations: + owner: "quokkas-team" +``` + +In this map, you can see we have defined 2 policies. One that checks for +Services to be unique, and another that checks for an annotation `owner: +foo-team`. These policy entries in the map are named. And this allows us +to write a boolean expresion with the boolean results of each policy. + +If you look even closer, you will realize that the `unique_service_selector` +policy is context-aware, as it defines its own `contextAwareResources`. That's +true, Policy Groups support context-aware policies, and will evaluate them as +usual, with their fine-grained defined permissions. + +### Expresion field & mutation + +We evaluate that logic in the `spec.expression`. In our case: + +```yaml +expression: "unique_service_selector() || (!unique_service_selector() && owned_by_quokkas_team())" +``` + +Which means that we expect the Service selector to be unique, or if it isn't, +to be owned by the Quokkas team. The Quokkas team is awesome; they know how to +have a clean house by themselves. + +The `expression` is a boolean one, evaluating the known policy identifiers +defined in the Policy Group. We can use `&&`, `||`, `!` for AND, OR, and NOT +operations, as well as `(`,`)` for evaluation priorities. + +The expression evaluation has short-circuit and only evaluates those that are +needed; if one of the boolean results is true and nothing else is needed to +accept or reject, the other evaluations are not performed to save resources. + +The short-circuit means that we don't support mutation for Policy Groups so +far, which simplifies evaluation and mental load. + +### Message field + +Ok, so we know how the policy gets evaluated into a rejection or acceptation. +But how do we express that to the users? We do it by setting its +`spec.message`, which gets returned as part of the AdmissionResponse as usual, +to kubectl, or whomever expects it. + +In our case, we can write: + +```yaml +message: "the service selector is not unique or the service is not owned by the foo team" +``` + +In addition, all the evaluation details of each the group policies are sent as part +of the AdmissionResponse `.status.details.causes` as we will see later. + +## Hands-on: Instantiating + +Our Policy Group looks as follows: + +```yaml +# unique-service-selector.yml +--- +apiVersion: policies.kubewarden.io/v1 +kind: ClusterAdmissionPolicyGroup +metadata: + name: unique-service-selector +spec: + rules: + - apiGroups: [""] + apiVersions: ["v1"] + resources: ["services"] + operations: ["CREATE", "UPDATE"] + policies: + unique_service_selector: + module: registry://ghcr.io/kubewarden/policies/unique-service-selector-policy:v0.1.0 + contextAwareResources: + - apiVersion: v1 + kind: Service + settings: + app.kubernetes.io/name: MyApp + owned_by_quokkas_team: + module: registry://ghcr.io/kubewarden/policies/safe-annotations:v0.2.9 + settings: + mandatory_annotations: + - owner + constrained_annotations: + owner: "quokkas-team" + expression: "unique_service_selector() || (!unique_service_selector() && owned_by_quokkas_team())" + message: "the service selector is not unique or the service is not owned by the Quokkas team" +``` + +We can apply it as usual: + +```console +$ kubectl apply -f unique-service-selector.yml +clusteradmissionpolicygroup.policies.kubewarden.io/unique-service-selector created +$ kubectl get clusteradmissionpolicygroups.policies.kubewarden.io +NAME POLICY SERVER MUTATING BACKGROUNDAUDIT MODE OBSERVED MODE STATUS AGE +unique-service-selector default true protect protect active 30s +``` + +On Policy Group instantiation, both the `spec.expression` and each of the policies' +settings get validated, and if any is incorrect, one would get an error as usual. + +Our policy is now active and ready. Let's try to create a Service: + +```console +kubectl apply -f - < Date: Mon, 30 Sep 2024 10:15:28 +0200 Subject: [PATCH 2/4] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: John Krug Signed-off-by: Víctor Cuadrado Juan <2196685+viccuad@users.noreply.github.com> --- content/blog/2024/10/policy-groups.md | 31 +++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/content/blog/2024/10/policy-groups.md b/content/blog/2024/10/policy-groups.md index b3cb7f05..d48a15b9 100644 --- a/content/blog/2024/10/policy-groups.md +++ b/content/blog/2024/10/policy-groups.md @@ -6,7 +6,7 @@ date: 2024-11-04 --- With [v1.17](https://www.kubewarden.io/blog/2024/10/kubewarden-1-17-release/), -we introduced a new powerful feature, Policy Groups, brought by 2 new Kubernetes +we introduced a new powerful feature, Policy Groups, enabled by two new Kubernetes Custom Resources: - **AdmissionPolicyGroups**: Namespaced policy comprised of several policies. @@ -18,13 +18,13 @@ policies using logical operators. Why are these useful? Because they reuse existing policies, reducing the need for custom policy creation. And they provide complex logic while at the same -time reducing complexity as you have all the logic packed in 1 resource +time reducing complexity as you have all the logic contained in one resource definition. ## Writing a Policy Group AdmissionPolicyGroups and ClusterAdmissionPolicyGroups are similar to the -policies you already knew. Let's start writing a ClusterAdmissionPolicyGroup +policies you already know. Let's start writing a ClusterAdmissionPolicyGroup that ensures Service selectors are unique, or assigned to a specific organization team. @@ -45,7 +45,7 @@ spec: ### Policies field & context-aware -Now, we define several policies and their settings. This is done in its +Now, we define several policies and their settings. This is done in the `spec.policies` map: ```yaml @@ -69,9 +69,9 @@ policies: In this map, you can see we have defined 2 policies. One that checks for Services to be unique, and another that checks for an annotation `owner: foo-team`. These policy entries in the map are named. And this allows us -to write a boolean expresion with the boolean results of each policy. +to write a boolean expression combining the results of each policy. -If you look even closer, you will realize that the `unique_service_selector` +If you look closer, you will see that the `unique_service_selector` policy is context-aware, as it defines its own `contextAwareResources`. That's true, Policy Groups support context-aware policies, and will evaluate them as usual, with their fine-grained defined permissions. @@ -86,25 +86,24 @@ expression: "unique_service_selector() || (!unique_service_selector() && owned_b Which means that we expect the Service selector to be unique, or if it isn't, to be owned by the Quokkas team. The Quokkas team is awesome; they know how to -have a clean house by themselves. +keep a secure house. The `expression` is a boolean one, evaluating the known policy identifiers defined in the Policy Group. We can use `&&`, `||`, `!` for AND, OR, and NOT operations, as well as `(`,`)` for evaluation priorities. -The expression evaluation has short-circuit and only evaluates those that are +The expression evaluation has a short-circuit and only evaluates those that are needed; if one of the boolean results is true and nothing else is needed to accept or reject, the other evaluations are not performed to save resources. -The short-circuit means that we don't support mutation for Policy Groups so -far, which simplifies evaluation and mental load. +The short-circuit means that we don't support mutation for Policy Groups, which simplifies evaluation and the conceptual load. ### Message field -Ok, so we know how the policy gets evaluated into a rejection or acceptation. -But how do we express that to the users? We do it by setting its +Ok, so we know how the policy gets evaluated and rejected or accepted. +But how do we express that to users? We do it by setting its `spec.message`, which gets returned as part of the AdmissionResponse as usual, -to kubectl, or whomever expects it. +to kubectl, or whoever expects it. In our case, we can write: @@ -112,7 +111,7 @@ In our case, we can write: message: "the service selector is not unique or the service is not owned by the foo team" ``` -In addition, all the evaluation details of each the group policies are sent as part +In addition, all the evaluation details of each of the group policies are sent as part of the AdmissionResponse `.status.details.causes` as we will see later. ## Hands-on: Instantiating @@ -162,7 +161,7 @@ unique-service-selector default true protect ``` On Policy Group instantiation, both the `spec.expression` and each of the policies' -settings get validated, and if any is incorrect, one would get an error as usual. +settings get validated, and if any is incorrect, one gets an error as expected. Our policy is now active and ready. Let's try to create a Service: @@ -250,7 +249,7 @@ Error from server: error when creating "second-service.yml": admission webhook " ### Audit scanner Another way of obtaining details on the state of the cluster is the [Audit -Scanner](https://docs.kubewarden.io/next/explanations/audit-scanner) feature. +Scanner](https://docs.kubewarden.io/next/explanations/audit-scanner). Policy Groups are fully supported in the Kubewarden stack, and their results get published to Policy Reports. From 85bfbba5feb64d8f20b4dcb8aba1f14e024e075a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Mon, 30 Sep 2024 12:34:37 +0200 Subject: [PATCH 3/4] Fix typo on bold formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Víctor Cuadrado Juan --- content/blog/2024/10/policy-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/10/policy-groups.md b/content/blog/2024/10/policy-groups.md index d48a15b9..367aeb5a 100644 --- a/content/blog/2024/10/policy-groups.md +++ b/content/blog/2024/10/policy-groups.md @@ -255,7 +255,7 @@ get published to Policy Reports. ## Recap -We have seen the 2 new Policy Groups Custom Resources: **\*AdmissionPolicyGroups** and +We have seen the 2 new Policy Groups Custom Resources: **AdmissionPolicyGroups** and **ClusterAdmissionPolicyGroups**. They provide a way to define complex policies by including definitions of existing policies. They support context-aware policies, yet they don't perform mutations. From ba57dce41172be0ad9d354dd61b7cc25cf0c6eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Tue, 1 Oct 2024 12:05:07 +0200 Subject: [PATCH 4/4] Update publish date to Wed 11-02 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Víctor Cuadrado Juan --- content/blog/2024/10/policy-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2024/10/policy-groups.md b/content/blog/2024/10/policy-groups.md index 367aeb5a..95a27afa 100644 --- a/content/blog/2024/10/policy-groups.md +++ b/content/blog/2024/10/policy-groups.md @@ -2,7 +2,7 @@ title: Policy Groups deep dive authors: - Víctor Cuadrado Juan -date: 2024-11-04 +date: 2024-11-02 --- With [v1.17](https://www.kubewarden.io/blog/2024/10/kubewarden-1-17-release/),