Skip to content

ADR-0007: Operator-generated Cilium perimeter, gateway-only egress

Status: Accepted — 001-walking-skeleton (2026-06); deepened 003-governance-depth (2026-07) Principles served: I, II Source: specs/001-walking-skeleton/research.md D-7; specs/003-governance-depth/research.md G-2; specs/003-governance-depth/spec.md Clarifications (2026-07-02)

Context

An app's network reach must be exactly what its governed contract implies, with no hand-written per-app policy objects and no way for a generated app to reach a data source directly and bypass the data gateway. Sandbox must have no network route to production. Network policy is the contract-not-code enforcement point for network scope, so the perimeter has to be generated by the platform, not authored.

Decision

The Tend operator renders CiliumNetworkPolicy objects per app namespace at reconcile time: a tend-default-deny floor plus a tend-allow-platform-gateways policy permitting exactly

  • egress to the data gateway, the inference gateway, and the identity provider's token endpoint in tend-system — three destinations, not two; an app exchanges its platform-provisioned client credentials there for the token it presents to the inference gateway.

The floor itself carries the rest: intra-namespace traffic, node kubelet probes, and kube-dns egress tightened with an L7 DNS rule (allow-listed cluster suffixes, closing the DNS-tunnel side channel). A third policy, tend-allow-edge-ingress, admits the platform ingress controller to the app's serving port; it arrived with the ingress controller and is not part of the gateways policy.

The perimeter shape is static: scope changes alter gateway decisions, never the perimeter — one shape for every app namespace, sandbox and production alike. No free-form egress is declarable; any egress declaration beyond the platform gateway set is refused. Probe tests verify the negatives: app→other-app-namespace, app→datasource-direct, app→off-cluster, and sandbox→production must all fail; app→gateways must succeed.

Alternatives considered

  • Plain Kubernetes NetworkPolicy — rejected: less expressive for identity-aware egress later.
  • Shared cluster with namespace-only separation — rejected: insufficient for a strict CISO (design risk 4).
  • Per-scope rendered egress policies — rejected by the 003 spec clarifications: no free-form egress exists; the gateway is the enforcement point.
  • CiliumClusterwideNetworkPolicy for the perimeter — rejected: per-namespace objects keep the ownership/reconcile semantics 001 already has. Note what this does not mean: a dedicated clusterwide sandbox→production deny was recorded as a hardening follow-up and has not shipped. Sandbox→production isolation holds today, and is proven by a live probe, on the strength of the per-namespace floor alone — there is no second layer behind it.

Consequences

The data gateway becomes unbypassable, which is what makes ADR-0008's "sole data path" true. Because the perimeter never varies per app, scope changes take effect at the gateway immediately with no policy churn. The live perimeter gate caught two real defects: Cilium DNS matchPattern is single-label (requiring a tiered allow-list), and the DNS proxy must reject with NXDOMAIN or glibc resolvers stall on search-domain expansions. These probes run in CI on pull requests that touch the paths the policy workflow filters on — not on every pull request. The filter has been wrong three times, and twice a policy change merged green having run no policy job at all.