From 985275e8dfc358bba1155ff2f63a04db031a5402 Mon Sep 17 00:00:00 2001 From: juliankatz Date: Wed, 11 Aug 2021 12:05:49 -0700 Subject: [PATCH 1/7] Add docs page about v1 Constraint Templates Fixes #1476 Signed-off-by: juliankatz --- website/docs/constrainttemplates.md | 152 ++++++++++++++++++++++++++++ website/sidebars.js | 3 +- 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 website/docs/constrainttemplates.md diff --git a/website/docs/constrainttemplates.md b/website/docs/constrainttemplates.md new file mode 100644 index 00000000000..0378ee0d742 --- /dev/null +++ b/website/docs/constrainttemplates.md @@ -0,0 +1,152 @@ +--- +id: constrainttemplates +title: Constraint Templates +--- + +ConstraintTemplates define a way to validate some set of Kubernetes objects in Gatekeeper's Kubernetes [admission controller](https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/). They are made of two main elements: + +1. [Rego](https://www.openpolicyagent.org/docs/latest/#rego) code that defines a policy violation +2. The schema of the accompanying `Constraint` object, which represents an instantiation of a `ConstraintTemplate` + +## `v1` Constraint Template + +In release version (some version goes here), Gatekeeper included the `v1` version of `ConstraintTemplate`. Unlike past versions of `ConstraintTemplate`, `v1` requires the Constraint schema section to be [structural](https://kubernetes.io/blog/2019/06/20/crd-structural-schema/). + +Structural schemas have a variety of [requirements](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema). One such requirement is that of the `type` field to be defined for each level of the schema. + +For example, users of Gatekeeper may recognize the `k8srequiredlabels` ConstraintTemplate, defined here in version `v1beta1`: + +```yaml +apiVersion: templates.gatekeeper.sh/v1beta1 +kind: ConstraintTemplate +metadata: + name: k8srequiredlabels +spec: + crd: + spec: + names: + kind: K8sRequiredLabels + validation: + # Schema for the `parameters` field + openAPIV3Schema: + properties: + labels: + type: array + items: string + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8srequiredlabels + + violation[{"msg": msg, "details": {"missing_labels": missing}}] { + provided := {label | input.review.object.metadata.labels[label]} + required := {label | label := input.parameters.labels[_]} + missing := required - provided + count(missing) > 0 + msg := sprintf("you must provide labels: %v", [missing]) + } +``` + +The `parameters` field schema (`spec.crd.spec.validation.openAPIV3Schema`) is _not_ structural. Notably, it is missing the `type:` declaration: + +```yaml +openAPIV3Schema: + # missing type + properties: + labels: + type: array + items: string +``` + +This schema is _invalid_ by default in a `v1` ConstraintTemplate. Adding the `type` information makes the schema valid: + +```yaml +openAPIV3Schema: + type: object + properties: + labels: + type: array + items: string +``` + +For more information on valid types in JSONSchemas, see the [JSONSchema documentation](https://json-schema.org/understanding-json-schema/reference/type.html). + +## Why implement this change? + +Requiring structural schemas in Constraint Templates yields usability improvements. As the required data types are defined, the API server will reject a `Constraint` with an incorrect `parameters` field, as opposed to ingesting it and simply not passing those `parameters` into Gatekeeper. + +For example, see this incorrectly defined `k8srequiredlabels` Constraint: + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: ns-must-have-gk +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Namespace"] + parameters: + # Note that "labels" is now an array item, rather than an object + - labels: ["gatekeeper"] +``` + +In a `v1beta1` ConstraintTemplate, this Constraint would be ingested successfully. However, it would not work. The creation of a new namespace, `foobar`, would succeed, even in the absence of the `gatekeeper` label: + +```shell +$ kubectl create ns foobar +namespace/foobar created +``` + +This is incorrect. We'd expect this to fail: + +```shell +$ kubectl create ns foobar +Error from server ([ns-must-have-gk] you must provide labels: {"gatekeeper"}): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-gk] you must provide labels: {"gatekeeper"} +``` + +The structural schema requirement _prevents this mistake_. The aforementioned `type: object` declaration would prevent the API server from accepting the incorrect `k8srequiredlabels` Constraint. + +```shell +# Apply the Constraint with incorrect parameters schema +$ cat << EOF | kubectl apply -f - +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: ns-must-have-gk +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Namespace"] + parameters: + # Note that "labels" is now an array item, rather than an object + - labels: ["gatekeeper"] +EOF +The K8sRequiredLabels "ns-must-have-gk" is invalid: spec.parameters: Invalid value: "array": spec.parameters in body must be of type object: "array" +``` + +Fixing the incorrect `parameters` section would then yield a successful ingestion and a working Constraint. + +```shell +$ cat << EOF | kubectl apply -f - +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: ns-must-have-gk +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Namespace"] + parameters: + labels: ["gatekeeper"] +EOF +k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-gk created +``` + +```shell +❯ kubectl create ns foobar +Error from server ([ns-must-have-gk] you must provide labels: {"gatekeeper"}): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-gk] you must provide labels: {"gatekeeper"} +``` diff --git a/website/sidebars.js b/website/sidebars.js index bfbf4c878a0..2e1cba82df8 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -28,7 +28,8 @@ module.exports = { 'emergency', 'vendor-specific', 'failing-closed', - 'mutation' + 'mutation', + 'constrainttemplates' ], }, { From 980e950d3e8f49fec44abc43bbd0da827b6d460c Mon Sep 17 00:00:00 2001 From: juliankatz Date: Thu, 12 Aug 2021 13:52:26 -0700 Subject: [PATCH 2/7] Small change to some code examples Signed-off-by: juliankatz --- website/docs/constrainttemplates.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/constrainttemplates.md b/website/docs/constrainttemplates.md index 0378ee0d742..ba20b53caba 100644 --- a/website/docs/constrainttemplates.md +++ b/website/docs/constrainttemplates.md @@ -88,7 +88,7 @@ spec: - apiGroups: [""] kinds: ["Namespace"] parameters: - # Note that "labels" is now an array item, rather than an object + # Note that "labels" is now contained in an array item, rather than an object key under "parameters" - labels: ["gatekeeper"] ``` @@ -147,6 +147,6 @@ k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-gk created ``` ```shell -❯ kubectl create ns foobar +$ kubectl create ns foobar Error from server ([ns-must-have-gk] you must provide labels: {"gatekeeper"}): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-gk] you must provide labels: {"gatekeeper"} ``` From cc61a29e8395ce354ef73b32abf1bc66c827e6f5 Mon Sep 17 00:00:00 2001 From: juliankatz Date: Mon, 16 Aug 2021 14:15:39 -0700 Subject: [PATCH 3/7] Add link to constrainttemplates.md Signed-off-by: juliankatz --- website/docs/howto.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/howto.md b/website/docs/howto.md index 43c91c70290..fc5d99320bc 100644 --- a/website/docs/howto.md +++ b/website/docs/howto.md @@ -7,7 +7,7 @@ Gatekeeper uses the [OPA Constraint Framework](https://github.com/open-policy-ag ## Constraint Templates -Before you can define a constraint, you must first define a `ConstraintTemplate`, which describes both the [Rego](https://www.openpolicyagent.org/docs/latest/#rego) that enforces the constraint and the schema of the constraint. The schema of the constraint allows an admin to fine-tune the behavior of a constraint, much like arguments to a function. +Before you can define a constraint, you must first define a [`ConstraintTemplate`](constrainttemplates.md), which describes both the [Rego](https://www.openpolicyagent.org/docs/latest/#rego) that enforces the constraint and the schema of the constraint. The schema of the constraint allows an admin to fine-tune the behavior of a constraint, much like arguments to a function. Here is an example constraint template that requires all labels described by the constraint to be present: From 2d74849bb38713310745e2c8eacd54e85c54a63d Mon Sep 17 00:00:00 2001 From: juliankatz Date: Mon, 16 Aug 2021 14:16:19 -0700 Subject: [PATCH 4/7] Added actual release version to constrainttemplate.md Signed-off-by: juliankatz --- website/docs/constrainttemplates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/constrainttemplates.md b/website/docs/constrainttemplates.md index ba20b53caba..35c29aae7eb 100644 --- a/website/docs/constrainttemplates.md +++ b/website/docs/constrainttemplates.md @@ -10,7 +10,7 @@ ConstraintTemplates define a way to validate some set of Kubernetes objects in G ## `v1` Constraint Template -In release version (some version goes here), Gatekeeper included the `v1` version of `ConstraintTemplate`. Unlike past versions of `ConstraintTemplate`, `v1` requires the Constraint schema section to be [structural](https://kubernetes.io/blog/2019/06/20/crd-structural-schema/). +In release version 3.6.0, Gatekeeper included the `v1` version of `ConstraintTemplate`. Unlike past versions of `ConstraintTemplate`, `v1` requires the Constraint schema section to be [structural](https://kubernetes.io/blog/2019/06/20/crd-structural-schema/). Structural schemas have a variety of [requirements](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema). One such requirement is that of the `type` field to be defined for each level of the schema. From c486bd68925e0c061b30ca7b589e29f06020c003 Mon Sep 17 00:00:00 2001 From: juliankatz Date: Mon, 16 Aug 2021 14:17:24 -0700 Subject: [PATCH 5/7] fix { items: string } in constrainttemplate.md Signed-off-by: juliankatz --- website/docs/constrainttemplates.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/website/docs/constrainttemplates.md b/website/docs/constrainttemplates.md index 35c29aae7eb..79685c8773b 100644 --- a/website/docs/constrainttemplates.md +++ b/website/docs/constrainttemplates.md @@ -32,7 +32,8 @@ spec: properties: labels: type: array - items: string + items: + type: string targets: - target: admission.k8s.gatekeeper.sh rego: | @@ -55,7 +56,8 @@ openAPIV3Schema: properties: labels: type: array - items: string + items: + type: string ``` This schema is _invalid_ by default in a `v1` ConstraintTemplate. Adding the `type` information makes the schema valid: @@ -66,7 +68,8 @@ openAPIV3Schema: properties: labels: type: array - items: string + items: + type: string ``` For more information on valid types in JSONSchemas, see the [JSONSchema documentation](https://json-schema.org/understanding-json-schema/reference/type.html). From 54979984e369845436110b1b2af57ad261943982 Mon Sep 17 00:00:00 2001 From: juliankatz Date: Thu, 19 Aug 2021 14:34:14 -0700 Subject: [PATCH 6/7] Incorporated max's comments about structural schema reasons Signed-off-by: juliankatz --- website/docs/constrainttemplates.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/docs/constrainttemplates.md b/website/docs/constrainttemplates.md index 79685c8773b..19a83036894 100644 --- a/website/docs/constrainttemplates.md +++ b/website/docs/constrainttemplates.md @@ -76,7 +76,11 @@ For more information on valid types in JSONSchemas, see the [JSONSchema document ## Why implement this change? -Requiring structural schemas in Constraint Templates yields usability improvements. As the required data types are defined, the API server will reject a `Constraint` with an incorrect `parameters` field, as opposed to ingesting it and simply not passing those `parameters` into Gatekeeper. +Structural schemas are required in version `v1` of `CustomResourceDefinition` resources, which underlie ConstraintTemplates. Requiring the same in ConstraintTemplates puts Gatekeeper in line with the overall direction of Kubernetes. + +Beyond this alignment, structural schemas yield significant usability improvements. The schema of a ConstraintTemplate's associated Constraint is both more visible and type validated. + +As the data types of Constraint fields are defined in the ConstraintTemplate, the API server will reject a Constraint with an incorrect `parameters` field. Previously, the API server would ingest it and simply not pass those `parameters` to Gatekeeper. This experience was confusing for users, and is noticeably improved by structural schemas. For example, see this incorrectly defined `k8srequiredlabels` Constraint: From 997efd4ae995a1804e92aaac175199383dcf9705 Mon Sep 17 00:00:00 2001 From: juliankatz Date: Thu, 19 Aug 2021 14:36:05 -0700 Subject: [PATCH 7/7] "that of" --> "that" Signed-off-by: juliankatz --- website/docs/constrainttemplates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/constrainttemplates.md b/website/docs/constrainttemplates.md index 19a83036894..08db467adb1 100644 --- a/website/docs/constrainttemplates.md +++ b/website/docs/constrainttemplates.md @@ -12,7 +12,7 @@ ConstraintTemplates define a way to validate some set of Kubernetes objects in G In release version 3.6.0, Gatekeeper included the `v1` version of `ConstraintTemplate`. Unlike past versions of `ConstraintTemplate`, `v1` requires the Constraint schema section to be [structural](https://kubernetes.io/blog/2019/06/20/crd-structural-schema/). -Structural schemas have a variety of [requirements](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema). One such requirement is that of the `type` field to be defined for each level of the schema. +Structural schemas have a variety of [requirements](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema). One such requirement is that the `type` field be defined for each level of the schema. For example, users of Gatekeeper may recognize the `k8srequiredlabels` ConstraintTemplate, defined here in version `v1beta1`: