Skip to main content
Docs / CRD Policy

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.

Three-Step Setup
  1. Create a pando-policy.yaml file
  2. Run kubectl apply -f pando-policy.yaml
  3. 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:

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"
The spec is exactly these four fields

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:

Next Steps