We are going to create 2 templates:
Run the following commands to download some policy template examples to your local git repo:
mkdir opa/templates
curl https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/library/general/allowedrepos/template.yaml -o opa/templates/allowed-repos.yaml
curl https://github.com/open-policy-agent/gatekeeper/blob/master/library/general/requiredlabels/template.yaml -o opa/templates/require-labels.yaml
Lets understand what’s happening and what each part of this template is doing.
If we look at require-labels.yaml
We can see this is a ConstraintTemplate object. If you remember this crd was created when we first deplopyed opa-gatekeeper.
Okay, the metadata has the name of the template which is standard stuff, let’s look at the next part, we can see inside the spec that this template is going to create another crd!!
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
message:
type: string
labels:
type: array
items:
type: object
properties:
key:
type: string
allowedRegex:
type: string
Remember, this is only a template, so that means that on its own, its not going to do anything, it needs to be consumed by something else. So every constraint template needs to create constraint objects so it can be used (and it does this by creating crd’s). Hopefully that makes sense but don’t worry we will show some examples shortly.
The above example shows the outline for the crd, in this case it’s called K8sRequiredLabels
it also contains the parameters of the constraint, in this case its labels
which is an array of items you can add (key
+ allowedRegex
).
There is also a message
string which can be used add different messages to the template.
This allows us to create simple constraints with just a few parameters and re-use this template.
The next part of the constraints template is targets
. This is where the rego code (policy rules) are added..
You can only have 1 target per constraint
This contains the rego
policy you should use to define what rules are applied.
Let’s take a look:
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
get_message(parameters, _default) = msg {
not parameters.message
msg := _default
}
get_message(parameters, _default) = msg {
msg := parameters.message
}
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_].key}
missing := required - provided
count(missing) > 0
def_msg := sprintf("you must provide labels: %v", [missing])
msg := get_message(input.parameters, def_msg)
}
violation[{"msg": msg}] {
value := input.review.object.metadata.labels[key]
expected := input.parameters.labels[_]
expected.key == key
# do not match if allowedRegex is not defined, or is an empty string
expected.allowedRegex != ""
not re_match(expected.allowedRegex, value)
def_msg := sprintf("Label <%v: %v> does not satisfy allowed regex: %v", [key, value, expected.allowedRegex])
msg := get_message(input.parameters, def_msg)
}
As we can see in the rego policy, we’ve set 2 violations
, one flags if required labels are missing, the other flags up if the label exists but does not match the regex we specified in the contraint.
Things to note - when configuring rules you can add as many and configure them how you want but you must have the following format on at least one of them for this to work and flag as a violation:
violation[{"msg": msg, "details": {}}] {
# rule body
}
As we discussed earlier, this is just a contraint template, its not a constraint and it does nothing when deployed on it’s own.
The format for this look svery similar, we have metadata
and name
, then inside the spec
we are defining a crd called K8sAllowedRepos
.
The accepted parameters are an array of repos
which include item values.
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
repos:
type: array
items:
type: string
Next let’s look at the targets:
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
satisfied := [good | repo = input.parameters.repos[_] ; good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("container <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
satisfied := [good | repo = input.parameters.repos[_] ; good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("container <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
}
For this we’ve created 2 rule violations. Bot of which checks the provided container image and compares the beginning of it to the repos in our parameters.
If the image we use does not match what we have in our contraint, then it will trigger a violation.
Lets check this in to git and next we can look at constraints and start using these templates.
git add opa/templates
git commit -m "adding opa templates"
git push