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