Skip to main content

Runtime Push-Sink Collector

Self-Hosted Kubernetes

AIControls can receive Runtime Controls findings two ways: by listing and watching the sensor's Report objects from the Kubernetes API server, or by receiving them as a live gRPC stream pushed directly from the sensor. This page covers the second path — the collector — as a piece of cluster infrastructure: what it listens on, how its certificates are provisioned, and how it composes with the rest of Runtime Controls. For what Runtime Controls observes and why, see Runtime Controls concepts; for the install walkthrough, see Enable Runtime Controls.

Self-managed installations only

Workload Trust is available only in private, self-managed AIControls installations, on premises or in a cloud cluster that you run. It needs a dedicated cluster that you run, with Kyverno, Kyverno Runtime, and cluster OIDC and RBAC access in place. Nirmata-hosted (SaaS) workspaces don't include it. See Feature availability.

What it is​

The collector is a gRPC server, embedded in every AIControls replica, that implements a single client-streaming RPC: a Kyverno runtime sensor daemon opens one long-lived stream per pod and pushes each finding onto it as it is produced. This is an additive, lower-latency alternative to the sensor's other ingestion path — AIControls listing and watching its Report custom resources from the API server — not a replacement for it. Both paths feed the same downstream processing (profile rollup, audit, notifications), so a finding looks identical in the admin console regardless of which path delivered it.

The trade-off is replay. The API-server path re-lists on reconnect, so a missed watch event is eventually caught up. The push path has no such buffer: a finding in flight when the daemon pod dies or the stream drops can be lost — the sensor's own EventsDropped{source="pushsink"} metric tracks this on the sending side.

How it composes with OpenReports ingest​

Never enable both paths for the same sensor. The collector and the API-server path use independent series identities for the same underlying observation, so a DaemonSet reporting through both at once has every finding counted twice — inflating both the reconciliation table and the findings rollup. Exactly one must be active per sensor:

  • The bundled sensor subchart (installed by default alongside AIControls) is hard-configured to push only — its daemon.reports.enabled is set to false in the chart's own child values, so a bundled sensor with the collector turned off would silently produce zero findings. A Helm template guard fails the render loudly if you disable the collector while the bundled subchart is still installed, rather than letting that combination ship.
  • A customer-managed sensor (your own separate kyverno-runtime release) defaults to writing Report objects. If you point it at this collector instead, set that release's own daemon.reports.enabled: false so exactly one path stays active.

Independently of which path delivers a finding, the observation categories (runtimeControls.categories.ungovernedLLM / agentExec / mcpExec / ungovernedEgress) apply the same way: a category toggle controls which RuntimePolicy observation shapes AIControls authors in the cluster in the first place, so disabling one stops the sensor from producing that shape's findings at all — upstream of both ingestion paths, not specific to either one.

Enable or disable the collector​

runtimeControls.collector.enabled defaults to true alongside the bundled sensor subchart, so a default install has this listener already running with no values to add. It also requires runtimeControls.enabled to be true — the collector's certs, Service, and container port are provisioned by this sub-flag alone, but the listener itself only starts once both flags agree:

runtimeControls:
enabled: true
collector:
enabled: true

Turning the collector off explicitly while the bundled subchart is still installed fails the Helm render outright, rather than shipping a sensor with nowhere to push its findings. To run without the collector, disable the bundled sensor subchart too (kyverno-runtime.enabled: false) and manage your own sensor writing Report objects instead — see Manage the sensor yourself.

Ports​

WhatDefaultConfig key
Collector listen address (in-process):9083runtimeControls.collector.listenAddr
In-cluster Service, release-scoped nameSame port, under {{ .Release.Name }}-aicontrols's own Service—
In-cluster Service, fixed name (bundled subchart only)Same port, aicontrols-runtime-collectorruntimeControls.collector.subchartServiceName

The fixed-name Service exists only because the bundled sensor subchart's own child values (kyverno-runtime.daemon.push.target, set in this chart's values.yaml) are literal text with no access to this chart's release-scoped naming — they need a name they can hardcode. It fronts the exact same pods as the chart's main Service; it's an additional Service object, not a separate deployment. A customer-managed sensor pointed at the release-scoped Service name keeps working unchanged whether or not the bundled subchart is installed.

If you change listenAddr, the Service port moves with it automatically — there's nothing else to update.

The self-generated PKI​

The push sink is mutual-TLS-only by design on the sending side — the sensor's push client has no plaintext mode. AIControls refuses to start the listener at all if its certificate, key, or client CA is missing, rather than exposing an unauthenticated ingestion endpoint.

By default (existingSecretName left empty), the chart generates and persists everything needed without a cert-manager dependency: a single-purpose CA, a server certificate for this listener, and a client certificate for the sensor to present. All three come from one CA, minted together in a single Helm template render — generating them independently would risk each one signing against a different random CA on first install. Three Kubernetes Secrets carry the result:

SecretContentsUsed for
<release>-aicontrols-runtimecontrols-collector-caca.crt, ca.keyKept so a later helm upgrade can mint a replacement server or client certificate off the same CA
<release>-aicontrols-runtimecontrols-collector-servertls.crt, tls.key, ca.crtMounted directly as this listener's TLS bundle — its own certificate plus the CA to verify presented client certificates against
<release>-aicontrols-runtimecontrols-collector-clienttls.crt, tls.key, ca.crtThe certificate a customer-managed sensor's push.tls.secretName needs — copy it out, per the helm install/helm upgrade NOTES output

When the bundled sensor subchart is installed, the client certificate is also copied into a second, fixed-name Secret (aicontrols-runtime-collector-client, overridable via runtimeControls.collector.subchartClientSecretName) that the subchart's own literal child values point at directly — no manual copy step for that case.

Every one of these Secrets carries helm.sh/resource-policy: keep: helm uninstall never deletes them, and a plain helm upgrade reuses whatever already exists rather than regenerating it. That matters because regenerating the CA or the server certificate would invalidate every certificate already trusted or presented by a connected sensor.

Upgrade caveat: enabling the bundled subchart on an existing release​

The server certificate's SAN list only covers the fixed-name Service (aicontrols-runtime-collector) on a freshly minted certificate. If you ran the collector against a customer-managed sensor first and are now turning on the bundled subchart (kyverno-runtime.enabled: true) on that same release, the persisted server certificate predates that SAN and its TLS handshake against the fixed-name Service will fail hostname verification. Force a fresh certificate by deleting the server Secret before the next upgrade:

kubectl delete secret <release-name>-aicontrols-runtimecontrols-collector-server -n <namespace>
helm upgrade <release-name> oci://ghcr.io/nirmata/charts/aicontrols -n <namespace> -f values.yaml

The CA and client Secrets are untouched by this — only the server certificate needs to be re-minted, since only its SAN list is affected.

Bring your own PKI​

Set existingSecretName to skip all three chart-generated Secrets and mount a Secret from your own PKI or cert-manager workflow instead:

runtimeControls:
collector:
existingSecretName: my-collector-tls

The Secret must carry the same three keys as the chart-generated server Secret: tls.crt and tls.key (this listener's own certificate and key) plus ca.crt (the CA that also signed the client certificate you hand to the sensor's push.tls.secretName). With this set, the chart does not generate a client certificate either — minting one signed by the same CA, and getting it to the sensor, is your responsibility.

Verify it's working​

The Health → Runtime tab and the GET /api/v1/runtime/status endpoint report capability the same way regardless of ingestion path — see Runtime Controls reference for the three-valued state contract. There is no separate "is the collector connected" indicator distinct from ordinary finding freshness: if findings are landing (check GET /api/v1/runtime/findings or the workload's Runtime tab) and ingestLag isn't climbing, the active path — whichever one your sensor is configured for — is working.

To confirm findings are specifically taking the push path rather than the API-server path, check the sensor's own logs and metrics for its push client — a connected stream and a non-growing EventsDropped{source="pushsink"} counter confirm delivery from the sending side.

Limitations​

  • One path per sensor, always. Running both against the same DaemonSet double-counts every finding it produces — see How it composes with OpenReports ingest above.
  • No per-message replay. A finding queued on the sensor's push client when its pod dies, or in flight when the stream drops, does not automatically resend. The API-server path does not have this gap, since it re-lists on reconnect.
  • Only one bundled-subchart release per namespace by default. The fixed-name Service and client Secret the subchart depends on are not release-scoped; a second AIControls release with the bundled subchart enabled in the same namespace collides on both objects unless you override subchartServiceName and subchartClientSecretName and pass matching overrides to that second release's kyverno-runtime.daemon.push.* values.
  • Every replica listens independently. The collector is not a leased singleton — each AIControls pod runs its own listener, and the in-cluster Service load-balances the sensor's stream to whichever replica it connects to. Writes from any replica land in the same shared store, so this has no effect on finding completeness.

See also​