DEV Community

John  Ajera
John Ajera

Posted on

Gatekeeper Disallow Default Namespace – Create, Deploy & Test Guide

Gatekeeper Disallow Default Namespace – Create, Deploy & Test Guide

A step-by-step guide to building and deploying a custom Gatekeeper policy that blocks workloads from being created in the Kubernetes default namespace.


1. Overview

What this guide does:

  • Blocks Pods, Services, Deployments, ConfigMaps, Secrets, and similar resources from being created in default
  • Runs in dryrun (audit-only) or deny (block) mode
  • Can be tuned to target specific resource types

Prerequisites:

  • Kubernetes cluster (1.19+)
  • Gatekeeper installed (Helm or manifest)
  • kubectl configured for your cluster

2. Prerequisites

Before starting, ensure you have:

  • A Kubernetes cluster (1.19+)
  • Gatekeeper installed (install guide)
  • kubectl configured for your cluster

Verify Gatekeeper is running:

kubectl get pods -n gatekeeper-system
kubectl get constrainttemplates
Enter fullscreen mode Exit fullscreen mode

3. How It Works

Gatekeeper uses two resource types:

Resource Purpose
ConstraintTemplate Defines the policy logic (Rego) and the CRD for the constraint
Constraint Instantiates the policy with match rules and enforcement mode

Gatekeeper's controller watches ConstraintTemplates, creates CRDs for them, and then Constraints of that kind can be applied. The order matters: ConstraintTemplate first, then Constraint.


4. Folder Structure

gatekeeper-policies/
├── constraint-templates/
│   └── disallow-default-namespace.yaml
└── constraints/
    └── disallow-default-namespace.yaml
Enter fullscreen mode Exit fullscreen mode

5. Create the ConstraintTemplate

Create constraint-templates/disallow-default-namespace.yaml:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdisallowdefaultnamespace
  annotations:
    metadata.gatekeeper.sh/title: "Disallow Default Namespace"
    metadata.gatekeeper.sh/version: 1.0.0
    description: "Disallows deploying namespaced resources to the default namespace."
spec:
  crd:
    spec:
      names:
        kind: K8sDisallowDefaultNamespace
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdisallowdefaultnamespace

        violation[{"msg": msg}] {
          input.review.object.metadata.namespace == "default"
          msg := "Deploying to the default namespace is not allowed"
        }
Enter fullscreen mode Exit fullscreen mode

Key points:

  • metadata.name: Must be lowercase; Gatekeeper derives the constraint kind from it (K8sDisallowDefaultNamespace).
  • spec.crd.spec.names.kind: Defines the constraint kind created by this template.
  • package: Must match metadata.name (e.g. k8sdisallowdefaultnamespace).
  • violation: Returned when the policy fails. input.review.object is the resource being created or updated.

6. Create the Constraint

Create constraints/disallow-default-namespace.yaml:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowDefaultNamespace
metadata:
  name: disallow-default-namespace
spec:
  enforcementAction: dryrun   # dryrun = audit only; deny = block
  match:
    kinds:
      - apiGroups: [""]
        kinds:
          - Pod
          - Service
          - ConfigMap
          - Secret
          - PersistentVolumeClaim
          - ServiceAccount
      - apiGroups: ["apps"]
        kinds:
          - Deployment
          - ReplicaSet
          - StatefulSet
          - DaemonSet
      - apiGroups: ["extensions", "networking.k8s.io"]
        kinds:
          - Ingress
      - apiGroups: ["batch"]
        kinds:
          - Job
          - CronJob
Enter fullscreen mode Exit fullscreen mode

Key points:

  • kind: Must match the kind defined in the ConstraintTemplate.
  • enforcementAction:
    • dryrun: Log violations only, do not block.
    • deny: Block violating requests.
  • match.kinds: List of API groups and kinds to validate. Extend as needed.

7. Deploy

Apply in this order: template first, then constraint.

# 1. ConstraintTemplate (creates the CRD)
kubectl apply -f constraint-templates/disallow-default-namespace.yaml

# 2. Wait for the CRD to exist (a few seconds)
kubectl get crd k8sdisallowdefaultnamespace.constraints.gatekeeper.sh

# 3. Constraint
kubectl apply -f constraints/disallow-default-namespace.yaml
Enter fullscreen mode Exit fullscreen mode

Verify:

kubectl get constrainttemplate k8sdisallowdefaultnamespace
kubectl get k8sdisallowdefaultnamespace
Enter fullscreen mode Exit fullscreen mode

8. Test

Phase 1: Dryrun (audit only)

With enforcementAction: dryrun, violations are reported but not blocked.

Allowed:

kubectl create deployment nginx --image=nginx -n my-app
Enter fullscreen mode Exit fullscreen mode

Violation (allowed but reported):

kubectl create deployment nginx --image=nginx
# Uses default namespace – creates successfully, but Gatekeeper logs a violation
Enter fullscreen mode Exit fullscreen mode

Check violations:

kubectl get k8sdisallowdefaultnamespace disallow-default-namespace -o yaml
Enter fullscreen mode Exit fullscreen mode

Look at status.violations.

Phase 2: Deny (block)

Switch to blocking mode:

kubectl patch k8sdisallowdefaultnamespace disallow-default-namespace --type=merge \
  -p '{"spec":{"enforcementAction":"deny"}}'
Enter fullscreen mode Exit fullscreen mode

Or update the YAML and re-apply:

spec:
  enforcementAction: deny
Enter fullscreen mode Exit fullscreen mode

Blocked:

kubectl create deployment nginx --image=nginx
Enter fullscreen mode Exit fullscreen mode

Expected error:

Error from server ([disallow-default-namespace] Deploying to the default namespace is not allowed):
admission webhook "validation.gatekeeper.sh" denied the request: ...
Enter fullscreen mode Exit fullscreen mode

Allowed:

kubectl create deployment nginx --image=nginx -n my-app
Enter fullscreen mode Exit fullscreen mode

Quick test commands

  • kubectl run nginx --image=nginx → default namespace → Blocked
  • kubectl run nginx --image=nginx -n prod → prod → Allowed
  • kubectl create configmap foo --from-literal=k=v → default → Blocked
  • kubectl create configmap foo --from-literal=k=v -n staging → staging → Allowed

9. Production Considerations

Gradual rollout

  1. Start with dryrun.
  2. Confirm violations in status.violations or logs.
  3. Fix or exempt workloads.
  4. Switch to deny.

Excluding namespaces

To skip certain namespaces, add excludedNamespaces in the constraint:

spec:
  match:
    kinds: [ ... ]
    excludedNamespaces:
      - kube-system
      - gatekeeper-system
      - argocd
Enter fullscreen mode Exit fullscreen mode

Argo CD

If using Argo CD:

  1. Order: ConstraintTemplates before Constraints (e.g. sync-wave 1 vs 2).
  2. Dry-run: Add argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true to constraints so sync succeeds when the CRD is not yet created.

Hardening the Rego

For DELETE requests, input.review.object may be empty. To avoid errors:

violation[{"msg": msg}] {
  object := input.review.object
  object != null
  object.metadata.namespace == "default"
  msg := "Deploying to the default namespace is not allowed"
}
Enter fullscreen mode Exit fullscreen mode

10. Summary: Copy-Paste

# 1. Apply template
kubectl apply -f constraint-templates/disallow-default-namespace.yaml

# 2. Wait for CRD
kubectl get crd k8sdisallowdefaultnamespace.constraints.gatekeeper.sh

# 3. Apply constraint
kubectl apply -f constraints/disallow-default-namespace.yaml

# 4. Test dryrun (create resources in default, check violations)
kubectl get k8sdisallowdefaultnamespace disallow-default-namespace -o yaml

# 5. Switch to deny
kubectl patch k8sdisallowdefaultnamespace disallow-default-namespace --type=merge \
  -p '{"spec":{"enforcementAction":"deny"}}'

# 6. Test deny (confirm default namespace is blocked)
kubectl create deployment nginx --image=nginx
# Should fail with admission webhook denied
Enter fullscreen mode Exit fullscreen mode

11. Troubleshooting

Issue: K8sDisallowDefaultNamespace.constraints.gatekeeper.sh not found

Solution: The constraint CRD does not exist yet. Gatekeeper creates it when it processes the ConstraintTemplate. Apply the ConstraintTemplate first, wait until kubectl get crd k8sdisallowdefaultnamespace.constraints.gatekeeper.sh succeeds, then apply the Constraint. With Argo CD, use SkipDryRunOnMissingResource=true on the constraint to avoid sync failures during this window.

Issue: ConstraintTemplate stuck or errors

Solution: Run kubectl describe constrainttemplate k8sdisallowdefaultnamespace and check status for Rego errors. Ensure package matches the template name.

Issue: No violations in dryrun

Solution: Confirm Gatekeeper admission webhook is active: kubectl get validatingwebhookconfigurations. Try a resource in the constraint's match.kinds. Inspect status on the constraint for violation details.

Issue: Policy blocks system components

Solution: Add their namespaces to excludedNamespaces (e.g. kube-system, gatekeeper-system).


12. References

Top comments (0)