Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Enforce Kubernetes admission policies using OPA Gatekeeper with ConstraintTemplates, Rego rules, and the Gatekeeper policy library.
.claude/skills/implementing-opa-gatekeeper-for-policy-enforcement/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-22 | ✗→✓ | ▲ Improved | — | — |
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
OPA Gatekeeper is a Kubernetes admission controller that enforces policies written in Rego. It uses ConstraintTemplates (policy blueprints with Rego logic) and Constraints (instantiated policies with parameters) to validate, mutate, or deny Kubernetes resource requests at admission time.
bash# Install via Helm helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts helm repo update helm install gatekeeper gatekeeper/gatekeeper \ --namespace gatekeeper-system --create-namespace \ --set replicas=3 \ --set audit.replicas=1 \ --set audit.logLevel=INFO # Verify kubectl get pods -n gatekeeper-system kubectl get crd | grep gatekeeper
bash# Check webhook kubectl get validatingwebhookconfigurations gatekeeper-validating-webhook-configuration # Check CRDs kubectl get crd constrainttemplates.templates.gatekeeper.sh kubectl get crd configs.config.gatekeeper.sh
yaml# template-required-labels.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8srequiredlabels spec: crd: spec: names: kind: K8sRequiredLabels validation: openAPIV3Schema: type: object 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("Missing required labels: %v", [missing]) }
yaml# constraint-require-team-label.yaml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: K8sRequiredLabels metadata: name: require-team-label spec: match: kinds: - apiGroups: [""] kinds: ["Namespace"] - apiGroups: ["apps"] kinds: ["Deployment"] parameters: labels: - "team" - "environment"
yaml# template-block-privileged.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8sblockprivileged spec: crd: spec: names: kind: K8sBlockPrivileged targets: - target: admission.k8s.gatekeeper.sh rego: | package k8sblockprivileged violation[{"msg": msg}] { container := input.review.object.spec.containers[_] container.securityContext.privileged == true msg := sprintf("Privileged container not allowed: %v", [container.name]) } violation[{"msg": msg}] { container := input.review.object.spec.initContainers[_] container.securityContext.privileged == true msg := sprintf("Privileged init container not allowed: %v", [container.name]) }
yaml# constraint-block-privileged.yaml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: K8sBlockPrivileged metadata: name: block-privileged-containers spec: match: kinds: - apiGroups: [""] kinds: ["Pod"] namespaces: - "production" - "staging"
yaml# template-allowed-repos.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8sallowedrepos spec: crd: spec: names: kind: K8sAllowedRepos validation: openAPIV3Schema: type: object properties: repos: type: array items: type: string targets: - target: admission.k8s.gatekeeper.sh rego: | package k8sallowedrepos violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not image_matches(container.image) msg := sprintf("Container image %v is not from an allowed registry. Allowed: %v", [container.image, input.parameters.repos]) } violation[{"msg": msg}] { container := input.review.object.spec.initContainers[_] not image_matches(container.image) msg := sprintf("Init container image %v is not from an allowed registry. Allowed: %v", [container.image, input.parameters.repos]) } image_matches(image) { repo := input.parameters.repos[_] startswith(image, repo) }
yaml# constraint-allowed-repos.yaml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: K8sAllowedRepos metadata: name: restrict-image-repos spec: match: kinds: - apiGroups: [""] kinds: ["Pod"] parameters: repos: - "gcr.io/my-project/" - "ghcr.io/my-org/" - "registry.k8s.io/"
yaml# template-require-limits.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8srequirelimits spec: crd: spec: names: kind: K8sRequireLimits targets: - target: admission.k8s.gatekeeper.sh rego: | package k8srequirelimits violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not container.resources.limits.cpu msg := sprintf("Container %v has no CPU limit", [container.name]) } violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not container.resources.limits.memory msg := sprintf("Container %v has no memory limit", [container.name]) }
yaml# template-block-latest-tag.yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8sblocklatesttag spec: crd: spec: names: kind: K8sBlockLatestTag targets: - target: admission.k8s.gatekeeper.sh rego: | package k8sblocklatesttag violation[{"msg": msg}] { container := input.review.object.spec.containers[_] endswith(container.image, ":latest") msg := sprintf("Container %v uses ':latest' tag. Use specific version tags.", [container.name]) } violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not contains(container.image, ":") msg := sprintf("Container %v has no tag (defaults to latest). Use specific version tags.", [container.name]) }
yamlapiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8sreadonlyroot spec: crd: spec: names: kind: K8sReadOnlyRoot targets: - target: admission.k8s.gatekeeper.sh rego: | package k8sreadonlyroot violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not container.securityContext.readOnlyRootFilesystem msg := sprintf("Container %v must have readOnlyRootFilesystem set to true", [container.name]) }
yaml# Dry-run mode (audit only, don't block) apiVersion: constraints.gatekeeper.sh/v1beta1 kind: K8sBlockPrivileged metadata: name: block-privileged-dryrun spec: enforcementAction: dryrun # dryrun | deny | warn match: kinds: - apiGroups: [""] kinds: ["Pod"]
bash# List all constraint violations kubectl get k8sblockprivileged block-privileged-containers -o yaml | grep -A 20 violations # Check all constraints audit status kubectl get constraints -o json | jq '.items[] | {name: .metadata.name, violations: (.status.violations // [] | length)}'
yamlapiVersion: config.gatekeeper.sh/v1alpha1 kind: Config metadata: name: config namespace: gatekeeper-system spec: match: - excludedNamespaces: - kube-system - gatekeeper-system - calico-system processes: - "*"
bash# Check Gatekeeper metrics kubectl port-forward -n gatekeeper-system svc/gatekeeper-webhook-service 8443:443 # Prometheus metrics kubectl get --raw /metrics | grep gatekeeper
dryrun mode first, review violations, then switch to deny.status.violations regularlyopa test or Rego Playground before deploying| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.