Skip to content

NetworkPolicy locking down training-pod egress - #42

Merged
saadqbal merged 1 commit into
developfrom
feature/training-pod-network-policy
Apr 21, 2026
Merged

NetworkPolicy locking down training-pod egress#42
saadqbal merged 1 commit into
developfrom
feature/training-pod-network-policy

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a Helm-templated NetworkPolicy that denies ingress and restricts egress on training pods. Selects on the tracebloc.io/workload=training label injected by the jobs-manager in the companion PR: tracebloc/client-runtime#1.

Training pods run untrusted Python code uploaded by external data scientists. This is the first kernel-level control on what that code can reach from inside the pod.

What the policy does

Ingress: denied entirely — nothing should connect TO a training pod.

Egress, allowed:

  • DNS to the cluster DNS service (kube-system / openshift-dns depending on platform)
  • TCP/443 to addresses outside the cluster's pod/service CIDRs — this is what lets the training container still reach Azure Service Bus, App Insights, and the tracebloc backend

Egress, blocked (the whole point):

  • Pod-to-pod in-cluster traffic
  • ClusterIP services — mysql-client, jobs-manager, Kubernetes API server
  • Any port other than 53 (DNS) and 443

Per-platform defaults

PlatformenabledReason
AKStrueRequires Azure NPM (--network-policy azure) or Calico at cluster create. Documented in ci/aks-values.yaml.
EKSfalseDefault AWS VPC CNI does NOT enforce NetworkPolicy. A policy that silently isn't enforced gives a false sense of security. Customers running Calico / Cilium add-ons should override to true.
BMtrueRequires Calico / Cilium / kube-router. Flannel alone does NOT enforce.
OCtrueOVN-Kubernetes enforces by default. Custom DNS selector + OpenShift pod/service CIDRs (10.128.0.0/14 + 172.30.0.0/16).

Design note: the map-merge footgun

Helm merges map values rather than replacing them. If I'd defaulted dnsSelector: {k8s-app: kube-dns} in values.yaml, the OpenShift override would add its label to that map rather than replacing it — producing a broken selector.

So dnsSelector defaults to {} in values.yaml, and the template falls back to {k8s-app: kube-dns} if empty. Each platform's ci/*-values.yaml sets the full intended selector. Documented in-line.

Known limitations (intentional for this PR)

  1. Exfiltration to external attacker.com on port 443 is still possible — this only closes in-cluster paths and non-443 egress. Closing external entirely requires routing all training-pod traffic through jobs-manager (architectural endgame, separate effort).
  2. No L7 filtering — NetworkPolicy is L3/L4.
  3. CIDR-exclusion requires the cluster's pod/service CIDRs to be in the defaults (10.0.0.0/8 / 172.16.0.0/12 / 192.168.0.0/16). Customers with custom CIDRs must override clusterCidrs.
  4. Enforcement depends on CNI — see the platform table above.

Test plan

  • helm lint --strict passes on all 4 platforms
  • helm unittest — 8 new cases covering rendering toggle, podSelector, ingress denial, DNS allow, external HTTPS allow, cluster CIDR blocking, OpenShift selector override. 47/47 total across 6 suites
  • Manual helm template inspection on AKS/EKS/BM/OC — EKS correctly produces no output (disabled), OC uses openshift-dns namespace and the correct DNS daemon-set label
  • Dev-edge rollout: spin a netshoot pod with the training label, verify:
    • DNS resolution ✓
    • curl https://api.tracebloc.io/
    • curl mysql-client:3306 blocked
    • curl kubernetes.default.svc blocked
    • curl jobs-manager pod IP blocked
  • Real experiment runs end-to-end on dev edge with policy active
  • Stg 72h soak across AKS + OC + bare-metal
  • Prod canary on one customer edge

Dependency

Must land after tracebloc/client-runtime#1 has built a new jobs-manager image tag (:dev first). Otherwise the label doesn't exist and the policy selects nothing — not broken, just ineffective.

CI note (not a blocker)

Existing helm-ci.yaml paths: filter triggers on main and openshift branches but not develop. I validated locally (helm lint --strict, helm unittest, per-platform helm template). Worth a follow-up to add develop to the trigger list.

🤖 Generated with Claude Code

Training pods run untrusted ML code uploaded by external data scientists.
This policy selects on the tracebloc.io/workload=training label (injected
by jobs-manager in the companion client-runtime PR) and:
- Denies all ingress (nothing should connect TO a training pod).
- Allows DNS to the cluster DNS service.
- Allows external TCP/443 only; blocks all pod-to-pod, ClusterIP, and
in-cluster pod traffic via ipBlock with cluster-CIDR exclusions.
Training pods can still reach tracebloc backend, Azure Service Bus, and
App Insights (external HTTPS). They can no longer reach mysql-client,
the K8s API server, the jobs-manager pod IP, or other training pods.
Per-platform defaults:
AKS: enabled=true (requires Azure NPM or Calico at cluster create)
EKS: enabled=false (AWS VPC CNI does not enforce NetworkPolicy; safer
to explicitly disable than silently have no effect)
BM: enabled=true (requires Calico / Cilium / kube-router)
OC: enabled=true (OVN-Kubernetes enforces by default; custom DNS
selector and OpenShift pod/service CIDRs)
The dnsSelector default is empty with a template-side fallback to
{k8s-app: kube-dns} to avoid Helm's map-merge semantics surprising
customers who override it (OpenShift's selector would otherwise be
unioned with the default rather than replacing it).
- templates/network-policy-training.yaml: new policy (gated on
networkPolicy.training.enabled)
- values.yaml + values.schema.json: new networkPolicy.training block
- ci/{aks,eks,bm,oc}-values.yaml: per-platform overrides with notes
- tests/network_policy_test.yaml: 8 helm-unittest cases covering
rendering, ingress denial, DNS allow, external HTTPS allow, cluster
CIDR blocking, and the OpenShift selector override
No effect until the companion client-runtime PR lands, which adds the
tracebloc.io/workload=training label to spawned training pods.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@saadqbal
saadqbal merged commit 9db53c7 into developApr 21, 2026
saadqbal added a commit that referenced this pull request Apr 22, 2026
Resolves additive conflicts in client/values.yaml and client/values.schema.json
from the concurrent NetworkPolicy work (PR #42 via e05a4ec). Both top-level
blocks — namespace (PSA labels) and networkPolicy (training egress lockdown) —
are retained so the two defenses can coexist in the chart.
@saadqbal
saadqbal deleted the feature/training-pod-network-policy branch July 9, 2026 11:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@LukasWodka@saadqbal