Skip to content

feat(chart,k8s): add optional NetworkPolicy for pod-level isolation (direct egress)#1400

Open
antigenius0910 wants to merge 7 commits into
openabdev:mainfrom
antigenius0910:feature/issue-1394-networkpolicy
Open

feat(chart,k8s): add optional NetworkPolicy for pod-level isolation (direct egress)#1400
antigenius0910 wants to merge 7 commits into
openabdev:mainfrom
antigenius0910:feature/issue-1394-networkpolicy

Conversation

@antigenius0910

@antigenius0910 antigenius0910 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1394. Design was discussed in-issue (see the reply to @pahud and RFC diff summary) and on Discord (thread).

Rendered resources (when enabled: true)

Resource Ingress Egress
<release>-<agent> deny-all (empty ingress: []) + ingress.extraRules DNS + IPv4/IPv6 HTTPS with metadata exclusions + agent→gateway TCP 8080 (if gateway deployed) + egress.extraRules
<release>-<agent>-gateway (if agents.<name>.gateway.enabled AND gateway.deploy != false) TCP 8080 from any + ingress.extraRules same egress rule set as agent

File changes

File Change Purpose
charts/openab/values.yaml added networkPolicy: block + gateway.env / gateway.envFrom Config surface with CNI-enforcement prerequisite + defense-in-depth caveats
charts/openab/templates/networkpolicy.yaml new Per-agent + per-gateway rendering; DNS scoping, IPv4/IPv6 HTTPS, configurable metadata exclusions
charts/openab/templates/gateway.yaml added env / envFrom rendering General-purpose env injection into gateway pod
charts/openab/tests/networkpolicy_test.yaml new 12 helm-unittest cases (see below)
charts/openab/README.md added rows under Release naming + Agent values Doc surface for every new value
k8s/networkpolicy.yaml new Standalone raw manifest matching chart's default
Cargo.toml, crates/openab-core/Cargo.toml, crates/openab-gateway/Cargo.toml added system-proxy to reqwest features Reqwest-based HTTP calls now honor HTTPS_PROXY / HTTP_PROXY / NO_PROXY env vars if operators set them
README.md, README.zh-TW.md added kubectl apply -f k8s/networkpolicy.yaml step + table row Raw-install path visibility

Test plan

  • helm unittest -f 'tests/networkpolicy_test.yaml' . — 12/12 pass
  • helm unittest . — full chart suite 52/52 pass, no regression in other test files
  • helm lint . — clean
  • cargo check --workspace — clean rebuild across all three reqwest sites
  • cargo test -p openab-gateway --lib — 262/262 pass (round-3 verification, unchanged)

Test cases (from tests/networkpolicy_test.yaml):

  1. enabled: false (default) → no resource rendered
  2. enabled: true, no gateway → single agent policy, DNS scoped to kube-system + IPv4/IPv6 HTTPS
  3. enabled: true, gateway deployed → 2 policies (agent + gateway), agent egress authorizes TCP 8080 to gateway pod
  4. gateway.enabled=true, gateway.deploy=false → single agent policy, no orphan gateway NP, no agent→gateway rule
  5. allowHttp: true → HTTP egress with IPv4/IPv6 present
  6. allowIpv6: false → IPv6 ipBlock omitted (IPv4-only cluster)
  7. dnsNamespace: "" → falls back to legacy any-namespace DNS selector
  8. ingress.extraRules populated → merged verbatim
  9. allowDns: false, allowHttps: false → locked down to only extraRules
  10. Custom metadataExclusions.{ipv4,ipv6} → renders as expected except entries
  11. gateway.env → rendered on gateway container
  12. gateway.envFrom → rendered on gateway container

Out of scope (follow-ups)

  • Proxy-aware egress ("networking refactor") — deferred to a separate RFC. Scope: replace/fork Serenity for CONNECT-aware WSS, replace tokio_tungstenite direct calls (Slack Socket Mode, external gateway, Feishu WebSocket), AgentCore proxy configuration, AWS SDK proxy configuration, and end-to-end integration test with a local Squid/Envoy. See docs/openshell.md for existing project stance.
  • Per-agent networkPolicy override — reasonable follow-up per reviewer, deferred to keep this PR bounded.
  • Gateway ingress source selector — RFC / discussion landed on port-8080-from-any (Ingress controller in front handles auth); agreed defense-in-depth follow-up.
  • CNI-specific extensions (CiliumNetworkPolicy, Calico GlobalNetworkPolicy) — stay on vanilla networking.k8s.io/v1 for portability.
  • Command-execution restriction / tool allowlist (@pahud's Discord suggestion) — ACP / agent tool-allowlist layer, separate RFC.

Backwards compatibility

Fully backwards-compatible. Master switch defaults to off; nothing renders until an operator flips it. system-proxy feature addition on reqwest is behaviourally inert unless operators set proxy env vars.

Adds an opt-in NetworkPolicy for openab agent and gateway pods. Default
networkPolicy.enabled=false — no resource is rendered, existing releases
are unaffected. Mirrors the opt-in pattern of openabdev#901 / openabdev#911 / openabdev#914.

Two supported egress topologies via networkPolicy.egress.mode:

- direct (default): DNS + HTTPS to 0.0.0.0/0 (except 169.254.169.254/32,
  the cloud metadata service) — hobbyist / dev-cluster path.
- proxy: DNS + egress restricted to a designated proxy Service via
  networkPolicy.egress.proxy.{namespace, podSelector, ports} — enterprise
  path per @pahud's Discord feedback citing OpenSHELL prior art. Chart
  ships no proxy; users BYO (OpenSHELL, Squid, Envoy, mitmproxy, etc.),
  keeping upstream maintenance surface unchanged.

Rendered resources when enabled:
- <release>-<agent>: deny-all ingress, egress per mode.
- <release>-<agent>-gateway: adds port-8080 ingress when
  agents.<name>.gateway.enabled=true.

Selector labels reuse openab.selectorLabels so drift with deployment.yaml
and gateway.yaml is structurally impossible.

Also adds k8s/networkpolicy.yaml mirroring the chart's direct-mode
default (deny-all ingress, DNS + HTTPS egress) so the raw manifests set
cited by the driving AppSec finding is covered too.

Tests: 8 helm-unittest cases covering both modes, gateway split,
extraRules, and locked-down egress. Full chart suite (48 tests) still
passes; helm lint clean.

Closes openabdev#1394
@chaodu-agent

This comment has been minimized.

@chaodu-agent

This comment has been minimized.

@chaodu-agent

This comment has been minimized.

… gateway path, DNS scoping, IPv6

Addresses all 9 findings raised on the initial commit. Details map 1:1
to the chaodu-agent review on PR openabdev#1400.

Critical (fail-closed):

- F1 Validate networkPolicy.egress.mode + required proxy fields at
  render time. mode must be exactly "direct" or "proxy" (typos like
  "proxxy" no longer silently fall back to direct). mode=proxy now
  requires non-empty proxy.podSelector AND non-empty proxy.ports,
  otherwise an empty selector authorizes every pod on every port
  (previous fail-open behaviour).

- F2 Add agent→gateway egress rule on TCP 8080 when a chart-managed
  gateway is deployed (agents.<name>.gateway.enabled=true AND
  gateway.deploy is not "false"). Without this, enabling the policy
  breaks the ws://<release>-<agent>-gateway:8080/ws data path.

Important:

- F3 Match templates/gateway.yaml condition for gateway.deploy — the
  previous `default true | toString` treated an explicit false as empty
  and rendered an orphan gateway NetworkPolicy with no Deployment.

- F4 Scope default DNS destination to the trusted kube-system namespace
  (kubernetes.io/metadata.name=kube-system) instead of any namespace.
  Prevents a co-tenant labelling a pod k8s-app=kube-dns from becoming
  an approved port-53 destination. New egress.dnsNamespace value
  (default "kube-system", empty = legacy any-namespace behaviour).
  Same tightening applied to k8s/networkpolicy.yaml.

- F5 Document the proxy-mode contract explicitly in values.yaml and
  charts/openab/README.md: NetworkPolicy enforces the destination
  allowlist only. Operators must configure HTTPS_PROXY/HTTP_PROXY via
  agents.<name>.env AND add them to inherit_env in configToml so ACP
  children inherit the settings. Chart does not auto-inject.

- F6 Add opt-in `kubectl apply -f k8s/networkpolicy.yaml` step and
  manifest-table row to README.md and README.zh-TW.md so users
  following the raw-install path aren't left unisolated.

- F7 Quote the proxy namespace label value so all-numeric namespace
  names (e.g. "1234") render as strings, not YAML integers.

- F8 Add optional IPv6 support (allowIpv6, default true). Adds ::/0
  alongside 0.0.0.0/0 in HTTPS/HTTP rules, excluding fd00:ec2::254/128
  (AWS IMDS IPv6 pattern). Non-AWS IPv6 metadata addresses vary; docs
  point users at extraRules for cluster-specific exclusions. Same
  addition in k8s/networkpolicy.yaml. Toggle off for IPv4-only clusters.

- F9 Update allowHttp documentation in values.yaml and chart README to
  explicitly call out HTTP configUrl as an affected use case; recommend
  HTTPS configUrl by default.

Tests: 15 helm-unittest cases (up from 8). New coverage:
- 3 negative tests for F1 validation (invalid mode, empty podSelector,
  empty ports)
- agent→gateway egress path assertion (F2)
- gateway.enabled=true+deploy=false → no gateway NP, no agent egress (F3)
- DNS namespace scoping default + empty-string escape hatch (F4)
- numeric proxy namespace renders as string (F7)
- IPv6 rendered by default, omitted when allowIpv6=false (F8)

Full chart suite: 55/55 pass, no regression. helm lint clean.

Refs openabdev#1394
Refs openabdev#1400 review by chaodu-agent
@antigenius0910

Copy link
Copy Markdown
Contributor Author

Thanks @chaodu-agent — pushed d1a7ff0 addressing all 9 findings. Kept it as a follow-up commit (not amend) so review history stays legible.

Finding-by-finding response:

# Sev Fix Verified
F1 Fail-open proxy validation 🔴 Template now fails if egress.mode isn't exactly "direct" or "proxy", and in proxy mode requires non-empty proxy.podSelector.matchLabels/matchExpressions AND non-empty proxy.ports. 3 new negative tests: invalid mode (proxxy), empty podSelector, empty ports. All three assert failedTemplate with the exact error message.
F2 Agent → gateway 8080 blocked 🔴 When a chart-managed gateway is deployed (gateway.enabled=true AND gateway.deploy != "false"), the agent NetworkPolicy now includes a TCP 8080 egress rule targeting the gateway's openab.selectorLabels. Applied in both direct and proxy modes. New assertion in the gateway-enabled test verifies the agent egress contains a to: [{ podSelector: { matchLabels: {…component: kiro-gateway} } }] rule on TCP 8080. Also covered by the proxy-mode-with-gateway test.
F3 Orphan gateway NP on deploy=false 🟡 Replaced default true | toString with plain toString — matches templates/gateway.yaml:2. Refactored via a $gwDeployed local so the agent egress rule and the gateway NP share the same condition. New regression test: gateway.enabled=true, gateway.deploy=falsehasDocuments: count: 1 (agent only) AND notContains the agent→gateway egress rule.
F4 DNS reachable in any namespace 🟡 Default DNS namespaceSelector now scoped to {kubernetes.io/metadata.name: kube-system}. New egress.dnsNamespace value (default "kube-system") makes this configurable; empty string reverts to the legacy any-namespace selector for non-standard DNS setups. Same tightening applied to k8s/networkpolicy.yaml. New test asserts default renders with kubernetes.io/metadata.name: kube-system; second test asserts dnsNamespace: "" reverts to namespaceSelector: {}.
F5 Proxy contract undefined 🟡 Documentation-only in this PR (no chart-managed env-var injection — would expand upstream maintenance surface, and the community-review pattern here favours BYO). New "⚠️ Proxy-mode CONTRACT" block in values.yaml and the chart README explicitly states: NetworkPolicy enforces the destination allowlist only; operator must set HTTPS_PROXY/HTTP_PROXY/NO_PROXY via agents.<name>.env AND list them in inherit_env in configToml so ACP children inherit. Full example config block included. Documented — no automated test for docs.
F6 Raw-install docs skip NP 🟡 Added opt-in kubectl apply -f k8s/networkpolicy.yaml step and a manifest-table row in both README.md and README.zh-TW.md. Documented as opt-in + CNI-enforcement requirement (Calico / Cilium etc.) so users on stock KIND / GKE-classic-mode don't get confused. Rendered manually.
F7 Numeric namespace unquoted 🟡 Added | quote on the kubernetes.io/metadata.name label value for both the proxy namespaceSelector and (as a preventive measure) the DNS namespaceSelector. New test with namespace: "1234" asserts the label value is the string "1234".
F8 IPv4-only egress 🟡 New egress.allowIpv6 value (default true). When true, ::/0 is added alongside 0.0.0.0/0 in both HTTPS and HTTP rules, with fd00:ec2::254/128 excluded (AWS IMDS IPv6 pattern). Non-AWS clouds use different IPv6 metadata addresses — docs point users at extraRules for tighter exclusions. IPv4-only clusters can flip allowIpv6: false for explicit denial. Same additions in k8s/networkpolicy.yaml. New test asserts IPv6 ipBlock present by default; second test asserts it's omitted when allowIpv6: false.
F9 allowHttp ignores configUrl 🟡 Updated allowHttp comment in values.yaml and the chart README row to explicitly list agents.<name>.configUrl (HTTP variant) as an affected use case, alongside HTTP lifecycle hooks and STT base URLs. Recommendation: prefer HTTPS configUrl wherever possible. Documented — no automated test for docs.

Non-blocking dispositions

  • Per-agent networkPolicy override — agreed as a reasonable follow-up. Deferred to keep this PR bounded.
  • Gateway ingress source selector — the RFC / discussion landed on port-8080-from-any because the Ingress controller in front handles auth; agreed this is a defense-in-depth follow-up, not a blocker.
  • Raw app: openab vs chart labels — deliberately preserved to match k8s/deployment.yaml. Won't change without a coordinated raw-install label refactor.

Test summary

  • helm-unittest: 15/15 pass in the NetworkPolicy suite (was 8), 55/55 pass in the full chart suite (was 48). No regressions.
  • helm lint: clean.
  • Smoke renders manually verified for: default off, direct/direct+gateway/proxy/proxy+gateway/direct+http/direct+ipv6-off/dns-any-namespace/numeric-proxy-namespace/deploy-false-no-orphan.

Ready for another look. If any of the deferred items above should land in this PR instead of follow-up, happy to add.

@chaodu-agent

This comment has been minimized.

…env, configurable metadata exclusions

Addresses both round-2 findings from chaodu-agent on the follow-up commit
(d1a7ff0). Kept as a third commit rather than amend to preserve review
history.

Critical (round-2 F1) — Complete the proxy contract for the gateway:

The previous commit locked the chart-managed gateway to proxy-only egress
when networkPolicy.egress.mode=proxy, but the gateway Deployment had no
mechanism to receive proxy env vars — its env list was a fixed
platform-specific block only. Effect: proxy-mode gateway pods could reach
DNS + the proxy pod but nothing else, so all outbound platform API calls
(Telegram replies, Teams OAuth token exchange, Feishu/WeCom callbacks)
silently failed.

Fix: add first-class `agents.<name>.gateway.env` (map) and
`agents.<name>.gateway.envFrom` (list) fields to values.yaml and render
them into templates/gateway.yaml, right after the fixed platform env
block. This lets operators inject HTTPS_PROXY / HTTP_PROXY / NO_PROXY
into the gateway pod the same way they do for the agent.

Updated the "Proxy-mode CONTRACT" doc block in values.yaml (and the
matching chart README row) to spell out that operators must configure
BOTH the agent AND the gateway to route through the proxy, with a
concrete example config for each side.

Important (round-2 F2) — Configurable IPv4/IPv6 metadata exclusions:

The previous docs suggested users could tighten IPv6 metadata protection
via `egress.extraRules`. That is incorrect — Kubernetes NetworkPolicy
allow rules combine additively, so an extraRule can only broaden what is
allowed, never subtract from an existing `::/0` allow. Non-AWS clouds
with different IPv6 IMDS addresses would have been silently unprotected.

Fix: new `networkPolicy.egress.metadataExclusions.{ipv4,ipv6}` value.
Defaults preserve the AWS pattern (169.254.169.254/32 for IPv4,
fd00:ec2::254/128 for IPv6). Users on other clouds edit the list
directly (e.g. add IBM Cloud's 161.26.0.0/16 to ipv4, or override
ipv6 for GCP/Azure). The template consumes both lists via
`toYaml . | nindent`, so the exclusion set can grow as needed.

Updated values.yaml comment block to enumerate common cloud IMDS
addresses. Updated chart README rows for allowHttps/allowHttp/allowIpv6/
extraRules to point at metadataExclusions and clarify the additive
semantics constraint.

Tests: 18 helm-unittest cases in NetworkPolicy suite (up from 15). New:
- gateway.env renders HTTPS_PROXY/HTTP_PROXY/NO_PROXY into the gateway
  Deployment container (round-2 F1)
- gateway.envFrom renders on the gateway Deployment (round-2 F1)
- custom metadataExclusions.ipv4/ipv6 render as expected `except` entries
  in the direct-mode HTTPS rule (round-2 F2)

Full chart suite: 58/58 pass. helm lint clean.

Refs openabdev#1394
Refs openabdev#1400 round-2 review by chaodu-agent
@antigenius0910

Copy link
Copy Markdown
Contributor Author

Thanks @chaodu-agent — round-2 fixes pushed in f323386. Kept as a third commit rather than amend so review history stays clean.

Finding-by-finding response

🔴 F1 (round-2) — Gateway proxy contract incomplete

Verified. Local render with mode=proxy + agents.kiro.gateway.enabled=true + agents.kiro.env.HTTPS_PROXY=… confirms: agent Deployment gets HTTPS_PROXY, gateway Deployment gets only RUST_LOG and platform-specific vars — no way for the operator to inject proxy env into the gateway. Proxy-mode gateway would silently lose Telegram / Teams / Feishu / WeCom callouts.

Fix: added first-class agents.<name>.gateway.env (map) and agents.<name>.gateway.envFrom (list) fields — mirrors the existing agent env pattern. Rendered into templates/gateway.yaml right after the fixed platform env block.

Updated the Proxy-mode CONTRACT doc block (values.yaml + chart README row) to spell out the two-part configuration — one for the agent side, one for the gateway side — with a concrete example each.

Coverage:

  • New test injects gateway.env into the gateway Deployment — asserts HTTPS_PROXY, HTTP_PROXY, NO_PROXY all present on the gateway container's env list at documentIndex: 0.
  • New test renders gateway.envFrom on the gateway Deployment — asserts spec.template.spec.containers[0].envFrom equals [{secretRef: {name: proxy-credentials}}].

Manual smoke:

$ helm template t . --set networkPolicy.enabled=true --set networkPolicy.egress.mode=proxy \
    --set-string networkPolicy.egress.proxy.podSelector.matchLabels.app=openshell \
    --set 'networkPolicy.egress.proxy.ports[0].port=3128' \
    --set agents.kiro.gateway.enabled=true \
    --set-string agents.kiro.gateway.env.HTTPS_PROXY=http://openshell:3128 \
    --set-string 'agents.kiro.gateway.envFrom[0].secretRef.name=proxy-creds' \
    ...

→ gateway container env now contains RUST_LOG, HTTPS_PROXY, and gateway envFrom → secretRef: proxy-creds.

🟡 F2 (round-2) — Additive-only NetworkPolicy semantics vs. IPv6 tightening guidance

Verified. Kubernetes NetworkPolicy allow rules combine additively (union of matching allows). An extraRules entry with a tighter ::/0 except <cidr> cannot revoke an address that the base rule's ::/0 except fd00:ec2::254/128 still permits — the union effectively covers everything. My previous README/values guidance was wrong.

Fix: replaced hardcoded metadata CIDR exclusions with a configurable list:

networkPolicy:
  egress:
    metadataExclusions:
      ipv4:
        - 169.254.169.254/32     # AWS/GCP/Azure IMDS default
      ipv6:
        - fd00:ec2::254/128      # AWS IPv6 IMDS default

Both lists are consumed by the template via toYaml . | nindent, so users can grow the exclusion set for their cloud (IBM 161.26.0.0/16, GCP/Azure IPv6 patterns, etc.) by editing the value — the operation Kubernetes semantics actually supports.

Updated values.yaml comment block to enumerate common cloud IMDS addresses. Chart README rows for allowHttps, allowHttp, allowIpv6, and extraRules now explicitly say "NetworkPolicy allow rules combine additively — you cannot subtract via extraRules; edit metadataExclusions directly." Reference to the K8s docs section on additive semantics baked into the comment.

Coverage: new test uses configurable metadataExclusions for both IPv4 and IPv6 in direct-mode HTTPS rule sets custom lists on both v4 and v6, asserts they render as the expected except entries in the HTTPS ipBlock.

Test summary

  • helm-unittest: 18/18 pass in the NetworkPolicy suite (was 15), 58/58 pass in the full chart suite (was 55). No regressions.
  • helm lint: clean.
  • Manual smoke renders verified for: gateway.env alone, gateway.envFrom alone, gateway.env + gateway.envFrom together, custom v4-only metadataExclusions, custom v6-only, custom both, default (backwards-compat with round-1).

Backwards compatibility

Both fixes are additive and default-preserving:

  • gateway.env / gateway.envFrom default to {} / [] — existing gateway deployments render identically to before.
  • metadataExclusions.{ipv4,ipv6} defaults are exactly the values that were previously hardcoded — no behavioural change unless explicitly overridden.

Ready for another look. If a proper "first-class proxy URL injection into both workloads" (your alternative suggestion) is preferred over the manual env-var pattern, happy to iterate — but that would tie the NetworkPolicy config to the workload templates more tightly, and I wanted to keep the smaller, additive change to keep upstream maintenance surface bounded.

@chaodu-agent

This comment has been minimized.

…t system-proxy + CNI/IMDS caveats

Addresses all three round-3 findings from chaodu-agent on commit f323386.
Kept as a fourth commit rather than amend to preserve review history.

Critical (round-3 F1) — Enable reqwest system-proxy in openab-gateway:

The chart correctly renders HTTPS_PROXY / HTTP_PROXY / NO_PROXY into the
gateway container env (round-2 fix), but the gateway binary was compiled
with `reqwest = { default-features = false, features = ["rustls-tls",
"json"] }`. In reqwest 0.12, `system-proxy` is a *default* feature that
turns on env-var auto-detection (HTTP_PROXY / HTTPS_PROXY / NO_PROXY /
ALL_PROXY). With `default-features = false` and `system-proxy` not
re-enabled, reqwest silently ignores those env vars.

Effect: proxy-mode gateway pods were reachable to the proxy Service and
DNS, but every outbound platform-API call (Telegram replies, Teams OAuth
token exchange, Feishu/WeCom/Google Chat/LINE callouts, WebSocket
downstream setup) tried direct egress and hit the NetworkPolicy deny.

Fix: add `"system-proxy"` to reqwest's feature list in
crates/openab-gateway/Cargo.toml. Verified with `cargo check -p
openab-gateway` (clean build) and `cargo test -p openab-gateway --lib`
(262 tests pass, no regression). Added an inline comment above the
dependency line explaining why the feature is re-enabled — otherwise a
future developer might strip it during a dependency cleanup.

Rust code is unchanged. All existing HTTP clients constructed via
`reqwest::Client::new()` or `Client::builder()` now respect env-var
proxy config automatically.

Important (round-3 F2) — Document CNI-enforcement prerequisite:

A NetworkPolicy has no effect unless the cluster's CNI enforces it —
stock KIND, Docker Desktop, or bare-EKS clusters without a supported
CNI silently ignore the resource. Added an explicit "⚠️ PREREQUISITE"
block at the top of the values.yaml networkPolicy section listing
supported CNIs (Calico, Cilium, Antrea, EKS with NetworkPolicy add-on,
GKE Dataplane V2, AKS with Azure NPM / Calico) and recommending a
scratch-namespace deny-all verification before relying on the feature.
Mirrored the caveat into the chart README row for networkPolicy.enabled.

Important (round-3 F3) — Qualify metadata isolation as defense-in-depth:

The metadataExclusions rendering is correct under additive NetworkPolicy
semantics (round-2 fix), but the docs presented it as portable IMDS
protection. K8s NetworkPolicy always permits pod-to-resident-node
traffic, and CNI implementations differ in whether ipBlock matches pre-
or post-NAT addresses. Added an explicit "⚠️ Treat this as defense-in-
depth, NOT a portable IMDS guarantee" block in values.yaml enumerating
the two K8s-level caveats and recommending cloud-provider hardening
(IMDSv2 mandatory + hop-limit 1, GCP metadata concealment, Azure
metadata endpoint policy) as the primary control. Mirrored into the
chart README rows for both metadataExclusions.ipv4 and .ipv6.

No behavioural change beyond the reqwest feature addition — F2 and F3
are documentation-only. Existing 58 helm-unittest tests still pass.

Refs openabdev#1394
Refs openabdev#1400 round-3 review by chaodu-agent
@antigenius0910

Copy link
Copy Markdown
Contributor Author

Thanks @chaodu-agent — round-3 fixes pushed in a66f3aa. Kept as a fourth commit to preserve review history.

Finding-by-finding response

🔴 F1 (round-3) — reqwest system-proxy feature was disabled

Verified. Confirmed against crates/openab-gateway/Cargo.toml:14, Cargo.lock (reqwest = "0.12.28"), and reqwest's own docs (system-proxy is in the default feature set; when default-features = false, it must be re-enabled to keep env-var proxy discovery). Also confirmed the gateway's HTTP client construction sites (lib.rs:104, 172, 658, 823) use reqwest::Client::new() / Client::builder() with no explicit .proxy() config, so they depended entirely on the missing feature.

Fix:

- reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }
+ # `system-proxy` re-enables HTTPS_PROXY / HTTP_PROXY / NO_PROXY env-var auto-detection,
+ # which is a default reqwest feature disabled by `default-features = false`. Required so
+ # the gateway respects proxy env vars injected via chart `agents.<name>.gateway.env` when
+ # running under `networkPolicy.egress.mode=proxy`. See PR #1400.
+ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "system-proxy"] }

Added the inline comment so a future developer doing a "clean up unused features" pass doesn't strip it and silently reintroduce the regression.

Verification:

  • cargo check -p openab-gateway → clean build (reqwest 0.12.28 recompiled with the extra feature).
  • cargo test -p openab-gateway --lib262 tests pass, 0 failed (including all wiremock-based adapter tests that construct reqwest clients).
  • No Rust code changes — every existing reqwest::Client::new() / Client::builder() call site now respects env-var proxy config automatically.

The reviewer's point about "a Helm render assertion alone cannot verify this contract" is fair; a full end-to-end proxy-routing integration test would need a live CNI cluster + a running Squid/Envoy sidecar. Not adding that here — same reason the RFC scope kept live-CNI testing out. Cargo test coverage on the reqwest paths is the closest available proxy for correctness.

🟡 F2 (round-3) — Document CNI-enforcement prerequisite

Verified. Root README's raw-install table mentions it (round-1 fix), but neither charts/openab/README.md nor values.yaml did. A Helm user could enable networkPolicy.enabled=true on stock KIND / Docker Desktop / bare-EKS and get zero enforcement while assuming they're isolated.

Fix:

  • Added an explicit "⚠️ PREREQUISITE — CNI enforcement" block at the top of the networkPolicy section in values.yaml, listing supported CNIs (Calico, Cilium, Antrea, EKS with NetworkPolicy add-on, GKE Dataplane V2 / Calico, AKS with Azure NPM / Calico) and recommending a scratch-namespace deny-all verification test.
  • Mirrored the caveat into the chart README row for networkPolicy.enabled with the same list of supported CNIs.

🟡 F3 (round-3) — Qualify metadata isolation as defense-in-depth

Verified. The metadataExclusions rendering is technically correct (round-2 fix), but my docs suggested it was a portable IMDS-protection guarantee. Two K8s-level gotchas the docs skipped:

  1. NetworkPolicy always permits pod-to-resident-node traffic — a compromised pod on a node running metadata-service DNAT still reaches IMDS regardless of the ipBlock.except.
  2. CNI implementations differ on whether ipBlock matches pre-NAT or post-NAT addresses — some CNIs check the original destination, some check post-DNAT.

Fix: added an explicit "⚠️ Treat this as defense-in-depth, NOT a portable IMDS guarantee" block in values.yaml's metadataExclusions docs, enumerating both caveats and recommending cloud-provider hardening (IMDSv2 mandatory + hop-limit 1, GCP metadata concealment, Azure metadata endpoint policy) as the primary control. Mirrored the caveat into the chart README rows for both metadataExclusions.ipv4 and .ipv6.

Test summary

  • Helm-unittest: 58/58 pass (no new cases — F2/F3 are doc-only, F1 is behavioural but only verifiable end-to-end which is out of scope here).
  • helm lint: clean.
  • cargo test -p openab-gateway --lib: 262 pass, 0 fail — reqwest 0.12.28 rebuilds cleanly with the added feature; all wiremock-based adapter tests still exercise the reqwest client paths.

Backwards compatibility

  • F1 — reqwest system-proxy is a well-established default feature; enabling it is compatible with all existing behaviour (no proxy env vars → no change). No API changes.
  • F2, F3 — pure documentation additions.

Ready for another look.

@chaodu-agent

This comment has been minimized.

…ompatibility, stricter mode/port validation

Addresses all three round-4 findings from chaodu-agent on commit a66f3aa.
Kept as a fifth commit rather than amend to preserve review history.

Critical (round-4 F1) — Feishu WebSocket + proxy-mode incompatibility:

The reqwest system-proxy fix (round-3) makes gateway HTTP clients honor
HTTPS_PROXY, but Feishu's default connectionMode="websocket" opens its
event stream via tokio_tungstenite::connect_async which does NOT consume
reqwest's proxy configuration. Under networkPolicy.egress.mode=proxy the
WSS TCP 443 direct-egress attempt is blocked — Feishu gateway can't
establish its event connection.

Rather than implement a full HTTP CONNECT-aware WebSocket path (large
Rust scope), enforce the constraint at chart render time with a clear
error pointing operators at the supported fallback: fail render if
networkPolicy.egress.mode=proxy AND any agent has gateway.feishu.appId
set AND gateway.feishu.connectionMode is unset or "websocket". Positive
path (connectionMode="webhook") renders cleanly. Documented in values.yaml
and the chart README row for networkPolicy.egress.mode.

Critical (round-4 F2) — Empty-string mode silently defaulted to direct:

The previous `$mode := $np.egress.mode | default "direct"` treated an
explicit `--set-string networkPolicy.egress.mode=""` as "direct" and
rendered open-internet HTTPS, contradicting the fail-closed contract.

Fix: drop the `| default "direct"` — validate the raw value directly.
values.yaml already provides the default; explicitly-empty input is now
rejected with the same error format as any other invalid mode value.

Important (round-4 F3) — Protocol-only proxy port entries:

The previous check ensured proxy.ports was non-empty, but a valid
NetworkPolicyPort like `{protocol: TCP}` (no `port` field) passed and
rendered — Kubernetes treats missing `port` as "all ports for the given
protocol", silently broadening egress to the proxy pods.

Fix: iterate proxy.ports and require every entry to have a truthy `port`
field; error message names the offending index. Documented in the chart
README row for networkPolicy.egress.proxy.

Tests: 4 new helm-unittest cases (62 total, was 58):
- F2: empty mode string → fail-closed with exact error message
- F3: proxy port entry with only `protocol` → fail-closed
- F1: proxy+feishu websocket → fail-closed with actionable message
- F1 positive: proxy+feishu webhook → 2 policies render as expected

Full chart suite: 62/62 pass. helm lint clean. No Rust changes.

Refs openabdev#1394
Refs openabdev#1400 round-4 review by chaodu-agent
@antigenius0910

Copy link
Copy Markdown
Contributor Author

Thanks @chaodu-agent — round-4 fixes pushed in b0a35dd. Fifth commit, still no amends.

Finding-by-finding response

🔴 F1 (round-4) — Feishu WebSocket bypasses reqwest proxy config

Verified. crates/openab-gateway/src/adapters/feishu.rs:1022 uses tokio_tungstenite::connect_async(&ws_url).await for the WSS event stream. tokio_tungstenite doesn't consume reqwest's proxy configuration, so the system-proxy fix from round-3 doesn't reach this path. Under mode=proxy the WSS TCP 443 direct-egress attempt is denied by NetworkPolicy → Feishu gateway can't establish its event connection.

Fix approach: rather than implement a full HTTP CONNECT-aware WebSocket path (large Rust scope — new crate dependency or hand-rolled CONNECT tunnel + tests), took the reviewer's second suggestion: "explicitly reject/document Feishu WebSocket mode with proxy-only egress and require webhook mode." Chart render now fails when:

  • networkPolicy.enabled=true
  • networkPolicy.egress.mode=proxy
  • Any agent has gateway.feishu.appId non-empty
  • AND gateway.feishu.connectionMode is unset or "websocket"

Error message names the agent and points at the fix:

networkPolicy.egress.mode=proxy is incompatible with Feishu WebSocket mode for
agent "kiro": the WSS event connection opens TCP 443 directly via
tokio_tungstenite and does not honor HTTP CONNECT proxies. Set
agents.kiro.gateway.feishu.connectionMode="webhook", or disable networkPolicy
for this release.

Positive path — connectionMode: webhook — renders 2 NetworkPolicies cleanly. Documented in values.yaml and the chart README row for networkPolicy.egress.mode.

Coverage:

  • New negative test: fails rendering when proxy mode is combined with Feishu WebSocket mode — asserts the exact error message.
  • New positive test: renders successfully when proxy mode is combined with Feishu WEBHOOK mode — asserts hasDocuments: count: 2.

If proper CONNECT support for the Feishu WSS path is desired, happy to open a follow-up PR — but the reviewer's alternative (explicitly reject) matches the "keep upstream maintenance surface small" pattern this RFC has been following.

🔴 F2 (round-4) — Empty-string mode silently defaulted to direct

Verified. --set-string networkPolicy.egress.mode='' succeeded and rendered the direct-mode DNS + open-internet HTTPS policy — my previous $mode := $np.egress.mode | default "direct" treated the explicit empty string as empty and defaulted it.

Fix: dropped | default "direct" — validation now checks the raw value. values.yaml already supplies the field default (mode: direct), so users who don't set it still get direct mode. Users who explicitly set it to "" now get:

networkPolicy.egress.mode must be exactly "direct" or "proxy" (got "")

Same error format as any other invalid mode value.

Coverage: new negative test asserts empty-string mode fails with the exact error.

🟡 F3 (round-4) — Protocol-only proxy port entries broaden egress

Verified. ports: [{protocol: TCP}] (no port) passed the existing non-empty check and rendered — Kubernetes treats missing port as "all ports for the given protocol", authorizing every port on the selected proxy pods.

Fix: added a per-entry iteration:

{{- range $i, $p := $np.egress.proxy.ports }}
{{- if not $p.port }}
{{- fail (printf "…proxy.ports entry %d missing port — omitted port matches ALL ports for the given protocol" $i) }}
{{- end }}
{{- end }}

Named the offending index in the error so operators can find it in a multi-entry list. Documented in the chart README row for networkPolicy.egress.proxy.

Coverage: new negative test asserts protocol-only entry fails with the exact error message.

Test summary

  • helm-unittest: 62/62 pass (was 58, +4 new: F2 empty-mode, F3 protocol-only-port, F1 negative feishu-ws, F1 positive feishu-webhook).
  • helm lint: clean.
  • Manual smoke verified for each new failure path + Feishu webhook happy path.

Backwards compatibility

  • F1 — additive: only fails when the exact proxy + Feishu websocket combination is configured. Direct mode and proxy-mode-without-Feishu are unaffected.
  • F2 — the only observable change is that mode="" now fails instead of silently defaulting. Anyone relying on that undocumented coercion had a fail-open bug.
  • F3 — same story: anyone with a protocol-only port entry was operating outside the documented contract.

Ready for another look.

@chaodu-agent

This comment has been minimized.

…m-proxy only

Round-5 review revealed that proxy-mode egress cannot be honored end-to-end
without a proxy-aware networking refactor across openab-core / openab-
gateway. Every prior fix round (rounds 1-4) uncovered another transport
that bypasses reqwest and opens WSS directly:

- Discord (default) — Serenity's own WSS client via client.start()
- Slack Socket Mode — crates/openab-core/src/slack.rs:761 uses
  tokio_tungstenite::connect_async
- External gateway (gateway.deploy=false) —
  crates/openab-core/src/gateway.rs:818 same pattern
- Feishu WebSocket — already guarded in round 4, but the guard only
  covered the structured chart config path; configToml / envFrom /
  gateway.env bypasses remained
- AgentCore — its own TLS/WebSocket transport

The project already documents this in docs/openshell.md: "Unless OAB's
networking layer is refactored to be fully HTTP/HTTPS proxy-aware
(tunneling WSS through OpenSHELL's L7 proxy), the integration cannot
function." Continuing to layer chart-level guards would leave proxy
mode practically unusable (Discord is the default transport for most
deployments) while giving users a false sense of enterprise egress
isolation.

Per Option C of the reviewer's third suggestion (also confirmed with the
PR author), withdraw proxy mode from this PR entirely. Follow-up RFC
will cover proxy-aware egress properly once the Rust networking refactor
is scoped.

## What's removed

- `networkPolicy.egress.mode` value (was "direct" | "proxy")
- `networkPolicy.egress.proxy` config block (namespace / podSelector / ports)
- Proxy-mode branch in templates/networkpolicy.yaml egress helper
- Feishu WebSocket incompatibility guard (no longer needed)
- All proxy-mode validation (empty mode, invalid mode, empty proxy fields,
  protocol-only ports)
- All proxy-mode positive + negative tests
- "Proxy-mode CONTRACT" doc block in values.yaml
- Proxy-mode language in chart README

## What's kept (all still generally useful)

- Opt-in `networkPolicy.enabled` master switch (unchanged)
- Direct-mode egress: DNS to kube-system + IPv4/IPv6 HTTPS with configurable
  metadata exclusions
- Agent-to-gateway TCP 8080 egress rule when chart-managed gateway is deployed
- `gateway.deploy=false` handling (matches templates/gateway.yaml)
- DNS scoping to kube-system (configurable via egress.dnsNamespace)
- `metadataExclusions.{ipv4,ipv6}` configurable per cloud
- IPv6 support (`allowIpv6`) with AWS IMDS exclusion + additive-semantics doc
- CNI-enforcement prerequisite documentation
- Defense-in-depth caveat on metadata exclusions
- `k8s/networkpolicy.yaml` raw manifest
- `gateway.env` / `gateway.envFrom` — reframed as general-purpose env
  injection (docs no longer suggest proxy usage)

## F2 fix (round 5) — landed as small win regardless

Enabled reqwest `system-proxy` feature for the root package and openab-core
(previously only openab-gateway had it). Now operators who set
HTTPS_PROXY / HTTP_PROXY / NO_PROXY on any variant's pod get reqwest-based
HTTP calls (LLM APIs, Slack Web API, remote config, hooks, STT, media)
routed through the proxy — WITHOUT this implying the chart supports
enforced proxy mode. WSS transports (Slack Socket Mode, external gateway)
remain unaffected pending the networking refactor. Cargo comment in
openab-core/Cargo.toml calls out that limitation explicitly.

Verified with `cargo check --workspace` (clean rebuild across all three
reqwest sites) and existing openab-gateway lib test suite.

## Tests

Chart: 52/52 pass (was 62, drop is expected — removed 10 proxy-mode tests).
Rust: cargo check --workspace clean; openab-gateway lib tests unaffected.
helm lint clean.

## Follow-up RFC to file

Proxy-aware egress ("networking refactor") — covers:
- Replace or fork Serenity to route WSS through HTTP CONNECT
- Replace tokio_tungstenite direct calls with a CONNECT-aware alternative
  (Slack Socket Mode, external gateway, Feishu WebSocket)
- AgentCore proxy configuration
- AWS SDK proxy configuration (separate from reqwest)
- End-to-end integration test with a local Squid/Envoy

Refs openabdev#1394
Refs openabdev#1400 round-5 review by chaodu-agent
@antigenius0910 antigenius0910 changed the title feat(chart,k8s): add optional NetworkPolicy for pod-level isolation feat(chart,k8s): add optional NetworkPolicy for pod-level isolation (direct egress) Jul 15, 2026
@antigenius0910

Copy link
Copy Markdown
Contributor Author

Thanks @chaodu-agent. Round-5 findings led to a scope-narrowing decision rather than another round of guards.

Decision: withdraw proxy-mode egress from this PR (dfc1dae)

Every round of your review revealed another transport that bypasses reqwest and opens WSS directly:

  • Round 4: Feishu WebSocket via tokio_tungstenite::connect_async (crates/openab-gateway/src/adapters/feishu.rs:1022)
  • Round 5: Discord (the default!) via Serenity's own WSS client; Slack Socket Mode (crates/openab-core/src/slack.rs:761); external gateway (crates/openab-core/src/gateway.rs:818); AgentCore's own TLS/WebSocket

Also confirmed: docs/openshell.md:12 already documents this — "Unless OAB's networking layer is refactored to be fully HTTP/HTTPS proxy-aware (tunneling WSS through OpenSHELL's L7 proxy), the integration cannot function." Pahud himself wrote that.

Continuing to layer chart-level guards (Discord check + Slack Socket check + external-gateway check + configToml scan + envFrom scan …) would leave proxy mode practically unusable — Discord is the default transport for most deployments — while giving users a false sense of enterprise egress isolation. That's worse than not shipping it.

Took Option C of your suggestion ("narrow the advertised proxy-mode contract to transports that are actually supported") to its logical conclusion: withdraw proxy mode entirely from this PR, ship direct mode + all the hardening that already landed, and file a follow-up RFC for proxy-aware egress once the Rust networking refactor is scoped.

What was removed in dfc1dae

  • networkPolicy.egress.mode value (was "direct" | "proxy")
  • networkPolicy.egress.proxy config block
  • Proxy-mode branch in the egress helper
  • Feishu WebSocket incompatibility guard (no longer needed)
  • All proxy-mode validation (empty mode, invalid mode, empty proxy fields, protocol-only ports)
  • All proxy-mode positive + negative tests
  • "Proxy-mode CONTRACT" doc block in values.yaml
  • Proxy-mode language throughout the chart README

What survived (all still useful)

  • Opt-in networkPolicy.enabled master switch (unchanged)
  • Direct-mode egress: DNS to kube-system + IPv4/IPv6 HTTPS with configurable metadata exclusions
  • Agent-to-gateway TCP 8080 egress rule when chart-managed gateway is deployed
  • gateway.deploy=false orphan-avoidance handling
  • dnsNamespace configurable
  • metadataExclusions.{ipv4,ipv6} configurable per cloud
  • IPv6 support (allowIpv6) + additive-semantics doc
  • CNI-enforcement prerequisite documentation (round 3 F2)
  • Defense-in-depth caveat on metadata exclusions (round 3 F3)
  • k8s/networkpolicy.yaml raw manifest + opt-in step in both root READMEs
  • gateway.env / gateway.envFrom — reframed as general-purpose env injection (no longer proxy-specific)

Round-5 F2 addressed as a small win

Enabled reqwest's system-proxy feature for the root package AND openab-core (previously only openab-gateway had it). Operators who set HTTPS_PROXY / HTTP_PROXY / NO_PROXY on any variant's pod now get reqwest-based HTTP calls (LLM APIs, Slack Web API, remote config, hooks, STT, media) routed through the proxy — without the chart claiming enforced proxy mode. WSS transports remain unaffected pending the networking refactor. Added an inline Cargo comment in openab-core/Cargo.toml calling out the WSS limitation explicitly so a future dev doesn't reintroduce misleading proxy claims.

Follow-up RFC scope (to file after this merges)

Proxy-aware egress needs to touch:

  1. Replace or fork Serenity so its WSS client routes through HTTP CONNECT
  2. Replace tokio_tungstenite::connect_async direct calls (Slack Socket Mode, external gateway, Feishu WebSocket) with a CONNECT-aware alternative
  3. AgentCore proxy configuration
  4. AWS SDK proxy configuration (separate from reqwest — has its own env-var scheme)
  5. End-to-end integration test with a local Squid/Envoy proving each transport actually tunnels

That's a multi-crate Rust refactor, not a chart change. Belongs in its own RFC + PR.

Verification

  • helm unittest .52/52 pass (was 62, drop is expected: removed 10 proxy-mode tests)
  • helm lint . — clean
  • cargo check --workspace — clean rebuild across all three reqwest sites
  • cargo test -p openab-gateway --lib — 262/262 pass (round-3 verification, unaffected)

Updating the PR body next to reflect the narrower scope. Thanks for the persistent scrutiny — the withdrawal is a better outcome than shipping a mode that would have degraded to "silently break Discord users" in production.

@chaodu-agent

Copy link
Copy Markdown
Collaborator

Important

CHANGES REQUESTED ⚠️ — With proxy-mode egress withdrawn, the direct-mode policy is a sound opt-in baseline, but two supported networking claims remain incomplete and one removed proxy-mode reference is stale.

What This PR Does

This PR adds opt-in Kubernetes NetworkPolicy resources for Helm-managed agent and gateway pods plus a raw-install policy. It defaults to deny-all agent ingress and permits scoped DNS plus direct IPv4/IPv6 HTTP(S) egress, with configurable metadata exclusions and escape hatches for environment-specific rules.

How It Works

The chart renders one policy per enabled agent and another for each chart-managed gateway. Agent-to-chart-managed-gateway TCP 8080 is allowed only when that gateway is deployed; shared Helm logic supplies DNS, HTTP(S), metadata exclusions, and user-provided extra rules. The PR also adds generic gateway environment injection and enables reqwest system-proxy discovery in the root, openab-core, and openab-gateway workspace packages.

Findings

# Severity Finding Location
1 🟡 Important The documentation says allowHttp covers HTTP STT endpoints, but it opens only TCP 80; documented STT and config-only external-gateway examples use TCP 8080 and remain blocked unless users discover and author an extraRules rule themselves. charts/openab/values.yaml:98, charts/openab/README.md:22
2 🟡 Important The PR claims proxy routing for LLM API calls, but the workspace-excluded native openab-agent—the component that owns those LLM clients—still disables reqwest defaults without enabling system-proxy for either reqwest version. openab-agent/Cargo.toml:12, openab-agent/Cargo.toml:16
3 🟡 Important The gateway dependency comment still says system-proxy is required for the removed networkPolicy.egress.mode=proxy contract. crates/openab-gateway/Cargo.toml:14
4 🟢 Praise Proxy-mode withdrawal resolves the prior fail-closed/runtime-transport blockers, while the remaining direct policy has focused Helm coverage and honest CNI/IMDS caveats.
Finding Details

🟡 F1: Document and test the custom-port egress contract

allowHttp=true renders an internet rule for TCP 80 only. However, the repository's supported examples include http://192.168.1.100:8080/v1 for STT and ws://openab-gateway:8080/ws for config-only/external gateways. A targeted render using networkPolicy.enabled=true, gateway.deploy=false, and an external gateway on port 8080 produced only DNS and TCP 443 egress, so that connection is denied.

This does not require broadly allowing port 8080 by default. Instead, state beside allowHttp, gateway.deploy, and egress.extraRules that non-80 HTTP and non-443 WS/WSS destinations require an explicit rule, and provide at least one concrete selector/CIDR + port example. Add a render test for that documented recipe so enabling isolation does not silently break these supported configurations.

🟡 F2: Either cover the native LLM client or narrow the proxy claim

The root workspace correctly enables reqwest system-proxy in the root package, openab-core, and openab-gateway. openab-agent is explicitly excluded from that workspace and has its own lockfile. Its reqwest 0.12 and aliased reqwest 0.13 declarations both use default-features = false without system-proxy, while its LLM providers construct these clients directly (for example, openab-agent/src/llm.rs:293).

Therefore, setting HTTPS_PROXY / HTTP_PROXY does not make the native agent's LLM requests follow the PR body's stated behavior. Either enable the appropriate proxy-discovery feature for the native client's reqwest dependencies and validate its separate build, or narrow the PR/body/code wording so it does not claim LLM API coverage that this commit does not provide.

🟡 F3: Remove the withdrawn mode from the dependency rationale

The openab-gateway Cargo comment still says the feature is required when running under networkPolicy.egress.mode=proxy, but that value and mode were deliberately removed in this commit. Rewrite the comment around general operator-supplied proxy environment variables, matching the root/core comments, so future maintainers do not infer that the chart still supports an enforced proxy-only mode.

Addressing External Reviewer Feedback

@antigenius0910 (round 5, dfc1dae)

“withdraw proxy mode entirely from this PR, ship direct mode + all the hardening that already landed”

Addressed: proxy-only NetworkPolicy values, branches, validation, and tests are removed. This resolves the prior Discord, Slack Socket Mode, external-gateway, Feishu, and AgentCore proxy-routing blockers rather than retaining incomplete chart guards.

“Enabled reqwest's system-proxy feature for the root package AND openab-core

⚠️ Partially addressed: root, core, and gateway declarations are covered, but the public LLM claim does not hold for the separately built native openab-agent clients described in F2.

No submitted GitHub reviews or inline review threads from other reviewers were present at this commit.

Review Coverage

Reviewer Focus Outcome
Reviewer A NetworkPolicy security and Helm semantics F1; direct-policy rendering otherwise sound
Reviewer B Runtime/dependency integration F2, F3
Reviewer C Tests, docs, and operability F1; CNI and IMDS caveats confirmed
Reviewer D Independent final audit Confirmed changes requested
Baseline and Validation
  • PR opened: 2026-07-14
  • Reviewed commit: dfc1dae552968d2c604bcaa6a336ef55dca0c03a
  • Current main checked: f26f99a93bf624e2b26ea2bf947cce6e22ddb594
  • Main already has: no chart or raw NetworkPolicy
  • Net-new value: opt-in pod isolation, chart-managed gateway integration, raw-manifest support, configurable metadata exclusions, gateway env/envFrom, and system-proxy support for the root workspace packages
  • GitHub checks: all reported build/check/smoke/Helm jobs passed; operator and polling jobs skipped as expected
  • Local validation: helm unittest . passed 52/52; helm lint . passed; git diff --check passed
  • Targeted render: config-only external gateway on TCP 8080 received DNS + TCP 443 egress only, reproducing F1
  • Limitation: Cargo is unavailable in the reviewer runtime; the remote check job is green
What's Good (🟢)
  • The feature is disabled by default, preserving existing releases and providing a straightforward rollback.
  • DNS defaults to the trusted kube-system namespace, with an explicit escape hatch for nonstandard clusters.
  • IPv4/IPv6 metadata exclusions are configurable in the original allow rule, and the documentation correctly treats them as CNI-dependent defense in depth.
  • Agent-to-chart-managed-gateway TCP 8080 and gateway.deploy=false resource boundaries have focused tests.
  • With proxy mode withdrawn, the chart no longer advertises an enforcement topology that its direct socket transports cannot satisfy.

5️⃣ Three Reasons We Might Not Need This PR

  1. It remains opt-in — existing installations gain no isolation until operators enable and verify enforcement.
  2. Central policy may be stronger — platform-managed namespace defaults, policy-as-code, or CNI-specific policies can provide uniform enforcement and observability.
  3. Portable L3/L4 rules are necessarily incomplete — DNS names, custom service ports, proxy tunneling, and provider-specific metadata behavior still require deployment-specific configuration.

…gent proxy, external-gateway docs, stale comment

Addresses all three round-6 findings from chaodu-agent on commit dfc1dae.

F1 (custom-port destinations require extraRules):

allowHttps opens only TCP 443 and allowHttp only TCP 80. Supported openab
configurations that use other ports were silently broken when enabling
NetworkPolicy: (a) external config-only gateway (gateway.deploy=false)
listens on TCP 8080; (b) self-hosted STT often runs on custom ports
(e.g. http://192.168.1.100:8080/v1); (c) internal LLM proxies like
LiteLLM commonly bind :4000.

Fix (docs-only, since a chart-level default for arbitrary custom ports
would be too broad):
- Added a ⚠️ warning block above `networkPolicy.egress.extraRules` in
  values.yaml enumerating the three common cases and providing a
  concrete extraRules recipe for the external-gateway case.
- Chart README row for `extraRules` mirrors the caveat.
- Chart README row for `gateway.deploy` calls out that setting it to
  false removes the chart-managed agent→gateway TCP 8080 exception, so
  operators must supply an extraRules entry.
- New helm-unittest test asserts the documented extraRules recipe for
  external-gateway TCP 8080 renders when gateway.deploy=false (also
  verifies that no chart-managed agent→gateway rule leaks in that path).

F2 (native openab-agent proxy claim was incomplete):

The PR body claimed reqwest system-proxy coverage for LLM API calls, but
openab-agent is workspace-excluded (root Cargo.toml `exclude` list) and
has its own Cargo.toml + Cargo.lock. Its reqwest 0.12 and reqwest013
(0.13, aliased for rmcp OAuth) declarations both had
`default-features = false` without system-proxy. Setting HTTPS_PROXY on
the native agent's pod would not route LLM requests (src/llm.rs:293/663/
1033 construct clients via reqwest::Client::new() without explicit
proxy config).

Fix: added "system-proxy" to both reqwest and reqwest013 features in
openab-agent/Cargo.toml, with an inline comment matching the root/core
style. `cargo check` in openab-agent clean; Cargo.lock updated for the
new feature dep.

F3 (stale withdrawn-mode reference in gateway Cargo comment):

The comment I added in round 3 referenced networkPolicy.egress.mode=proxy,
which was removed in the round-5 withdraw commit (dfc1dae). Rewrote the
comment to match root/core framing: general operator-supplied proxy env
vars, with an explicit note that WSS transports (Feishu WSS via
tokio_tungstenite) don't honor these env vars — that's the follow-up
networking refactor.

Tests: 53/53 helm-unittest pass (was 52, +1 external-gateway extraRules
render test). helm lint clean. cargo check on openab-agent workspace
clean.

Refs openabdev#1394
Refs openabdev#1400 round-6 review by chaodu-agent
@antigenius0910

Copy link
Copy Markdown
Contributor Author

Thanks @chaodu-agent — round-6 fixes pushed in 802cdd9. All three findings addressed, no new critical.

Finding-by-finding response

🟡 F1 (round-6) — Custom-port destinations require extraRules

Verified. allowHttps opens only TCP 443, allowHttp only TCP 80. Three supported openab configurations use other ports and were silently broken when enabling NetworkPolicy:

  • External config-only gateway (gateway.deploy=false + gateway.url on TCP 8080)
  • Self-hosted STT on custom ports (e.g. http://192.168.1.100:8080/v1)
  • Internal LLM proxies (LiteLLM on :4000, etc.)

Fix (docs-only — a chart default for arbitrary custom ports would be too broad):

  • Added a ⚠️ warning block above networkPolicy.egress.extraRules in values.yaml enumerating the three common cases with a concrete extraRules recipe for the external-gateway case.
  • Chart README row for extraRules mirrors the caveat with a summary of the three broken paths.
  • Chart README row for gateway.deploy explicitly notes that deploy=false removes the chart-managed agent→gateway TCP 8080 exception → operators must supply an extraRules entry.

Coverage: new helm-unittest test — documented extraRules recipe for external-gateway TCP 8080 renders when gateway.deploy=false. Asserts:

  1. The extraRules entry renders with the expected namespaceSelector + podSelector + ports structure.
  2. No chart-managed agent→gateway-component rule leaks in that path (regression guard).

🟡 F2 (round-6) — Native openab-agent proxy claim was incomplete

Verified. openab-agent is workspace-excluded (root Cargo.toml exclude = ["openab-agent", ...]), has its own Cargo.toml + Cargo.lock, and both its reqwest 0.12 and reqwest013 (0.13 alias for rmcp OAuth) declarations had default-features = false without system-proxy. LLM clients in openab-agent/src/llm.rs:293/663/1033 construct via reqwest::Client::new() with no explicit proxy config. So HTTPS_PROXY did NOT reach the native agent's LLM requests, contradicting the PR body.

Fix: added "system-proxy" to both reqwest and reqwest013 features in openab-agent/Cargo.toml. Inline comment matches the root/core style. Verified with:

cd openab-agent && cargo check
    Checking reqwest v0.13.4
    Checking reqwest v0.12.28
    Checking openab-agent v0.1.0
    Finished `dev` profile

Cargo.lock regenerated for the new feature dep. Both reqwest versions now honor HTTPS_PROXY / HTTP_PROXY / NO_PROXY env vars in the native agent build. The PR body claim about LLM API coverage now holds.

🟡 F3 (round-6) — Stale mode=proxy reference in gateway Cargo comment

Verified. My round-3 comment on crates/openab-gateway/Cargo.toml:14 still said "when running under networkPolicy.egress.mode=proxy" — but that config was removed in the round-5 withdraw commit (dfc1dae).

Fix: rewrote the comment to match the root/core framing (general operator-supplied proxy env vars, no chart-mode reference). Kept the explicit note that WSS transports (Feishu WSS via tokio_tungstenite) do NOT honor these env vars — that's tracked as the follow-up networking refactor in docs/openshell.md.

Test summary

  • helm-unittest: 53/53 pass (was 52, +1 for the external-gateway extraRules render test).
  • helm lint: clean.
  • cd openab-agent && cargo check: clean rebuild across both reqwest 0.12 and reqwest013 (0.13).
  • cargo check --workspace (main workspace): unchanged, clean.

Backwards compatibility

  • F1 — pure documentation additions + one new test case; no rendered behaviour change.
  • F2system-proxy is behaviourally inert unless operators set proxy env vars. No API changes to openab-agent.
  • F3 — comment-only change.

Ready for another look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(chart): add optional NetworkPolicy template for pod-level network isolation

2 participants