Kubewarden Network Enforcer phases: learn, monitor, protect

This document explains the three phases of Network Enforcer and how to move between them.

Learn phase

What happens during this phase

  • Traffic is not blocked.

  • Network-enforcer observes flows after the controller is running, and turns those flows into WorkloadNetworkPolicyProposal objects.

  • Istio backend (controller.provider.name istio): Istio ambient L4 authorization is enforced on the receiving side, so learning creates only an ingress proposal on the destination workload.

  • Kubernetes backend (controller.provider.name calico or cilium): each observed connection updates two proposals, one per endpoint and direction.

    • Source workload: an egress proposal that allows traffic to the destination.

    • Destination workload: an ingress proposal that allows traffic from the source.

Learn is based on flows observed after the controller is running. Network-enforcer does not reconstruct traffic that happened before it started.

That means:

  • If a workload was already running before network-enforcer started, we cannot propose the full set of peers it already talked to in the past.

  • You will only learn flows that occur after the controller is running (for long-lived pods that rarely open new connections, you may learn little or nothing until you generate traffic or restart the workload).

Operationally, if you want complete proposals, install network-enforcer first and then (re)start the workloads you want to learn, or generate the traffic you care about while the controller is running.

How to enter and leave the Learn phase

  • Enter

    • Deploy the controller with a provider (controller.provider.name: istio, calico, or cilium). See Backend selection.

    • No per-workload configuration is required. Proposals are created as flows are observed.

  • Leave

    • Promote the proposal. See Transitions.

    • Promotion creates a WorkloadNetworkPolicy (in monitor or protect mode, depending on the label value) and then deletes the proposal.

CRDs created/updated during this phase

  • Created/updated: WorkloadNetworkPolicyProposal

    • Name format is <kind>-<name>-<ingress|egress>, where <kind> is the owning workload kind lowercased (deployment, statefulset, daemonset), <name> is the owning workload name as-is, and the direction suffix is lowercase. Examples: deployment-http-client-egress, deployment-http-server-ingress.

    • Istio produces only the ingress name (…-ingress) for each destination workload. There is one proposal per workload and direction for the Kubernetes backend.

Monitor phase

What happens during this phase

  • Traffic is not blocked.

  • When an observed flow is not allowed by the WorkloadNetworkPolicy:

    • the flow still goes through

    • a violation is recorded on the policy status with action: monitor

    • the same event is also exported as an OpenTelemetry log (see Where OTLP events go)

Monitor evaluation is best-effort. The Istio backend relies on ztunnel dry-run logs, which cover inbound TCP connections through the mesh; UDP and traffic that never hits ztunnel are not observed. The Kubernetes backend reuses the same observed flows that drive learning: if a flow is never seen by the scraper, it is never reported as a violation.

How to enter and leave the phase

  • Enter

    • Promote a WorkloadNetworkPolicyProposal with networkenforcer.kubewarden.io/promote=monitor (see Transitions), or create a WorkloadNetworkPolicy whose .spec.mode is monitor.

    • Ensure .spec.mode: monitor and that the backend-specific rules are populated (spec.istio or spec.kubernetes).

  • Leave

    • Monitor → Protect: patch the WorkloadNetworkPolicy and set .spec.mode: protect.

    • Monitor → Learn: delete the WorkloadNetworkPolicy. Learning resumes on the next observed flow. See Transitions.

CRDs created/updated during this phase

  • Used/updated: WorkloadNetworkPolicy

    • .spec.mode: monitor records violations without blocking traffic. The Istio backend still renders an AuthorizationPolicy, annotated with istio.io/dry-run=true so ztunnel does not deny the flow. The Kubernetes backend creates no NetworkPolicy, so the CNI does not enforce anything.

    • Status fields:

      • status.violationCount: count of violation records ever observed for this policy, including entries that have since been trimmed or cleared because the flow is now allowed.

      • status.activeViolationCount: number of currently active (non-cleared, non-acknowledged) records.

      • status.violations: the most recent active records, capped at 100. Oldest entries are dropped when the cap is reached.

      • status.acknowledgedViolations: the most recent acknowledged records, also capped at 100.

Protect phase

What happens during this phase

  • Traffic is blocked when it violates the allow-list.

  • On a denied flow:

    • a violation is recorded on the policy status with action: protect

    • the same event is also exported as an OpenTelemetry log (see Where OTLP events go)

    • the connection fails (timeout, reset, or "connection refused", depending on the CNI or mesh)

    • depending on what was blocked this can cause application errors, crash loops, or failed jobs

Enforcement is backend-specific:

  • Istio backend: the controller creates and keeps in sync a security.istio.io/AuthorizationPolicy with action: ALLOW and the learned principals/ports. In protect mode the dry-run annotation is removed, so ztunnel enforces the policy on inbound connections.

  • Kubernetes backend: the controller creates and keeps in sync a networking.k8s.io/NetworkPolicy with the same name and namespace as the WorkloadNetworkPolicy. The CNI (Calico or Cilium) enforces it. Switching .spec.mode back to monitor deletes that NetworkPolicy.

Both rendered objects are owned by the WorkloadNetworkPolicy (ownerReferences). Deleting the WorkloadNetworkPolicy garbage-collects the AuthorizationPolicy or NetworkPolicy.

The Istio backend learns and enforces only on the destination (ingress), so there is no source-versus-destination split. Kubernetes NetworkPolicy is directional and is applied independently on each endpoint. An egress deny is enforced at the source. The packet never arrives, so the destination’s ingress policy records nothing. That is why, after you switch both Kubernetes policies to protect, a violating flow shows up only on the source (egress) WorkloadNetworkPolicy.

How to enter and leave the phase

  • Enter

    • Promote a proposal with networkenforcer.kubewarden.io/promote=protect, or patch an existing WorkloadNetworkPolicy and set .spec.mode: protect.

  • Leave

    • Protect → Monitor: update the WorkloadNetworkPolicy and set .spec.mode: monitor.

    • Protect → Learn: delete the WorkloadNetworkPolicy. Learning resumes on the next observed flow. See Transitions.

CRDs created/updated during this phase

  • Used/updated: WorkloadNetworkPolicy (same CRD as monitor; only .spec.mode changes).

  • Created/updated by the reconciler:

    • Istio backend: AuthorizationPolicy (security.istio.io/v1)

    • Kubernetes backend: NetworkPolicy (networking.k8s.io/v1)

Transitions

Promotion is explicit. Network-enforcer does not turn a proposal into a policy until you ask.

  • Promote a proposal

    • Set the label networkenforcer.kubewarden.io/promote=monitor or networkenforcer.kubewarden.io/promote=protect on the WorkloadNetworkPolicyProposal.

    • The proposal reconciler copies the backend spec onto a new WorkloadNetworkPolicy in the same name and namespace, sets .spec.mode from the label value, and adds networkenforcer.kubewarden.io/promoted-from: <proposal-name> so the promotion relationship is explicit.

    • The proposal is then deleted.

  • Leftover proposals

    • After promotion, a scraper can recreate a proposal at the same name before caches catch up. If a WorkloadNetworkPolicy already exists with networkenforcer.kubewarden.io/promoted-from=<proposal-name>, the leftover proposal is deleted. That usually happens as soon as the recreate is watched; if both objects sit idle, it is picked up on the next controller-runtime resync (defaulted to 10h).

  • Return to learning

    • Delete the WorkloadNetworkPolicy. The next observed flow creates or updates a WorkloadNetworkPolicyProposal again. If a leftover proposal is still around after you delete the policy, learning resumes by updating that object; delete it first if you want a clean slate.

    • Creating a WorkloadNetworkPolicy by hand without networkenforcer.kubewarden.io/promoted-from does not remove a same-named proposal. Delete the proposal yourself in that case.

  • ValidatingAdmissionPolicy

    • The Helm chart installs a cluster-scoped ValidatingAdmissionPolicy (and binding) that rejects WorkloadNetworkPolicyProposal CREATE/UPDATE when networkenforcer.kubewarden.io/promote is present and is set to anything other than monitor or protect. The check runs only when the label is set; proposals without the label are admitted as usual.

Backend selection

controller.provider.name selects both the scraper that feeds learning and the spec.backend written onto generated objects:

controller.provider.name spec.backend Generated payload

istio

istio

spec.istio (rendered as an Istio AuthorizationPolicy)

calico

kubernetes

spec.kubernetes (a standard NetworkPolicy spec)

cilium

kubernetes

spec.kubernetes (a standard NetworkPolicy spec)

spec.backend is immutable. You cannot convert an Istio-backed proposal or policy into a kubernetes one (or the other way around) in place.

Exactly one of spec.istio or spec.kubernetes must be populated, matching spec.backend.

Where OTLP events go

Violations always land on WorkloadNetworkPolicy status (status.violations).

When telemetry is enabled, the controller also exports OpenTelemetry logs:

  • Default Helm chart (telemetry.collectorStrategy=default): an in-cluster OTEL collector. The controller sends OTLP/gRPC to https://<fullname>-otel-collector.<namespace>.svc.cluster.local:4317. The collector’s logs pipeline dumps records with the debug exporter.

  • External collector (telemetry.collectorStrategy=external): the same exporter points at telemetry.externalCollector.endpoint.

  • Disabled (telemetry.collectorStrategy=none): no OTLP export; status still records violations.

Event names:

  • policy_violation_observed — emitted for each monitor or protect violation, with action=monitor or action=protect.

  • policy_violation_acknowledged — emitted after a networkenforcer.kubewarden.io/acknowledge-<id> annotation is applied and the status patch succeeds.

Quick mapping to CRDs

Phase Custom resources What changes

Learn

WorkloadNetworkPolicyProposal

Observed flows are merged into proposals (per workload and direction on Kubernetes; ingress-only on Istio). Nothing is enforced.

Monitor

WorkloadNetworkPolicy (.spec.mode: monitor)

Violations are recorded (and, on Istio, an AuthorizationPolicy is rendered but not enforced). Traffic is not blocked.

Protect

WorkloadNetworkPolicy (.spec.mode: protect) plus an Istio AuthorizationPolicy or Kubernetes NetworkPolicy

Violations are recorded and blocked at the data plane.

Important: the WorkloadNetworkPolicy CRD only supports monitor and protect (.spec.mode, defaults to monitor). Learn is implemented as WorkloadNetworkPolicyProposal generation.