Everything else in the CLI reports. This is the one command whose output you put into a cluster, so the rest of this page is mostly about the ways that can be wrong and what the command does about each of them.
# Review what the baseline supports, in the current namespace
pahlevan netpol
# Every namespace
pahlevan netpol --all-namespaces
# What applying this would change, against the policies already there
pahlevan netpol --diff
# The manifests alone, once you have read the review
pahlevan netpol -o yaml | kubectl apply -f -
The command applies nothing, ever. There is no --apply flag. The default
output is the review rather than the manifests, because the manifests are the
part somebody pipes into kubectl and the argument of this page is that they
should not be piped anywhere unread.
What applying one does
A NetworkPolicy is allow-only. The moment one selects a pod, every connection in a named direction that no rule permits is dropped: connections that were working a second earlier, connections from clients that were quiet during the learning window, and connections nobody observed because they happen less often than the window is long.
That is not a gradual tightening. It is a cut down to a list derived from one window, and it takes effect on the pods the policy selects the instant the object is created.
The three ways a generated policy lies, and what happens instead
An address that resolves to nothing
A learned baseline is a list of addresses. A NetworkPolicy is a list of label
selectors. An address the cluster cannot name has no selector, so it becomes an
ipBlock with a single-host CIDR, and the review says the rule is a literal
address that is wrong the day that address is reassigned.
It never becomes a namespaceSelector inferred from a subnet. A subnet is not
an identity.
A node address gets the same treatment for a different reason: NetworkPolicy has no node selector, so a node can only ever be an address.
A Service address becomes a selector over the pods behind the Service, because a NetworkPolicy cannot name a Service at all. The review says how many pods that is.
A selector that would be wider than the evidence
This is the one that matters.
Every label set derived from a group of pods selects at least those pods. The
question is what else it selects. In a namespace where app=api is on the API
deployment, on its debug deployment and on last quarter's canary, a rule built
from watching the first one permits traffic to all three. Nobody watched the
other two.
So the rule is: a label set may be written into a policy only if, applied to the namespace as it actually is, it selects exactly the pods it was derived from and nothing else. When it does not, no policy is generated for that workload and the review names the pods that made it unsafe:
prod/Deployment/api
-------------------
policy none
observed 12 learned destination(s), 0 written into rules
blocked: a podSelector on app=api would also select prod/debug-7d9f4,
which were never observed
Fixing that is a change to the workloads, not to this command: give the two deployments labels that tell them apart, and regenerate.
Labels a controller rewrites are dropped before the check, because a policy
naming pod-template-hash matches the pods it was generated from and nothing
after the next rollout, and a policy that selects zero pods enforces nothing.
When dropping one is what made the selector unsafe, the review says so.
A baseline that is shorter than the workload's cycle
A baseline only contains what happened during the learning window. A policy generated from it denies anything rarer than that window is long.
For a workload owned by a CronJob, pahlevan netpol walks the pod's owner
chain to the schedule and runs the pair through the same logic the operator
uses to decide how long to learn for. A monthly job learned over an hour gets:
warn: this workload fires on a 720h0m0s cycle and the learning window is
capped at 168h0m0s, so the baseline cannot contain a whole period. A policy
generated from it denies whatever that period does
Every workload, cycle or not, gets the shorter version of the same caveat with its own window in it.
Ingress is derived, not observed
The agents record a connection at the end that made it, so the learned baseline is egress. An ingress rule here is therefore a derivation: workload A was observed connecting to workload B's pods on port 8080, so B's policy permits ingress from A on 8080.
That is sound as far as it goes and it is not the whole truth about B. Any
client of B that Pahlevan did not observe, including clients outside the
cluster, is not in the rule and would be denied. The review says this once per
derived rule. --direction egress leaves ingress out entirely.
Other things the review tells you
- DNS. Name resolution happens before the connection the baseline recorded, so a baseline that captured the connection and not the lookup produces a policy that permits the destination and denies finding it. A generated egress policy with no flow to port 53 in it says so.
- Replicas. A selector names a workload, not a pod, because a replica is not an identity a policy can name. When the baseline came from fewer pods than the workload has, the review says how many will be enforced on evidence that is not theirs.
- A workload that made no outbound connection at all. That produces a policy permitting none, which is an honest reading of the baseline and a strong control. It is marked as such rather than emitted quietly.
- Destinations that did not make it. When part of a baseline could not be written down, the count is in the review. When none of it could, no policy is generated at all: a policy narrower than the evidence is how this command would take a workload down.
Reviewing before applying
--diff compares each generated policy against the one already in the cluster
under the same name and prints what would be replaced. A generated policy that
displaces rules a person reasoned about is invisible in the YAML, which says
only what the new policy is:
pahlevan netpol --diff
Against the NetworkPolicies already in the cluster:
~ prod/pahlevan-deployment-web already exists and would be replaced:
...
- app: something-else
+ app: web
Requirements
- A CNI that enforces NetworkPolicy. Creating the object in a cluster whose CNI ignores it changes nothing, which is its own kind of surprise.
- Kubernetes 1.21 or newer, because peers are written with a
namespaceSelectoronkubernetes.io/metadata.name, the label the API server sets on every namespace from that release. - Containers that have reached a learned baseline.
pahlevan profile list -Asays whether any have.
Flags
| Flag | Does |
|---|---|
-n, --namespace |
Namespace to read baselines from |
-A, --all-namespaces |
Read every namespace |
-o, --output |
report (default), yaml for the manifests alone, json for the review as data |
--direction |
both (default), egress, ingress |
--diff |
Show what applying this would change against what is in the cluster |
--name-prefix |
Prefix for generated policy names, pahlevan- by default |
Nothing in Pahlevan reconciles a generated policy. It is a starting point handed to whoever owns the namespace, and once applied it is theirs.