Constraint Templates
ConstraintTemplates define a way to validate some set of Kubernetes objects in Gatekeeper's Kubernetes admission controller. They are made of two main elements:
- Rego code that defines a policy violation
- The schema of the accompanying
Constraint
object, which represents an instantiation of aConstraintTemplate
❗ Validation of Rego for constraint templates is enabled by default. Set
validate-template-rego
flag tofalse
to disable rego validation if you want to useexperimental-enable-k8s-native-validation
Kubernetes CEL based policies as well. This flag will be removed from Gatekeeper 3.16 and later, please make use of Gator to validate constraint template in shift left manner to avoid any impact with this behavior change.
v1
Constraint Template
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.
Structural schemas have a variety of requirements. 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
:
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:
type: 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:
openAPIV3Schema:
# missing type
properties:
labels:
type: array
items:
type: string
This schema is invalid by default in a v1
ConstraintTemplate. Adding the type
information makes the schema valid:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
For more information on valid types in JSONSchemas, see the JSONSchema documentation.
Why implement this change?
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:
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 contained in an array item, rather than an object key under "parameters"
- 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:
$ kubectl create ns foobar
namespace/foobar created
This is incorrect. We'd expect this to fail:
$ 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.
# 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.
$ 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
$ 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"}