---
id: w7/m4/t001
title: Except 169.254.0.0/16 (link-local incl. cloud metadata) in the tenant egress policy; document the threat
worker: worker7
status: done
estimate: 30m
depends_on: []
---

## Objective

Close the cloud-metadata SSRF path in the m1 tenant egress policy: a tenant pod can currently reach `169.254.169.254` (the Hetzner instance-metadata service, which serves hostname/instance-id/network config and cloud-init user-data) because link-local `169.254.0.0/16` is not in the egress `Except` list. Add it, and write the threat up in the tenant-isolation ADR so the choice is recorded, not silent.

## Context

- `lego/operator/internal/controller/app_controller.go` `reconcileNetworkPolicy` — the public-internet egress rule (~L1123) is `IPBlock{CIDR: "0.0.0.0/0", Except: ["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16","100.64.0.0/10"]}`. `169.254.0.0/16` is missing.
- `docs/ADR022-tenant-isolation.md` — the ADR that records the egress model (threat model + allow list).
- Hetzner Cloud metadata: `http://169.254.169.254/hetzner/v1/metadata` (link-local; a classic SSRF → credential/user-data theft target).

## Steps

1. Add `"169.254.0.0/16"` to the `Except` slice on the public-internet egress `IPBlock`.
2. Update the inline comment on that rule to name link-local / cloud-metadata as the reason.
3. Add a short "Cloud-metadata / link-local egress" subsection to `docs/ADR022-tenant-isolation.md` (why the endpoint is dangerous, what it exposes on Hetzner, that the policy now excepts the whole link-local range).
4. `make manifests generate` if any marker/RBAC changed (policy body is Go, so likely a no-op — confirm no codegen drift).

## Files

- `lego/operator/internal/controller/app_controller.go`
- `docs/ADR022-tenant-isolation.md`

## Acceptance criteria

- [ ] The generated per-App NetworkPolicy's public-internet egress rule excepts `169.254.0.0/16` alongside the four RFC1918/CGNAT ranges.
- [ ] `docs/ADR022-tenant-isolation.md` documents the metadata/link-local threat and the mitigation.
- [ ] `cd lego/operator && go build ./...` passes; no codegen drift.

## Out of scope

- The node-public-IP path (kubelet `:10250`, nodePorts) — that's t002.
- The verification probes — that's t003.
