Skip to content

Restrict agent network traffic with NetworkPolicy

This guide shows you how to apply per-agent ingress and egress restrictions using a networking.k8s.io/v1 NetworkPolicy generated by the operator from your CR spec. All ingress/egress rules pass through unchanged from the spec; there are no operator-side defaults or rewriting.

How to enable

spec:
  networkPolicy:
    enabled: true
    policyTypes: [Ingress, Egress]
    ingress:
      - from:
          - namespaceSelector:
              matchLabels:
                kubernetes.io/metadata.name: hermes-operator-system
    egress:
      - to:
          - namespaceSelector: {}      # any namespace
        ports:
          - protocol: TCP
            port: 443                  # LLM provider HTTPS
      - to:
          - namespaceSelector: {}      # cluster DNS
        ports:
          - protocol: UDP
            port: 53

The operator reconciles a NetworkPolicy/hermes-<name> in the agent's namespace. The podSelector.matchLabels targets the agent's standard labels (hermes.undermountain.cc/{agent, agent-ns}); no need to author the selector yourself.

Spec fields

Field Type Notes
enabled bool When false, any previously-created NetworkPolicy is deleted in-place (not ownerRef GC, which only triggers on CR delete).
ingress []networkingv1.NetworkPolicyIngressRule Direct passthrough. Empty + policyTypes includes "Ingress" → ALL ingress denied.
egress []networkingv1.NetworkPolicyEgressRule Direct passthrough. Empty + policyTypes includes "Egress" → ALL egress denied.
policyTypes []networkingv1.PolicyType When empty, K8s derives from whether ingress/egress are non-nil.

Using K8s's own types means anyone who has written a NetworkPolicy by hand already knows the schema; there is no operator-side abstraction to learn.

Common patterns

Allow only operator-namespace ingress (so dashboard probe works) + LLM-provider egress

spec:
  networkPolicy:
    enabled: true
    policyTypes: [Ingress, Egress]
    ingress:
      - from:
          - namespaceSelector:
              matchLabels:
                kubernetes.io/metadata.name: hermes-operator-system
    egress:
      - to:
          - namespaceSelector: {}
        ports:
          - { protocol: TCP, port: 443 }
      - to:
          - namespaceSelector: {}
        ports:
          - { protocol: UDP, port: 53 }

Ingress-only deny-all (egress unmanaged)

spec:
  networkPolicy:
    enabled: true
    policyTypes: [Ingress]
    ingress: []

Total deny-all

spec:
  networkPolicy:
    enabled: true
    policyTypes: [Ingress, Egress]
    # ingress + egress both nil → both denied

The CRD rejects enabled: true with no ingress and no egress rules, since that combination is almost always user error.

Important: CNI enforcement

Effective enforcement depends on the cluster's CNI. Calico, Cilium, and Antrea enforce NetworkPolicy. The default kindnet (kind clusters) creates the resource but does not enforce it in the data plane. The operator's E2E suite verifies the resource shape, label selectors, and toggle-off cleanup; it does not test enforcement (kindnet would not validate it).

To verify enforcement in a real cluster, exec into the agent pod and try to reach a forbidden destination:

kubectl -n hermes exec deployment/hermes-<name> -- \
    curl -sv --max-time 5 https://example.com
# should hang / time out under deny-all egress

What you can't express here

  • FQDN-based egress (e.g., allow only api.deepseek.com). Standard NetworkPolicy is IP/port/selector-based. Calico's GlobalNetworkPolicy with DNS-based selectors or Cilium's CiliumNetworkPolicy can express this; both are out of scope for the operator's CRD, but cluster admins can layer their own policies on top.
  • Cluster-wide policies across multiple agents. Each HermesAgent's NetworkPolicy is per-CR with no shared template. For uniform rules across many agents, manage that separately (Kyverno, a GlobalNetworkPolicy, etc.).