CRD-Based Policy
Manage PandoCore configuration declaratively with Kubernetes custom resources.
Instead of scattering environment variables across deployments, define a single PandoCorePolicy custom resource per namespace and apply it with kubectl. Policy values override environment variables automatically.
- Create a
pando-policy.yamlfile - Run
kubectl apply -f pando-policy.yaml - Done. Sensors pick up the policy within 30 seconds.
Quick Start
Create a file called pando-policy.yaml:
apiVersion: pandocore.io/v1alpha1
kind: PandoCorePolicy
metadata:
name: default
namespace: production
spec:
mode: isolate
sensitivity: high
Apply it:
kubectl apply -f pando-policy.yaml
Verify:
kubectl get pcp -n production
NAME MODE SENSITIVITY AGE
default isolate high 5s
Full Policy Reference
Every field is optional. Any field you leave out falls back to the environment, and then to the built-in defaults. On the node sensor that environment is the DaemonSet's own, which is node-wide rather than per-workload.
apiVersion: pandocore.io/v1alpha1
kind: PandoCorePolicy
metadata:
name: default # must be "default", one policy per namespace
namespace: production
spec:
# Operating mode, a cumulative ladder: each tier adds to the one before it.
# monitor : observe and log only
# alert : + notify (Slack / SIEM)
# isolate : + quarantine via NetworkPolicy, auto-release on recovery.
# Never deletes a pod. This is the node sensor's ceiling.
# debug : monitor plus verbose logging, no enforcement
mode: isolate # monitor | alert | isolate | debug
# Sensitivity preset. Maps to threshold multipliers: a higher preset is a
# tighter gate. Ignored if explicit thresholds are set below.
sensitivity: medium # low | medium | high
# Tighten the auto-learned drift gates. A lower number is a tighter gate.
# Both bounds are exclusive: greater than 0.0 and less than 1.0.
thresholds:
incremental: 0.15 # incremental (sample-to-sample) drift
cumulative: 0.45 # cumulative (baseline) drift
# Per-namespace evidence destinations. Credentials are Secret references,
# never literals: a Secret in this namespace, read by name and key.
integrations:
slackWebhook:
urlSecretRef:
name: pando-slack
key: url
pagerdutyEvents:
routingKeySecretRef:
name: pando-pagerduty
key: routingKey
Precedence
PandoCore uses a layered configuration model:
| Priority | Source | Description |
|---|---|---|
| 1 (highest) | CRD Policy | Values in PandoCorePolicy win |
| 2 | Environment Variables | Fallback when no CRD field is set. On the node sensor this is the DaemonSet's own environment, which is node-wide rather than per-workload. |
| 3 (lowest) | Built-in Defaults | Hardcoded sane defaults |
What a poll finds decides what happens next, and there are three answers rather than two:
- A policy exists: it is applied. A namespace with no policy of its own is left as it was, not reset.
- No
PandoCorePolicyexists anywhere on the cluster: every protected workload returns to its environment-variable configuration on that poll. - The read failed: whatever was already applied stays applied, so an API blip never disarms a node.
Spec Fields
mode
| Value | Description |
|---|---|
monitor |
Detect and record only. No alerts and no network action. |
alert |
Everything in monitor, plus Slack / portal alerts on trip. |
isolate |
Everything in alert, plus a NetworkPolicy that blocks pod egress (except DNS) on trip. This is the ceiling of the supported install. Policy is removed automatically once drift subsides. |
debug |
Verbose logging for troubleshooting. No response actions. |
sensitivity
| Value | Description |
|---|---|
low |
Most permissive. Good starting point for chatty workloads (JVMs, databases, batch jobs). |
medium |
Default. Balanced for typical production workloads. |
high |
Strictest. Catches subtler deviations. Use after a workload has been running cleanly for weeks, or on security-critical services. |
For finer numeric control, set thresholds below. The PANDO_SENSITIVITY environment variable is the other route (see Configuration), but on the node sensor it belongs to the DaemonSet and so applies node-wide rather than to one workload.
thresholds
Tighten the auto-learned drift gates. A lower number is a tighter gate. Thresholds can only tighten what learning derived, never loosen it; use sensitivity to loosen. Setting either field overrides the sensitivity preset for that gate. Both bounds are exclusive, and the CRD rejects a value at or outside them at admission, so a bad number is an error on your kubectl command rather than a surprise at runtime.
| Field | Type | Description |
|---|---|---|
incremental |
number, 0.0 < n < 1.0 | Incremental (sample-to-sample) drift threshold. |
cumulative |
number, 0.0 < n < 1.0 | Cumulative (baseline) drift threshold. |
integrations
Per-namespace evidence destinations. The admission webhook reads this block cluster-wide, resolves every Secret reference against a Secret in the policy's own namespace, and hands the resolved set to the controller, which delivers each namespace's evidence to that namespace's destinations.
Credentials are given by Secret reference, never as a literal. A PandoCorePolicy is a namespaced object, so a token written into one is readable by anyone who can read policies in that namespace. Non-credential settings (endpoint URL, index, site, facility, port, protocol) stay literal: they are configuration, not secrets.
| Field | Type | Description |
|---|---|---|
slackWebhook |
object | Slack incoming webhook. urlSecretRef (name, key) holds the webhook URL. |
pagerdutyEvents |
object | PagerDuty Events v2. routingKeySecretRef (name, key) holds the routing key. |
splunk |
object | Splunk HTTP Event Collector. url is the HEC base URL, without /services/collector/event. tokenSecretRef holds the HEC token. Optional: index, sourcetype, source, ackEnabled. |
elastic |
object | Elasticsearch _bulk. url is the cluster base URL, without /_bulk. authType is api_key or basic, with the matching apiKeySecretRef or basicAuthSecretRef. Optional: index, refresh, stdoutEnabled. |
datadog |
object | Datadog Logs API. site takes a short name, a bare site domain, or a full URL for a proxy. apiKeySecretRef holds the API key. Optional: service, source, clusterName, extraTags. |
syslog |
object | RFC-5424 syslog, the one destination with no credential. host, port and protocol (udp, tcp or tls). Optional: appName, facility, severity, enterpriseId. |
Every destination also takes enabled, which defaults to true. Setting it to false pauses that destination, which is not the same as leaving the block out: a paused destination still overrides the controller's own baseline, while an absent one lets the baseline apply.
Examples
Production Namespace
apiVersion: pandocore.io/v1alpha1
kind: PandoCorePolicy
metadata:
name: default
namespace: production
spec:
mode: isolate
sensitivity: medium
thresholds:
incremental: 0.12
cumulative: 0.35
integrations:
slackWebhook:
urlSecretRef:
name: pando-slack
key: url
Staging namespace (monitor only)
apiVersion: pandocore.io/v1alpha1
kind: PandoCorePolicy
metadata:
name: default
namespace: staging
spec:
mode: monitor
sensitivity: high
Verifying
Check that the CRD is registered:
kubectl get crd pandocorepolicies.pandocore.io
List policies across namespaces:
kubectl get pcp --all-namespaces
Check for policy pickup. On the node sensor, read the sensor on the node rather than a container in the pod:
DS=$(kubectl -n pando-system get ds -l app.kubernetes.io/name=pando-node-sensor \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n pando-system logs "ds/$DS" | grep -i "PandoCorePolicy"
Expected output:
PandoCorePolicy CRD found namespace=production name=default
Applied PandoCorePolicy mode=isolate resource_version="12345"
mode, sensitivity, thresholds and integrations are the whole of it, and they are all that PandoCore reads from a policy. Anything else under spec is not part of the schema, so the API server drops it before the object is stored. If you need something the four do not cover, talk to support.
Polling Interval
The sensor polls for policy changes every 30 seconds by default, through PANDO_POLICY_POLL_INTERVAL_SECS. It belongs to the DaemonSet and is therefore node-wide, so there is one poll interval covering the pods on a node rather than one per workload.
GitOps Integration
Because PandoCorePolicy is a standard Kubernetes resource, it integrates naturally with GitOps workflows:
- Store policy YAML in your Git repository alongside application manifests
- Review policy changes in pull requests
- Apply via ArgoCD, Flux, or any
kubectl applypipeline - Track policy history with
git log
Next Steps
- Configuration: every chart value with defaults and ranges
- Operating Modes: the monitor → alert → isolate ladder
- Production Hardening: sizing, posture and rollout guidance