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)
-
kubectlconfigured for your cluster
2. Prerequisites
Before starting, ensure you have:
- A Kubernetes cluster (1.19+)
- Gatekeeper installed (install guide)
-
kubectlconfigured for your cluster
Verify Gatekeeper is running:
kubectl get pods -n gatekeeper-system
kubectl get constrainttemplates
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
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"
}
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 matchmetadata.name(e.g.k8sdisallowdefaultnamespace). -
violation: Returned when the policy fails.input.review.objectis 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
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
Verify:
kubectl get constrainttemplate k8sdisallowdefaultnamespace
kubectl get k8sdisallowdefaultnamespace
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
Violation (allowed but reported):
kubectl create deployment nginx --image=nginx
# Uses default namespace – creates successfully, but Gatekeeper logs a violation
Check violations:
kubectl get k8sdisallowdefaultnamespace disallow-default-namespace -o yaml
Look at status.violations.
Phase 2: Deny (block)
Switch to blocking mode:
kubectl patch k8sdisallowdefaultnamespace disallow-default-namespace --type=merge \
-p '{"spec":{"enforcementAction":"deny"}}'
Or update the YAML and re-apply:
spec:
enforcementAction: deny
Blocked:
kubectl create deployment nginx --image=nginx
Expected error:
Error from server ([disallow-default-namespace] Deploying to the default namespace is not allowed):
admission webhook "validation.gatekeeper.sh" denied the request: ...
Allowed:
kubectl create deployment nginx --image=nginx -n my-app
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
- Start with
dryrun. - Confirm violations in
status.violationsor logs. - Fix or exempt workloads.
- Switch to
deny.
Excluding namespaces
To skip certain namespaces, add excludedNamespaces in the constraint:
spec:
match:
kinds: [ ... ]
excludedNamespaces:
- kube-system
- gatekeeper-system
- argocd
Argo CD
If using Argo CD:
- Order: ConstraintTemplates before Constraints (e.g. sync-wave 1 vs 2).
-
Dry-run: Add
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=trueto 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"
}
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
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
- Gatekeeper Installation: https://open-policy-agent.github.io/gatekeeper/website/docs/install/
- Gatekeeper Documentation: https://open-policy-agent.github.io/gatekeeper/website/docs/
Top comments (0)