Kubernetes Operator
The DriftWise Kubernetes Operator reports live cluster resources to DriftWise for drift detection against Terraform-managed infrastructure. Resources discovered by the operator appear alongside cloud resources in your drift results.
How It Works
- The operator runs in your cluster and watches for resources
- Periodically POSTs discovered resources to
POST /api/v2/operator/report - DriftWise stores them as live resources linked to the cluster
- Drift detection compares them against your Terraform state
Authentication
The operator endpoint requires an org-scoped API key. OIDC
authentication is rejected with 403 — this is a machine-to-machine
path where human attribution would just be misleading.
Create an API key for the operator through the dashboard Settings page, then store it as a Kubernetes secret:
kubectl create secret generic driftwise-operator \
--from-literal=api-key=dw2_...
Reporting Resources
The operator POSTs discovered resources to
POST /api/v2/operator/report on its polling interval. DriftWise
creates a synthetic cloud_accounts row per cluster
(provider=kubernetes) so live_resources.cloud_account_id resolves;
cloud scan workers skip those rows.
See the accounts tag of the API
reference for the request
body shape (OperatorReportRequest) and response
(OperatorReportResponse). Key request fields summarized below.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
cluster_id | string | yes | Unique identifier for the cluster |
namespace | string | yes | Kubernetes namespace being reported |
resources | Resource[] | yes | Array of discovered resources |
(The request body also accepts generate_iac, iac_format, and
custom_prompt fields for wire-compatibility with older operator
builds, but the server currently ignores them — the report response
carries no generated IaC. Use the drift UI's IaC generation instead.)
Response
{
"report_id": "scan-run-uuid",
"resource_count": 15
}
The report_id is a scan run ID — use it to query drift results via GET /api/v2/orgs/:id/scans/:scan_id/drift.
Resource Structure
Each resource in the resources array:
| Field | Type | Description |
|---|---|---|
ID | string | Unique resource identifier within the cluster |
NormalizedType | string | Must use the k8s/<kind> format (e.g., k8s/deployment, k8s/pod, k8s/service, k8s/statefulset). Unlike cloud resources, Kubernetes types are kept fine-grained — they are not flattened to broad categories by Cloud Discovery. |
ProviderType | string | The Kubernetes kind in lowercase (e.g., deployment, pod, service) |
Provider | string | Always "kubernetes" |
Name | string | Resource name |
Region | string | Cluster region |
Tags | object | Labels as key-value pairs |
Properties | object | Resource-specific attributes |
Resource fields use Go-style CamelCase keys on the wire (ID,
NormalizedType, ProviderType, ...). JSON matching is
case-insensitive, so id/name/provider also work — but
snake_case compound names (normalized_type, provider_type) do
not map and are silently dropped. Send NormalizedType and
ProviderType exactly (or any casing without underscores).
Rate Limiting
The operator endpoint allows 120 requests per minute per IP address, designed for the typical ~30-second polling interval.
Drift Detection with K8s Resources
Once resources are reported, they participate in the normal drift detection flow:
- Operator reports resources → stored as live resources
- Terraform state sources provide IaC resources
- Drift computation matches live vs. IaC by resource ID
- Missing/extra/changed items appear in drift results
Resources are grouped by cluster using the cluster_id field — each cluster gets a synthetic provider = "kubernetes" cloud-account row whose external account ID is the cluster_id verbatim.