# Network Policies

Citadel implements zero-trust networking through layered CiliumNetworkPolicies plus operator-generated per-namespace policies.

## Foundation: Default-Deny

All traffic is blocked by default. Everything must be explicitly allowed. This is enforced by `policies/01-default-deny.yaml` (cluster-wide) and by the namespace infrastructure operator (per-namespace).

## Static Policy Files

8 policy files in `policies/`, applied in alphabetical order via `./citadel-ctl network policies`:

### `01-default-deny.yaml` — Cluster-Wide Foundation

4 CiliumClusterwideNetworkPolicies:

| Policy | Purpose |
|--------|---------|
| `default-deny-all` | Block all ingress and egress for all pods |
| `allow-dns` | Allow egress to CoreDNS (kube-system, port 53 UDP/TCP) |
| `allow-apiserver` | Allow egress to kube-apiserver entity (port 6443) |
| `allow-health-checks` | Allow ingress from host + remote-node (kubelet probes) |

### `02-system-policies.yaml` — kube-system Namespace

| Policy | Purpose |
|--------|---------|
| `cilium-agent-policy` | Agent ↔ agent, Hubble relay (4244), Prometheus scraping (9962, 9965) |
| `hubble-relay-policy` | Hubble UI → relay (4245), host → relay |
| `hubble-ui-policy` | World/host → UI (8081), UI → relay |
| `coredns-policy` | Cluster → DNS (53), DNS → world (upstream resolvers) |

### `03-demo-app-policies.yaml` — L7 HTTP Filtering

Layer 7 policies for the demo app:

| Policy | Rules |
|--------|-------|
| `frontend-l7-policy` | World → frontend (GET, HEAD); frontend → backend (:5678, GET/POST to `/api/.*`) |
| `backend-l7-policy` | Frontend → backend (CRUD on `/api/v1/*`, health/ready); backend → database (:6379) |
| `grpc-service-policy` | gRPC client → service (:9090, POST methods on service paths) |

### `04-l7-demo-policies.yaml` — HTTP Method+Path Filtering

Additional L7 rules with HTTP method and path constraints (same pattern as 03).

### `05-fqdn-egress-policies.yaml` — FQDN-Based Egress

| Policy | Allowed Destinations |
|--------|---------------------|
| `allow-dns-for-fqdn` | DNS resolution (prerequisite for FQDN matching) |
| `backend-fqdn-egress` | api.github.com, api.stripe.com, *.amazonaws.com, *.storage.googleapis.com (:443) |
| `compute-fqdn-egress` | *.s3.amazonaws.com, pypi.org, files.pythonhosted.org (:80, :443) |
| `database-no-egress` | No egress (fully isolated) |
| `internal-only-egress` | *.svc.cluster.local, *.internal.example.com (:80, :443) |

### `06-pod-disruption-budgets.yaml` — PDBs

| PDB | Namespace | Selector | Constraint |
|-----|-----------|----------|------------|
| `frontend-pdb` | demo | tier=frontend | minAvailable: 2 |
| `backend-pdb` | demo | tier=backend | maxUnavailable: 1 |
| `database-pdb` | demo | tier=database | minAvailable: 1 |
| `compute-pdb` | demo | tier=compute | maxUnavailable: 50% |
| `system-critical-pdb` | kube-system | component=critical | minAvailable: 1 |

### `07-resource-quotas.yaml` — Quotas + Limits + Priority Classes

**demo-quota** (ResourceQuota in demo namespace):
- CPU: 4 requests / 8 limits
- Memory: 8Gi requests / 16Gi limits
- Pods: 20, PVCs: 10, Storage: 100Gi, Services: 10

**demo-limits** (LimitRange in demo namespace):
- Default: 500m CPU, 512Mi memory
- Requests: 100m CPU, 128Mi memory
- Max: 4 CPU, 8Gi memory

**Priority classes:** `critical-workload` (1M), `high-priority-compute` (100K), `best-effort` (1K).

### `08-pod-security-standards.yaml` — PSS Enforcement

| Namespace | Enforce | Audit | Warn |
|-----------|---------|-------|------|
| demo | restricted | restricted | restricted |
| production | restricted | restricted | restricted |
| staging | baseline | restricted | restricted |

Also includes:
- Restricted PSS pod template (runAsNonRoot, readOnlyRootFilesystem, drop ALL capabilities, seccompProfile RuntimeDefault)
- `restrict-metadata-access` CiliumNetworkPolicy blocking egress to 169.254.169.254 (cloud metadata endpoint)

## Operator-Generated Policies

The namespace infrastructure operator creates additional per-namespace CiliumNetworkPolicies when a `NamespaceInfrastructure` resource exists:

| Policy | Name Pattern | Purpose |
|--------|-------------|---------|
| Default-deny | `{namespace}-default-deny` | Block all + allow DNS + API server |
| Tenant-allow | `{namespace}-tenant-allow` | Custom ingress/egress + kube-system/monitoring + intra-namespace |
| Peer policies | `{namespace}-peer-{peer}` | Bidirectional traffic with allowed peer namespaces |

These are labeled with `app.kubernetes.io/managed-by: citadel-operator` and cleaned up when the NamespaceInfrastructure is deleted.

## Policy Interaction

Static policies (from `policies/`) and operator-generated policies coexist:

- Cluster-wide policies in `01-default-deny.yaml` apply everywhere
- System policies in `02-system-policies.yaml` ensure core services work
- Operator-generated policies add namespace-specific rules on top
- Application-specific policies (03-05) are for the demo app only

For production namespaces, use the namespace infrastructure operator instead of static policy files.

## Verifying Policies

```bash
# List all CiliumNetworkPolicies
kubectl get ciliumnetworkpolicies -A

# List cluster-wide policies
kubectl get ciliumclusterwidenetworkpolicies

# Check for dropped traffic
hubble observe --verdict DROPPED

# Filter by namespace
hubble observe -n team-alpha --verdict DROPPED

# Test connectivity
kubectl exec -n team-alpha test-pod -- curl -s http://backend:8080/health
```
