Skip to main content
Docs / Controller

Controller

The in-cluster component that receives evidence and owns every outbound destination.

The controller is a StatefulSet that receives evidence from the node sensors over mTLS, writes it to a durable spool, and owns delivery to your SIEM, Slack and the portal. Centralising delivery is what keeps third-party credentials out of every node: the sensor holds none of them.

Install it first, and install it

The controller is its own Helm chart and the node sensor chart does not install it. Install the controller first: the sensor mounts its client certificate Secret with optional: false, so a sensor installed without a controller fails to start rather than running with nowhere to send evidence. See Getting Started for the order.

Install

helm install pando-controller \
  oci://us-central1-docker.pkg.dev/pandocore-prod/charts/pando-controller \
  --version 2.0.0 \
  --namespace pando-system --create-namespace \
  --set license.key="YOUR_LICENSE_KEY"

--version is not optional: installing an OCI chart without it fails outright rather than resolving to the newest release.

license.key is required unless you set license.existingSecret.name instead. portalURL is also required, and already carries the right default.

How Sensors Find It

Nothing to wire by hand. The sensor composes the controller's evidence URL from the controller.* values in its own chart, which default to the service name, namespace and port this chart publishes, so the two agree out of the box.

That composition is deliberate rather than a convenience. An early live run dialled a hand-written :8443 while the controller's Service has only ever published its evidence listener on :9443: the sensor reported a healthy channel, started its engines, and would have failed at the first detection, with hundreds of chart assertions green over it. If you override the controller's service name, namespace or port, override the matching controller.* value on the sensor rather than writing a URL.

The client certificate Secret, pando-controller-client-tls, is mounted by the sensor chart in pando-system. There is nothing to mirror into your workload namespaces, because nothing of PandoCore's runs there.

Ports

Port Transport Serves
9090 Plaintext /healthz and /metrics
9443 mTLS /evidence, /heartbeat, /siem/*

Security Posture

The controller runs unprivileged and holds no Kubernetes API access at all:

The Spool

Each replica gets a PersistentVolumeClaim, 2Gi by default. The default is 2 replicas, so that is two cluster-level volumes, not one per protected pod. It does not grow with the fleet.

The controller answers 202 only after an fsync, and replays whatever is on disk at startup. That is what makes it safe for a sensor to drop a record once it has been accepted.

The spool is a delivery hop, not a history

A spooled record is unlinked once every configured destination confirms it. The controller guarantees that accepted evidence is not lost in transit; it does not retain evidence for you. For retention, deliver to a SIEM or the portal and apply your retention policy there.

Per-Namespace Destinations

When destinations.enabled is set, the controller mounts a Secret written by the destinations mirror and re-reads it every 30 seconds. The mirror is an unprivileged Deployment installed by the sensor chart, and it exists for a privilege reason: resolving a policy's secretRef needs cluster-wide secrets get, and that grant must not sit on a hostPID pod running as uid 0 on every node. One replica holds it instead.

If mirror.enabled=false on the sensor chart, nothing writes that Secret: the mount is empty, the controller logs it once, and each namespace falls back to the portal and the environment baseline. The mount is optional by design so the controller still starts, which means per-namespace integrations declared in a policy fail quietly rather than loudly. Check the controller's log line if a policy's destination seems to do nothing.

Next Steps