# Give every namespace a default-deny NetworkPolicy with Kyverno

> A GeneratingPolicy adds default-deny to new namespaces only. Cover the old ones, restore deleted copies and keep one shared namespace open.

Level: Advanced · Topic: Security · About 15 minutes
Try it in the browser: https://k8wiz.com/playground/generate-default-deny-networkpolicies-with-kyverno

The playground is a simulated Kubernetes cluster that runs in the browser. kubectl output and errors follow the real ones.

## The situation

The platform team wants every team namespace isolated: a `default-deny` NetworkPolicy that blocks incoming traffic unless another policy allows it. A colleague wrote the `default-deny` GeneratingPolicy last week, and `team-c`, created since, got its copy.

`team-a` and `team-b` are older than the policy and have nothing. A `client` Pod in `team-b` can call `web` in `team-a` freely. And a policy someone deletes by hand stays deleted.

Make Kyverno cover the namespaces that already exist and put back copies that go missing. One namespace must stay open: `shared`, labelled `network-policy=open`, hosts the `docs` Service that every team calls. The policy is in the editor as `default-deny-gpol.yaml`.

## Objectives

1. See which namespaces have a default-deny policy (`kubectl get networkpolicies -A`)
2. Kyverno generates default-deny in team-a and team-b
3. client in team-b can no longer reach web in team-a (`kubectl exec -n team-b client -- curl -m 3 web.team-a`)
4. Delete default-deny in team-a and see Kyverno put it back
5. shared stays open: team-a still reaches docs.shared
6. team-c keeps its generated policy

## Hints

1. `kubectl get networkpolicies -A` lists only `team-c`. A GeneratingPolicy reacts to the operations in `matchConstraints`, here the CREATE of a Namespace, so namespaces that existed before it never trigger it.
2. `spec.evaluation.generateExisting.enabled: true` makes Kyverno run the policy for matching objects that already exist. Add the exception for `shared` first, or it gets a default-deny too.
3. Exclude by label with a match condition: `matchConditions: [{name: not-open, expression: "object.metadata.?labels[?'network-policy'].orValue('') != 'open'"}]`. The optional access matters, since most namespaces have no such label.
4. `spec.evaluation.synchronize.enabled: true` makes Kyverno own the generated objects: it recreates one that is deleted and reverts one that is edited.
5. Edit `default-deny-gpol.yaml` in the editor: add `generateExisting`, turn `synchronize` on, add the match condition, then `kubectl apply -f default-deny-gpol.yaml`. Kyverno acts within a few seconds.

## Solution

```bash
kubectl get networkpolicies -A
```
```bash
cat <<'EOF' | kubectl apply -f -
apiVersion: policies.kyverno.io/v1
kind: GeneratingPolicy
metadata:
  name: default-deny
spec:
  evaluation:
    generateExisting:
      enabled: true
    synchronize:
      enabled: true
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE"]
      resources: ["namespaces"]
  matchConditions:
  - name: not-open
    expression: "object.metadata.?labels[?'network-policy'].orValue('') != 'open'"
  variables:
  - name: downstream
    expression: >-
      [
        {
          "apiVersion": dyn("networking.k8s.io/v1"),
          "kind": dyn("NetworkPolicy"),
          "metadata": dyn({"name": "default-deny"}),
          "spec": dyn({"podSelector": dyn({}), "policyTypes": dyn(["Ingress"])})
        }
      ]
  generate:
  - expression: generator.Apply(object.metadata.name, variables.downstream)
EOF
```
```bash
sleep 6
```
```bash
kubectl get networkpolicies -A
```
```bash
kubectl delete networkpolicy default-deny -n team-a
```
```bash
sleep 6
```
```bash
kubectl get networkpolicy -n team-a
```

## What it teaches

- A `GeneratingPolicy` creates objects when a trigger matches, with `generator.Apply(namespace, objects)` in CEL. Its `matchConstraints` name the trigger, here the creation of a Namespace.
- `generateExisting` applies the policy to triggers that were there before it. Without it only new ones count.
- `synchronize` keeps the generated objects in line with the policy. Deleting or editing a copy is undone within seconds, and deleting the policy removes the copies.
- A default-deny for Ingress blocks traffic from other namespaces and from inside the namespace. Teams then add their own policies to allow what they need.
- Scope a generating policy with `matchConditions`. Labels on the namespace make the exception visible to anyone who runs `kubectl get ns --show-labels`.
