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
WorkloadNetworkPolicyProposalobjects. -
Istio backend (
controller.provider.nameistio): 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.namecalicoorcilium): 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, orcilium). 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(inmonitororprotectmode, 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
WorkloadNetworkPolicyProposalwithnetworkenforcer.kubewarden.io/promote=monitor(see Transitions), or create aWorkloadNetworkPolicywhose.spec.modeismonitor. -
Ensure
.spec.mode: monitorand that the backend-specific rules are populated (spec.istioorspec.kubernetes).
-
-
Leave
-
Monitor → Protect: patch the
WorkloadNetworkPolicyand 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: monitorrecords violations without blocking traffic. The Istio backend still renders anAuthorizationPolicy, annotated withistio.io/dry-run=trueso ztunnel does not deny the flow. The Kubernetes backend creates noNetworkPolicy, 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/AuthorizationPolicywithaction: ALLOWand 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/NetworkPolicywith the same name and namespace as theWorkloadNetworkPolicy. The CNI (Calico or Cilium) enforces it. Switching.spec.modeback tomonitordeletes thatNetworkPolicy.
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 existingWorkloadNetworkPolicyand set.spec.mode: protect.
-
-
Leave
-
Protect → Monitor: update the
WorkloadNetworkPolicyand set.spec.mode: monitor. -
Protect → Learn: delete the
WorkloadNetworkPolicy. Learning resumes on the next observed flow. See Transitions.
-
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=monitorornetworkenforcer.kubewarden.io/promote=protecton theWorkloadNetworkPolicyProposal. -
The proposal reconciler copies the backend spec onto a new
WorkloadNetworkPolicyin the same name and namespace, sets.spec.modefrom the label value, and addsnetworkenforcer.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
WorkloadNetworkPolicyalready exists withnetworkenforcer.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 aWorkloadNetworkPolicyProposalagain. 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
WorkloadNetworkPolicyby hand withoutnetworkenforcer.kubewarden.io/promoted-fromdoes 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 rejectsWorkloadNetworkPolicyProposalCREATE/UPDATE whennetworkenforcer.kubewarden.io/promoteis present and is set to anything other thanmonitororprotect. 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 |
|---|---|---|
|
|
|
|
|
|
|
|
|
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 tohttps://<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 attelemetry.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, withaction=monitororaction=protect. -
policy_violation_acknowledged— emitted after anetworkenforcer.kubewarden.io/acknowledge-<id>annotation is applied and the status patch succeeds.
Quick mapping to CRDs
| Phase | Custom resources | What changes |
|---|---|---|
Learn |
|
Observed flows are merged into proposals (per workload and direction on Kubernetes; ingress-only on Istio). Nothing is enforced. |
Monitor |
|
Violations are recorded (and, on Istio, an |
Protect |
|
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.