Skip to content

feat(egress-proxy): training-pod egress lockdown — squid gateway, gated rollout (client-runtime#102) - #247

Merged
saadqbal merged 5 commits into
developfrom
feat/102-egress-gateway
Jun 11, 2026
Merged

feat(egress-proxy): training-pod egress lockdown — squid gateway, gated rollout (client-runtime#102)#247
saadqbal merged 5 commits into
developfrom
feat/102-egress-gateway

Conversation

@saadqbal

@saadqbalsaadqbal commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Part of tracebloc/client-runtime#102 (SECURITY §8.2 — training-pod egress lockdown). Chart side; companion PR: tracebloc/client-runtime (jobs-manager HTTPS_PROXY injection).

Why

A training pod runs untrusted, externally-uploaded ML code, may read the dataset from in-cluster MySQL, and can today open outbound HTTPS to 0.0.0.0/0:443 (training NetworkPolicy rule 2) — a data-exfiltration path that undercuts the "data never leaves your infra" guarantee.

What

Everything ships inert behind flags — no fleet behaviour changes on upgrade until an operator opts in per fleet:

  1. Egress gateway (egressProxy.*, default deployed but unused): squid forward proxy, HTTPS CONNECT only to an FQDN allowlist (backend per env + App Insights; fail-closed), chains to a corporate proxy via cache_peer (mirrors tracebloc.proxyEnv inputs). app=egress-proxy (deliberately outside the training netpol), ClusterIP egress-proxy-service:3128, PSA-restricted, read-only rootfs, pid_filename none, pinned to the multi-arch (amd64+arm64) ubuntu/squid index digest.
  2. Routing (egressProxy.routeWorkloads, default false): jobs-manager gets EGRESS_PROXY_URL and injects HTTPS_PROXY into training pods (companion PR); netpol gains a training→gateway:3128 egress rule.
  3. The lockdown (networkPolicy.training.allowExternalHttps, default true): when explicitly false, rule 2 (0.0.0.0/0:443) is dropped — training pods then reach only DNS, MySQL:3306, requests-proxy:8888, and the gateway:3128. Implemented with dig … true, so an absent key (stored-values replay) keeps the rule: the safe direction.
  4. Docs: SECURITY.md §4.2/§8.1/§8.2 rewritten to "mechanism shipped, gated rollout" with the per-fleet procedure. BACKEND_TOKEN residual stays tracked under §8.1.
  5. CI: new upgrade-e2e job — installs the last published chart on a real k3d cluster, upgrades to this tree via both--reuse-values (nil-guards hold, lockdown must not engage by accident) and --reset-then-reuse-values (the auto-upgrade cronjob's path: new defaults flow in inert, image-refresh annotations survive), then flips the lockdown flags and proves the next auto-upgrade preserves the flip.

Auto-upgrade safety

  • The hourly auto-upgrade (--reset-then-reuse-values) carries the new defaults through and preserves operator overrides — flips are durable.
  • All new keys nil-guarded for manual helm upgrade --reuse-values from pre-1.7.0 stored values (regression-tested in egress_proxy_test.yaml + the e2e gate).
  • helm-unittest pins the training netpol podSelector to exactly tracebloc.io/workload: training, so the lockdown can never catch the auto-upgrade/image-refresh cronjobs.

Rollout (per fleet, each step reversible)

  1. Upgrade to 1.7.0 → gateway deploys, inert.
  2. egressProxy.routeWorkloads=true → verify a training run completes via the gateway.
  3. networkPolicy.training.allowExternalHttps=false → verify G2: in-pod curl https://example.com fails, run still completes. Requires a NetworkPolicy-enforcing CNI (Calico/Cilium/OVN — not flannel/kindnet).

Tests

  • helm unittest: 212/212 (20 suites) — incl. --reuse-values replay, flag gating both ways, podSelector stability.
  • helm lint + full helm template clean; chart 1.6.1 → 1.7.0 (version+appVersion lockstep).
  • Squid smoke-tested in docker under prod constraints (read-only rootfs, uid 13, pinned digest): backend 200 through the allowlist, example.com denied.
  • bash -n + shellcheck --severity=error clean on the new e2e script.

Remaining before fleet rollout

Cluster G2 e2e on a NetworkPolicy-enforcing CNI (tracked on tracebloc/client-runtime#102).

🤖 Generated with Claude Code


Note

High Risk
Changes training egress NetworkPolicy semantics and fleet upgrade paths; misconfiguration or broken nil-guards could break training or accidentally enable lockdown on upgrade.

Overview
Chart 1.6.1 → 1.7.0 adds a gated training-pod egress lockdown (SECURITY §8.2 / client-runtime#102): an optional in-cluster squid egress gateway (egressProxy.*), training NetworkPolicy changes, and jobs-manager wiring for EGRESS_PROXY_URL when routing is enabled.

Egress gateway (default on, inert until opted in): New ConfigMap/Deployment/Service render a fail-closed FQDN allowlist proxy (app=egress-proxy, outside the training netpol). egressProxy.routeWorkloads defaults false; when true, jobs-manager gets EGRESS_PROXY_URL for companion runtime HTTPS_PROXY injection.

Network lockdown: Rule 2 (0.0.0.0/0:443) is wrapped in dig "allowExternalHttps" true so missing keys on --reuse-values keep legacy behavior; explicit false drops direct external HTTPS. When the gateway is enabled, training pods get an explicit egress rule to egress-proxy on the proxy port.

Safety & CI:values.yaml, values.schema.json, helm-unittest suite egress_proxy_test.yaml, and SECURITY.md document the progressive rollout. New upgrade-e2e job runs scripts/tests/e2e-auto-upgrade.sh on k3d (published chart → local tree via --reuse-values and --reset-then-reuse-values, then lockdown flag flip persistence). Shellcheck/bash gates include the new script.

Reviewed by Cursor Bugbot for commit da57914. Bugbot is set up for automated code reviews on this repo. Configure here.

saadqbaland others added 5 commits June 11, 2026 09:29
Step 0 of the training-pod egress lockdown (SECURITY §8.2 /
tracebloc/client-runtime#102). Adds an in-cluster squid forward proxy that
permits HTTPS CONNECT only to an FQDN allowlist (backend + App Insights), so a
later step can drop the training pod's 0.0.0.0/0:443 egress rule. Additive and
inert — no NetworkPolicy change yet, default behaviour unchanged.
- egress-proxy Deployment/Service/ConfigMap: app=egress-proxy (deliberately
outside the training netpol), ClusterIP egress-proxy-service:3128,
PSA-restricted, read-only rootfs, pid_filename none, pinger disabled.
- image pinned to ubuntu/squid 6.6-24.04_beta multi-arch index digest (amd64+arm64).
- values: egressProxy.* + networkPolicy.training.allowExternalHttps (default true),
both nil-guarded for `helm upgrade --reuse-values`; values.schema.json updated.
- helm-unittest egress_proxy_test.yaml incl. a --reuse-values replay case.
- Chart 1.6.1 -> 1.7.0 (version + appVersion lockstep).
Verified: helm lint, full template render, helm unittest 207/207, and a docker
smoke test under read-only rootfs as uid 13 (backend reachable, example.com denied).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…kloads
Step 1 (chart side) of the §8.2 egress lockdown (tracebloc/client-runtime#102):
- egressProxy.routeWorkloads flag (default false) — gateway ships inert.
- jobs-manager gets EGRESS_PROXY_URL=http://egress-proxy-service:3128 only when
egressProxy.enabled + routeWorkloads, so it injects HTTPS_PROXY into training pods.
- network-policy-training: add an egress rule to the gateway (app=egress-proxy)
whenever egressProxy.enabled, so the path is ready before rule 2 is dropped.
- values.schema.json updated; new keys nil-guarded for `helm upgrade --reuse-values`.
- helm-unittest: EGRESS_PROXY_URL gating, the netpol egress-proxy rule, and a
training-podSelector-stability guard (auto-upgrade safety). 210/210.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d allowExternalHttps
Step 3 of the §8.2 lockdown (tracebloc/client-runtime#102) — THE guarantee. The
training NetworkPolicy's external-HTTPS rule (egress to 0.0.0.0/0:443) is now wrapped
in `dig "allowExternalHttps" true .Values.networkPolicy.training`:
- default / absent key (helm upgrade --reuse-values from a pre-1.7.0 release) -> rule
KEPT (no behaviour change, the safe direction).
- explicit allowExternalHttps=false -> rule DROPPED; training pods then reach only DNS,
in-cluster MySQL, the requests-proxy, and the egress gateway, so a pod running
untrusted uploaded code can no longer exfiltrate the dataset to an arbitrary host.
helm-unittest: rule present by default, absent (gateway path retained) when false. 212/212.
Runtime G2 gate is a cluster e2e on a NetworkPolicy-enforcing CNI (see handoff).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ut (#102)
Reflect the 1.7.0 egress gateway in SECURITY.md:
- §4.2: external-443 egress is now conditional on networkPolicy.training.allowExternalHttps;
document in-cluster egress to mysql / requests-proxy / egress gateway.
- §8.2: rewritten from "not scheduled" to "mechanism shipped, gated rollout" with the
per-fleet procedure (deploy -> routeWorkloads -> allowExternalHttps=false + verify G2)
and the BACKEND_TOKEN residual (-> §8.1).
- §8.1: interim mitigation now notes the lockdown hard-blocks direct SB when enabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/tests/e2e-auto-upgrade.sh: installs the last PUBLISHED chart from
gh-pages on a real k3d cluster, then upgrades to the local working tree through
both stored-values replay paths and asserts the fleet-safety contract:
- `--reuse-values` (manual-operator habit): nil-guards hold, the #102 lockdown
does NOT engage by accident.
- `--reset-then-reuse-values` (the auto-upgrade cronjob's flags): new defaults
flow in (egress gateway deploys, inert), image-refresh annotations survive.
- flipping egressProxy.routeWorkloads + allowExternalHttps=false drops the
external-443 rule and points jobs-manager at the gateway.
- the NEXT plain auto-upgrade preserves the operator's flip.
Pods are never waited on (published images need real credentials); the
regression class this guards lives in Helm templating/values semantics.
Wired as the `upgrade-e2e` job in helm-ci.yaml; script added to the shellcheck
enumerations in installer-tests.yaml and standard-checks.yml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@saadqbalsaadqbal self-assigned this Jun 11, 2026
@saadqbal
saadqbal merged commit 2fb5b8f into developJun 11, 2026
36 checks passed
saadqbal added a commit that referenced this pull request Jun 12, 2026
feat(egress-proxy): training-pod egress lockdown — squid gateway, gated rollout (client-runtime#102)
@saadqbal
saadqbal deleted the feat/102-egress-gateway 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.

3 participants

@saadqbal@shujaatTracebloc@LukasWodka