?mode=author), where the graph you are reasoning about is the same graph the policy will act on. Both call the endpoints below.
The loop
1
Draft
Build the policy from the graph — pick a workload, pick the edges you want to keep, and the editor renders CNP or CCNP YAML. The applications endpoint backs the picker so you can select a logical app rather than enumerating pods.
2
Dry-run
POST .../security/policies/dry-run scores the draft against the window’s real edges. Nothing is applied.3
Apply
POST .../security/policies writes it. Enforcement is immediate — the moment the apiserver accepts the object, Cilium acts on it.4
Verify
Coverage says which workloads the policy now selects; flow matches proves it is matching real traffic; the topology graph repaints those edges as
policy-protected.5
Watch for drift
Drift compares what policies declare against what the network actually did, and is the thing to check after any change to labels or workloads.
Dry-run
The request carries the draft YAML, the workload you are reasoning about, and the window to score against:wouldAllow (matched by the selector and permitted by a rule), wouldDeny (matched but no rule fits), and unaffected (the selector does not match) — each edge carrying direction, peer, port, protocol, bytes and its current observedAction. A summary reports edgesAnalysed and whether the policy acts on ingress, egress or both.
Two response fields matter more than the buckets:
parseErroris set while the YAML is incomplete. The editor polls as you type, so a half-written policy returns a hint rather than an error, and the verdict lists stay empty.warningssurface blast radius that per-edge verdicts cannot. Each has aseverityofwarnordanger, a title, detail, and the notable workloads involved.
supervisor:GetSummary, so anyone who can look at flows can evaluate a draft without being able to apply it.
Apply and delete
POST .../security/policies takes namespace, name and yaml. Semantics worth being explicit about:
- Replace, not merge. The YAML in the body is the new state of the object. There is no merge with what is live, so a field you removed from the draft is removed from the cluster.
- Server-side apply with a stable field manager, so repeated applies update in place and Cilium’s own
managedFieldskeep the change attributable alongside the platform’s audit log. - Two kinds only.
CiliumNetworkPolicyandCiliumClusterwideNetworkPolicyare accepted; anything else is rejected rather than silently widening what this endpoint can write. - CCNP is expressed by an empty namespace (the UI sends the placeholder
cluster).
DELETE .../security/policies?name=<name>&namespace=<namespace> removes one, with the same empty-namespace convention for cluster-wide policies. Both writes require supervisor:Manage.
Coverage
GET .../security/policies/coverage returns the map from workload to the policies covering it — name, namespace and kind, so a namespaced CNP is distinguishable from a cluster-wide CCNP at a glance. This is the same index the topology graph uses to paint an edge policy-protected instead of unprotected, and the same one that fills coveredBy on a node.
Drift
GET .../security/policies/drift compares what policies declare against what the datapath did, over the analysis window:
policiesChecked and edgesAnalysed are the reason to trust an empty result: zero drifts with 13 policies and 361 edges analysed means the policies agree with reality, whereas zero drifts with edgesAnalysed: 0 means nothing was compared and you should be looking at freshness instead.
The common driftType is policy-blocks-cilium-allows — the policy’s rules would deny an edge that was observed as allow or unprotected. Nine times out of ten that is the coverage gap below rather than a broken datapath: the policy is correct, but the workload is not being recognized as one it selects.
Per-policy detail
Four endpoints answer “what does this specific policy do”, all keyed byname plus namespace (empty or cluster for a CCNP):
flow-matches is the proof-of-life check: a policy with matched: 0 over a busy window either selects nothing or guards a path nobody uses. history reads the audit log, so it requires the platform database to be configured and only covers changes made through Celum — a kubectl apply by hand will not appear.
Identities — what selectors actually match
A policy selector matches Cilium labels, not Kubernetes objects, and the two are easy to confuse.GET .../security/identities/by-workload?namespace=…&workload=… returns the identity behind a workload with the exact label set:
security/identities/{id} does the same lookup from a numeric identity, which is what topology edges carry.
Coverage blind spots
Coverage, drift and the topology paint all rest on the same workload-identity index, and it has three documented limits. All three make policies look less effective than they are:
In every case
security/policy/flow-matches is the authority: it counts flows the policy matched, independent of the index.
Permissions
Read access covers every namespace on the supervisor — see the permissions note — while writing needs the broader
supervisor:Manage. There is no per-namespace policy-write scope; if you need one, model it as separate supervisors. See Permissions model and Projects for how tenancy is expressed elsewhere.
Celum AI can drive the safe half of this loop directly:
dry_run_policy evaluates a draft without applying it, list_security_policies and get_policy_detail inspect what exists, and get_policy_drift reports where declarations and observed traffic disagree. Applying and deleting stay manual.