Skip to main content
Celum treats a network policy as something you can check before you mean it. The flow store already knows every conversation the selected workload had in the last hour, so a draft policy can be evaluated against real traffic before it reaches the apiserver — which is the difference between “this looks right” and “this would have broken these 14 edges”. Two surfaces, one API. The policy list lives on Security → Network Policies (?tab=policies); authoring happens on Security → Topology in author mode (?mode=author), where the graph you are reasoning about is the same graph the policy will act on. Both call the endpoints below.
The list used to be reachable both from Networking and as a mode inside Flow Visibility — two answers to “where do I edit a policy”, and two places to keep correct. It was consolidated onto Security in September 2026. Old links still work: /networking?tab=policies and /security/policies forward here and carry their query string with them, so a bookmark that had scoped itself to a cluster stays scoped.

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.

Author mode

Authoring was a separate screen — Policy Studio — until it merged into the Topology tab in August 2026. It is the same graph in both modes; author mode adds the editor, and constrains the scope so that what the editor renders is answerable. /s/{supervisor}/policy-studio still works and forwards here with ?tab=topology&mode=author, carrying the rest of its query string — ?ns=, ?since=, ?cluster= — because those are the scope a shared link existed to set. Three constraints are worth knowing before a link surprises you:
  • A namespace must be chosen explicitly. Author mode never starts on the “all namespaces” wildcard, and a link that arrives carrying ns=* is treated as having chosen nothing. A policy drafted against every namespace at once is not a policy anyone can reason about, so the mode re-asks.
  • ?ns= locks the namespace on arrival when it can be inferred — following the author link from a policy list that holds a single namespace lands you already scoped to it.
  • It authors against the supervisor, not a guest. A link that carries mode=author while pointed at a guest cluster falls back to explore mode, because the authoring routes only exist at the supervisor address. From a guest-scoped policy list the author link therefore points at the supervisor the guest hangs off, rather than pretending the editor can be aimed at the guest.

Dry-run

The request carries the draft YAML, the workload you are reasoning about, and the window to score against:
The response sorts every observed edge into four buckets — wouldAllow (matched by the selector and permitted by a rule), wouldDeny (matched but no rule fits), wouldBlock (removed by an explicit deny rule — access another policy grants, kept separate because taking access away is categorically different from an allow-list not covering an edge), 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. Three response fields matter more than the buckets:
  • parseError is 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.
  • mode says which analysis ran, and reading the buckets without it is the most dangerous mistake available here. verdicts means an affectedWorkload was given and the edges were scored against its observed traffic; lint means none was, so what came back is a whole-namespace blast-radius advisory and edgesAnalysed is 0 by design. An empty verdict list from lint reads as “this policy affects nothing” — the opposite of what it means.
  • warnings surface blast radius that per-edge verdicts cannot. Each has a severity of warn or danger, a title, detail, and the notable workloads involved.
The warning to never dismiss is the whole-namespace one. A CiliumNetworkPolicy with an empty endpointSelector selects every pod in the namespace, which turns it into a default-deny for workloads you never considered. This has taken down guest-cluster control planes that happened to share a namespace with the workload being secured. If dry-run raises it at danger, scope the selector before you apply.
Dry-run is a read as far as authorization is concerned — it runs under security:Read, 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 managedFields keep the change attributable alongside the platform’s audit log.
  • Two kinds only. CiliumNetworkPolicy and CiliumClusterwideNetworkPolicy are 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 security:Write.

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 by name 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:
When a policy does not behave as expected, compare this label set against the selector before changing anything else. 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 security:Write. 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.