Skip to content

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

Description

@antigenius0910

Description

The openab Helm chart (charts/openab/) and raw manifests (k8s/) currently ship without any NetworkPolicy resource. Once deployed, agent pods can freely initiate and receive traffic to/from every other pod in the namespace — and, on most CNIs without a default-deny baseline, to/from every other pod in the cluster.

This surfaced as a Medium-severity IaC finding in an internal AppSec scan (automation confidence 93%): "a NetworkPolicy is a way to define how pods are allowed to communicate with each other over the network. By default, pods are allowed to communicate with any other pod within the same namespace, which can create security risks." The finding is valid — grep -r NetworkPolicy charts/ k8s/ returns zero matches. No shared upstream terraform / k8s-workload module supplies one either; the chart is fully self-contained.

For a workload whose whole job is executing LLM-driven agents (some tool-using, some code-executing), unrestricted pod-to-pod traffic and unrestricted egress are non-trivial concerns:

  • Lateral movement: an agent pod compromised via prompt-injection or a hostile tool result has unrestricted access to co-tenant workloads (databases, Vault, other agents' PVCs, etc.) in the same namespace.
  • Egress abuse: agent commands can reach arbitrary internal endpoints (cloud metadata service, admin APIs, internal SaaS), not just the intended LLM / Slack / Discord APIs.
  • Compliance pushback: downstream enterprise deployments (a Dataminr fork of this chart, for example) are being asked by AppSec to justify or fix this today. Without a chart-provided template, every downstream hand-rolls its own — different labels, different selector conventions, different levels of correctness.

Use Case

Give operators an opt-in, well-labelled NetworkPolicy they can flip on with a single value, without having to reverse-engineer the chart's pod selectors, gateway topology, or which ports actually matter.

Concrete scenarios:

  1. Enterprise deployment (e.g. Dataminr fork) — AppSec ticket requires a NetworkPolicy on k8s/deployment.yaml. Today the fix path is: fork the chart, hand-write the policy, keep it in sync forever. Proposal: --set networkPolicy.enabled=true closes the finding.
  2. Multi-tenant namespace — cluster admin runs openab next to unrelated workloads and wants agent pods walled off. Today: hand-write from scratch, hope the pod selector matches. Proposal: chart uses the same openab.selectorLabels helper the Deployment already uses, so ingress/egress selectors can never drift.
  3. Gateway + agent split — gateway pod needs webhook ingress (Telegram / LINE / Teams / Feishu / WeCom / Google Chat hit it), agent pods do not. Today: two hand-written policies. Proposal: chart renders the right policy per resource based on gateway.enabled.
  4. Egress-restricted enterprise cluster (per @pahud, see comment) — org policy is deny-all egress; all outbound traffic must be routed through a designated proxy (e.g. OpenSHELL, Squid, Envoy) that owns the domain allowlist. Today: hand-write a to: podSelector: {...proxy...} policy. Proposal: networkPolicy.egress.mode=proxy + egress.proxy.{namespace,podSelector,ports} renders the correct policy shape, chart stays proxy-agnostic (BYO proxy).

Proposed Design

Backwards-compatible, opt-in. Default networkPolicy.enabled: false — no rendered resource, no behaviour change for existing releases. Mirrors the established openab pattern (slack.enabled, gateway.enabled, per-agent discord.enabled) and matches the RFC precedent set by PRs #901 / #911 / #914.

Two supported egress topologies, chosen via egress.mode:

  • direct (default) — DNS + HTTPS direct to any external destination. Suits hobbyist / dev-cluster / small-team deployments where the cluster has no dedicated egress proxy.
  • proxy — DNS + egress restricted to a designated proxy Service; all HTTPS/HTTP is expected to be tunnelled through that proxy. Matches the enterprise pattern (OpenSHELL et al.) called out by @pahud on Discord (discussion / comment). Chart does not ship the proxy — users BYO.

values.yaml

networkPolicy:
  # Master switch. false (default) = no NetworkPolicy resource rendered.
  # Backwards-compatible; existing releases are unaffected until this flips.
  enabled: false

  # Ingress rules. Default = deny-all ingress to agent pods (they only
  # initiate outbound Socket Mode / websocket connections). Gateway pod
  # gets a separate policy allowing port 8080 when gateway.enabled=true.
  ingress:
    # extraRules: raw NetworkPolicyIngressRule entries appended verbatim.
    # Use for cluster-specific allow-lists (e.g. Prometheus scrape, mesh sidecars).
    extraRules: []

  # Egress rules.
  egress:
    # Egress topology. "direct" (default) allows egress to the open internet
    # (typically DNS + HTTPS). "proxy" restricts egress to a designated proxy
    # Service — recommended for enterprise / multi-tenant / compliance-driven
    # deployments (see OpenSHELL prior art).
    mode: direct   # or "proxy"

    # ── mode: direct ────────────────────────────────────────────────────────
    # Allow DNS to kube-dns (UDP+TCP 53). Almost always required.
    allowDns: true
    # Allow HTTPS (TCP 443) to any external destination.
    allowHttps: true
    # Allow HTTP (TCP 80). Off by default — enable if hooks/STT fetch over HTTP.
    allowHttp: false

    # ── mode: proxy ─────────────────────────────────────────────────────────
    # When mode=proxy, egress is restricted to this Service selector.
    # The chart does NOT ship the proxy — deploy it separately (OpenSHELL,
    # Squid, Envoy, mitmproxy, or anything speaking HTTP CONNECT).
    # allowDns above still applies (kube-dns needs DNS regardless).
    # allowHttps / allowHttp are IGNORED in proxy mode.
    proxy:
      namespace: ""            # e.g. "egress-proxy"
      podSelector: {}          # e.g. matchLabels: { app: openshell }
      ports: []                # e.g. [{ protocol: TCP, port: 3128 }]

    # extraRules: raw NetworkPolicyEgressRule entries appended verbatim.
    # Use to open extra ports or add cluster-specific carve-outs.
    extraRules: []

Rendered resources (when enabled: true)

Up to two policies emitted:

  1. <release>-agents — targets pods matching the agent Deployment's selector labels. Ingress: [] (deny-all). Egress: shape depends on egress.mode:
    • direct: DNS (if allowDns) + HTTPS (if allowHttps) + HTTP (if allowHttp) + any extraRules.
    • proxy: DNS (if allowDns) + single to: [{ namespaceSelector, podSelector, ports }] rule pointing at the configured proxy + any extraRules.
  2. <release>-gateway — only rendered when gateway.enabled=true. Ingress: TCP 8080 from any source (webhook entry point; Ingress controller in front handles auth). Egress: same shape as agent policy — because the gateway also talks to platform APIs (Telegram / Teams / Feishu / etc.), it needs the same egress path.

Selector labels reuse the existing openab.selectorLabels helper so drift with deployment.yaml / gateway.yaml is structurally impossible.

Raw k8s/ manifests

Add k8s/networkpolicy.yaml mirroring the chart's rendered direct-mode default (deny-all ingress, DNS + HTTPS egress). This is the specific file cited by the AppSec finding — a chart-only fix leaves k8s/deployment.yaml still flagged in downstream scans that consume the raw manifests directly. Users who want proxy-mode should use the chart.

Test plan

  • charts/openab/tests/networkpolicy_test.yaml (helm-unittest), extending the 6-case template from PRs feat(chart): add imagePullSecrets support at per-agent and global level #911 / feat(chart): add serviceAccountName support at per-agent and global level #914 to cover both modes:
    1. enabled: false (default) → no NetworkPolicy resource rendered
    2. enabled: true, mode: direct, gateway off → single agent policy, DNS + HTTPS egress
    3. enabled: true, mode: direct, gateway on → agent + gateway policies rendered
    4. enabled: true, mode: direct, allowHttp: true → HTTP egress rule present
    5. enabled: true, mode: proxy → egress restricted to proxy podSelector, no HTTPS/HTTP-to-any rule
    6. enabled: true, mode: proxy, gateway on → both policies use proxy egress
    7. ingress.extraRules populated → rules present verbatim
    8. egress.allowDns: false, allowHttps: false, mode: direct → egress locked down (only extraRules apply)
  • helm lint + helm template smoke on the affected release configurations.

Out of Scope

  • Namespace-level default-deny — cluster-admin concern, not a workload chart concern.
  • CNI-specific extensions (Cilium CiliumNetworkPolicy, Calico GlobalNetworkPolicy). Stay on vanilla networking.k8s.io/v1 for portability; advanced users can layer their own on top.
  • Automatic pinning of Slack / Discord / Anthropic egress CIDRs. Those move; hardcoding them in a chart ages badly. direct mode users who want tighter egress can supply egress.extraRules themselves; proxy mode users push the allowlist to the proxy where it belongs.
  • Chart-managed egress proxy (bundling OpenSHELL / Squid as a subchart). Would expand upstream maintenance surface — users BYO proxy, chart just emits the right NetworkPolicy pointing at it.
  • Command-execution restriction / tool allowlist (raised by @pahud in the linked Discord thread: "指定只有某些指令可以執行"). That belongs to the ACP / agent tool-allowlist layer, not the NetworkPolicy layer. Worth a separate RFC.

Prior Art

Happy to take the PR — will follow the same file layout (values.yaml, templates/networkpolicy.yaml, README.md, tests/networkpolicy_test.yaml) plus the k8s/networkpolicy.yaml addition.


Trigger: AppSec / IaC scan flagged k8s/deployment.yaml for missing NetworkPolicy (Medium severity, AppSec automation confidence 93%). Verified valid — repo-wide grep NetworkPolicy returns zero matches across charts/ and k8s/.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions