6 min read | by Jordi Prats
We might call it best-practices or policies but most organizations have some rules about how their applications should run, for example: Do not use the latest tag. Some others might even be required to meet certain compliance requirements to reach some security standard, for example: Do not use NodePort services.
To be able to enforce these policies we can use a policy engine like OPA.
OPA gatekeeper is a validating webhook that enforces CRD-based policies executed by the Open Policy Agent.
To implement these policies as CRDs it uses two objects:
The validating webhook, by default, is ignored when it is returning an error (webhook down or unreachable): During this time constraints will not be enforced, but the audit process is expected to highlight any invalid resources that made it into the cluster.
We can also set it to fail closed by changing the failurePolicy of the ValidatingWebhookConfiguration object to Fail (with the helm chart it is controlled using the validatingWebhookFailurePolicy option) Even though you can always edit or delete a ValidatingWebhookConfiguration since these operations are not subjected to admission webhooks, it can cause circular dependencies where the actions to fix the webhook itself depend on some other action that failed because the webhook is failing: This needs to be carefully considered
To install OPA gatekeeper we can either install the manifests or go for a helm based install, which can't be more straightforward:
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace
In order to start applying our rules we can start writing them from scratch or look at the gatekeeper-library for rules that somebody have already implemented.
They provide a way of installing them using kustomize as follows:
$ kustomize build github.com/open-policy-agent/gatekeeper-library/library | kubectl apply --filename -
But we can choose hand pick which ConstraintTemplate we want to install. For example, to prevent the usage of NodePort we will have to install the following definition:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sblocknodeport
annotations:
description: >-
Disallows all Services with type NodePort.
https://kubernetes.io/docs/concepts/services-networking/service/#nodeport
spec:
crd:
spec:
names:
kind: K8sBlockNodePort
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sblocknodeport
violation[{"msg": msg}] {
input.review.kind.kind == "Service"
input.review.object.spec.type == "NodePort"
msg := "User is not allowed to create service of type NodePort"
}
Once this constraint is installed, we will be able to create instances of this rule by creating the K8sBlockNodePort object as follows:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockNodePort
metadata:
name: block-node-port
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Service"]
With this in place we won't be able to create the following object:
apiVersion: v1
kind: Service
metadata:
name: demo-nodeport
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: pet2cattle
If we try, we will get the following error message:
$ kubectl apply -f nodeportdemo.yaml
Error from server ([block-node-port] User is not allowed to create service of type NodePort): error when creating "nodeportdemo.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [block-node-port] User is not allowed to create service of type NodePort
We can use these objects to fine-tune what we want to block, for example, we can also apply this restriction to a specific namespace as follows:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockNodePort
metadata:
name: block-node-port
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Service"]
namespaces:
- "test"
With this configuration we will be able to create NodePort services on the namespaces that are not included on the list:
$ kubectl apply -f demonodeport.yaml -n test
Error from server ([block-node-port] User is not allowed to create service of type NodePort): error when creating "demonodeport.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [block-node-port] User is not allowed to create service of type NodePort
$ kubectl apply -f demonodeport.yaml -n trial
service/demo-nodeport created
With the OPA gatekeeper installation, there is a Pod that performs periodic evaluations of existing resources against constraints, detecting pre-existing misconfigurations or any object that somehow managed to get past the validating webhook.
We have tree main ways of checking the audit results:
For example, if we had an existing NodePort service before applying the K8sBlockNodePort constraint we will be able to see the violations on it's status:
$ kubectl describe K8sBlockNodePort block-node-port
Name: block-node-port
Namespace:
Labels: <none>
Annotations: helm.sh/hook: post-install,post-upgrade
API Version: constraints.gatekeeper.sh/v1beta1
Kind: K8sBlockNodePort
(...)
Spec:
Match:
Kinds:
API Groups:
Kinds:
Service
Status:
Audit Timestamp: 2022-03-28T21:34:21Z
By Pod:
Constraint UID: 3640d4af-dead-beef-a123-26bc96100f3e
Enforced: true
Id: gatekeeper-audit-b8af6ac78c-pf6pr
Observed Generation: 3
Operations:
audit
mutation-status
status
Constraint UID: 3640d4af-dead-beef-a123-26bc96100f3e
Enforced: true
Id: gatekeeper-controller-manager-b8af6ac78c-9btw4
Observed Generation: 3
Operations:
mutation-webhook
webhook
Constraint UID: 3640d4af-dead-beef-a123-26bc96100f3e
Enforced: true
Id: gatekeeper-controller-manager-b8af6ac78c-w9dw7
Observed Generation: 3
Operations:
mutation-webhook
webhook
Constraint UID: 3640d4af-dead-beef-a123-26bc96100f3e
Enforced: true
Id: gatekeeper-controller-manager-b8af6ac78c-qq847
Observed Generation: 3
Operations:
mutation-webhook
webhook
Total Violations: 1
Violations:
Enforcement Action: deny
Kind: Service
Message: User is not allowed to create service of type NodePort
Name: demo-nodeport
Namespace: trial
Events: <none>
And on the audit pod as well:
$ kubectl get pods -n gatekeeper-system
NAME READY STATUS RESTARTS AGE
gatekeeper-audit-b8af6ac78c-pf6pr 1/1 Running 0 43h
gatekeeper-controller-manager-b8af6ac78c-9btw4 1/1 Running 0 43h
gatekeeper-controller-manager-b8af6ac78c-w9dw7 1/1 Running 0 43h
gatekeeper-controller-manager-b8af6ac78c-qq847 1/1 Running 0 9h
$ kubectl logs gatekeeper-audit-b8af6ac78c-pf6pr -n gatekeeper-system
(...)
{"level":"info","ts":1648496316.0117562,"logger":"controller","msg":"starting update constraints loop","process":"audit","audit_id":"2022-03-28T21:38:36Z","constraints to update":"map[{K8sBlockNodePort constraints.gatekeeper.sh/v1beta1 block-node-port}:{map[apiVersion:constraints.gatekeeper.sh/v1beta1 kind:K8sBlockNodePort metadata:map[annotations:map[helm.sh/hook:post-install,post-upgrade] creationTimestamp:2022-03-23T22:11:42Z generation:3 managedFields:[map[apiVersion:constraints.gatekeeper.sh/v1beta1 fieldsType:FieldsV1 fieldsV1:map[f:status:map[]] manager:gatekeeper operation:Update time:2022-03-23T22:11:42Z] map[apiVersion:constraints.gatekeeper.sh/v1beta1 fieldsType:FieldsV1 fieldsV1:map[f:metadata:map[f:annotations:map[.:map[] f:helm.sh/hook:map[]]] f:spec:map[.:map[] f:match:map[.:map[] f:kinds:map[]]]] manager:terraform-provider-helm_v2.4.1_x5 operation:Update time:2022-03-23T22:11:42Z]] name:block-node-port resourceVersion:166046384 uid:0364a4df-d8e9-44fe-b131-0e602961f3bc] spec:map[match:map[kinds:[map[apiGroups:[] kinds:[Service]]]]] status:map[auditTimestamp:2022-03-28T13:32:21Z byPod:[map[constraintUID:0364a4df-d8e9-44fe-b131-0e602961f3bc enforced:true id:gatekeeper-audit-6c78cb88bf-9btw4 observedGeneration:3 operations:[audit mutation-status status]] map[constraintUID:0364a4df-d8e9-44fe-b131-0e602961f3bc enforced:true id:gatekeeper-controller-manager-b8af6ac78c-w9dw7 observedGeneration:3 operations:[mutation-webhook webhook]] map[constraintUID:0364a4df-d8e9-44fe-b131-0e602961f3bc enforced:true id:gatekeeper-controller-manager-b8af6ac78c-qq847 observedGeneration:3 operations:[mutation-webhook webhook]] map[constraintUID:0364a4df-d8e9-44fe-b131-0e602961f3bc enforced:true id:gatekeeper-controller-manager-b8af6ac78c-nr6pn observedGeneration:3 operations:[mutation-webhook webhook]]] totalViolations:1 violations:[map[enforcementAction:deny kind:Service message:User is not allowed to create service of type NodePort name:demo-nodeport namespace:trial]]]]}]"}
Posted on 29/03/2022