The Kubescape Operator is the central orchestration engine for Kubescape's in-cluster components. It coordinates security scanning operations, manages scheduled tasks, and provides a REST API for triggering actions across your Kubernetes cluster.
- Why Use the Operator?
- Architecture
- Quick Start
- Installation
- Configuration
- API Documentation
- API Examples
- Private Registry Authentication
- Features
- Development
- Troubleshooting
- Contributing
- License
The Kubescape Operator provides:
- π Centralized Orchestration - Single control plane for all Kubescape in-cluster security operations
- π Scheduled Scanning - CronJob-based recurring security and vulnerability scans
- π Continuous Monitoring - Real-time detection of configuration changes with automatic rescanning
- π‘οΈ Admission Control - Validate workloads against security policies before deployment
- π‘ REST API - Programmatic access to trigger scans and manage security operations
- π Component Integration - Seamlessly coordinates Kubescape, Kubevuln, and other in-cluster components
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Kubernetes Cluster β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β kubescape namespace β β
β β β β
β β βββββββββββββββ REST API βββββββββββββββββββββββββββββββ β β
β β β Client β ββββββββββββββββββΆβ OPERATOR β β β
β β β (kubectl, β :4002 β β β β
β β β curl) β β β’ Command Processing β β β
β β βββββββββββββββ β β’ CronJob Management β β β
β β β β’ Continuous Scanning β β β
β β β β’ Admission Controller β β β
β β ββββββββββββ¬βββββββββββββββββββ β β
β β β β β
β β ββββββββββββββββββββββββββββΌβββββββββββββββββββ β β
β β β β β β β
β β βΌ βΌ βΌ β β
β β ββββββββββββββββββ ββββββββββββββββββ βββββββββββββ β
β β β Kubescape β β Kubevuln β β Storage ββ β
β β β β β β β ββ β
β β β Config Scans β β Vuln Scans β β Results ββ β
β β ββββββββββββββββββ ββββββββββββββββββ βββββββββββββ β
β β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Get Kubescape with the Operator running in under 5 minutes:
# 1. Add the Kubescape Helm repository
helm repo add kubescape https://kubescape.github.io/helm-charts/
helm repo update
# 2. Install Kubescape with all components
helm upgrade --install kubescape kubescape/kubescape-operator \
-n kubescape --create-namespace \
--set clusterName="my-cluster"
# 3. Verify the Operator is running
kubectl get pods -n kubescape -l app.kubernetes.io/name=operator
# 4. Trigger a configuration scan via port-forward
kubectl port-forward -n kubescape svc/operator 4002:4002 &
curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "kubescapeScan",
"args": {
"scanV1": {
"submit": true
}
}
}]
}'Expected output:
{"commands":[{"commandName":"kubescapeScan",...}]}The Kubescape Operator is deployed as part of the Kubescape Helm chart. Visit the Kubescape Helm Charts repository for detailed installation options.
# Basic installation
helm upgrade --install kubescape kubescape/kubescape-operator \
-n kubescape --create-namespace \
--set clusterName="my-cluster"
# With cloud connection (for Kubescape Cloud users)
helm upgrade --install kubescape kubescape/kubescape-operator \
-n kubescape --create-namespace \
--set clusterName="my-cluster" \
--set account="your-account-id" \
--set accessKey="your-access-key"For development or testing, you can run the Operator locally:
- A running Kubernetes cluster (minikube, kind, etc.)
kubectlconfigured to access your cluster- Go 1.24+ installed
-
Install Kubescape in-cluster components:
helm upgrade --install kubescape kubescape/kubescape-operator \ -n kubescape --create-namespace \ --set operator.enabled=false # We'll run it locally -
Port-forward the required services:
kubectl port-forward -n kubescape service/kubescape 8080:8080 & kubectl port-forward -n kubescape service/kubevuln 8081:8080 &
-
Create configuration files (see Configuration Files section)
-
Build and run:
go build -o operator . ./operator
The Operator reads configuration from /etc/config/. When running locally, set the CONFIG environment variable to point to your config directory.
/etc/config/clusterData.json - Cluster connection settings
{
"gatewayWebsocketURL": "",
"gatewayRestURL": "",
"kubevulnURL": "127.0.0.1:8081",
"kubescapeURL": "127.0.0.1:8080",
"accountID": "your-account-id",
"clusterName": "my-cluster"
}/etc/config/config.json - Operator settings
{
"namespace": "kubescape",
"port": "4002",
"bindAddress": "",
"cleanupDelay": 600000000000,
"workerConcurrency": 3,
"triggerSecurityFramework": false,
"matchingRulesFilename": "/etc/config/matchingRules.json",
"excludeNamespaces": "kube-system,kubescape",
"excludeNamespacesRegex": "",
"includeNamespaces": "",
"includeNamespacesRegex": ""
}/etc/config/capabilities.json - Feature toggles
{
"capabilities": {
"configurationScan": "enable",
"continuousScan": "disable",
"nodeScan": "enable",
"vulnerabilityScan": "enable",
"relevancy": "enable",
"networkGenerator": "disable",
"runtimeObservability": "disable",
"nodeSbomGeneration": "disable",
"seccomp": "disable",
"otel": "enable",
"admissionController": "disable"
},
"components": {
"operator": { "enabled": true },
"kubescape": { "enabled": true },
"kubescapeScheduler": { "enabled": true },
"kubevuln": { "enabled": true },
"kubevulnScheduler": { "enabled": true },
"nodeAgent": { "enabled": true },
"hostScanner": { "enabled": true },
"storage": { "enabled": true },
"otelCollector": { "enabled": true },
"serviceDiscovery": { "enabled": true }
},
"configurations": {
"persistence": "enable",
"server": {
"account": null,
"discoveryUrl": "https://api.kubescape.io"
}
}
}/etc/config/matchingRules.json - Continuous scanning filters (optional)
{
"match": [
{
"apiGroups": ["apps"],
"apiVersions": ["v1"],
"resources": ["deployments", "daemonsets", "statefulsets"]
},
{
"apiGroups": [""],
"apiVersions": ["v1"],
"resources": ["pods"]
}
],
"namespaces": ["default", "production"]
}| Variable | Description | Default |
|---|---|---|
CONFIG |
Path to cluster configuration file | /etc/config/clusterData.json |
SERVICES |
Path to services configuration file | /etc/config/services.json |
OTEL_COLLECTOR_SVC |
Legacy operational OTLP endpoint alias; see telemetry configuration and discovery | not set |
RELEASE |
Image version for logging | set at build time |
NODE_NAME |
Kubernetes node name (from downward API) | set by Kubernetes |
KS_SKIP_UPDATE_CHECK |
Set to true on the Operator container to suppress its daily report |
not set |
| Setting | Default Value | Description |
|---|---|---|
namespace |
kubescape |
Namespace for Kubescape components |
port |
4002 |
REST API port |
bindAddress |
"" |
Address the REST API listens on; empty binds every interface. 127.0.0.1 keeps kubescape operator working, since the CLI port-forwards to the pod, while nothing on the pod network can reach /v1/triggerAction |
cleanupDelay |
10m |
Interval for cleanup routines |
workerConcurrency |
3 |
Number of concurrent workers |
triggerSecurityFramework |
false |
Trigger security framework on startup |
eventDeduplicationInterval |
2m |
Interval to deduplicate continuous scan events |
podScanGuardTime |
1h |
Minimum pod age before scanning (for orphan pods) |
registryScanningSkipTlsVerify |
false |
Skip TLS verification for registry scanning |
registryScanningInsecure |
false |
Allow insecure registry connections |
includeNamespaces |
"" |
Comma-separated string or array of exact namespaces to include |
includeNamespacesRegex |
"" |
Comma-separated string or array of RE2 regex patterns for namespaces to include |
excludeNamespaces |
"kube-system,kubescape" |
Comma-separated string or array of exact namespaces to exclude |
excludeControls |
[] |
Control IDs or legacy CIS section numbers omitted from operator posture scans |
excludeNamespacesRegex |
"" |
Comma-separated string or array of RE2 regex patterns for namespaces to exclude |
namespaceFilterConfigMapName |
"" |
Optional ConfigMap name in the operator namespace for live namespace filtering |
Set excludeControls in the operator's config.json to a list of control IDs
(or legacy CIS section numbers):
{"excludeControls": ["C-0069", "C-0070"]}The operator forwards these settings for explicit, scheduled, and continuous
scans, including resource deletion scans. Individual scanV1 requests can add
excludeControls; the installation and request lists are combined. An empty
request list does not clear installation settings. Whitespace is trimmed and
duplicates are removed without regard to case, including scheduled replays.
Stored jobs retain only request-specific exclusions; installation settings are
applied when they run. Restart the operator after changing its config file.
Blank identifiers and values other than a list of strings are rejected.
A scanner supporting this field applies the existing --exclude-controls
filter before evaluating controls and computing compliance scores. Excluding a
control narrows the assessment scope; it does not assert that the check passed.
Omitting the installation setting leaves existing behavior unchanged.
Compatibility: external implementations of config.IConfig must also provide
ExcludeControls() []string. Returning nil or an empty slice keeps scans
without installation-level exclusions.
The Operator supports filtering namespaces using exact names (includeNamespaces, excludeNamespaces) and RE2 regular expressions (includeNamespacesRegex, excludeNamespacesRegex):
- Precedence (Allow-List): If either
includeNamespacesorincludeNamespacesRegexis configured, inclusion mode is active. Only namespaces matching an exact entry inincludeNamespacesOR a regex inincludeNamespacesRegexwill be processed. All other namespaces are skipped, andexcludeNamespaces/excludeNamespacesRegexare ignored. - Exclusion (Deny-List): When no inclusion rules are set, any namespace matching an exact entry in
excludeNamespacesOR a regex inexcludeNamespacesRegexwill be skipped. - Anchoring & Blast Radius:
[!WARNING] Unanchored regex patterns match as substrings anywhere within the namespace name. For example, an unanchored pattern
team-matchesteam-prod,team-staging, andmy-team-dev. To match exact prefixes, suffixes, or full namespace names, anchor the patterns using^and$, such as^team-.*-prod$. - RE2 Limitations: Patterns use Go's standard
regexpengine (RE2), which runs in guaranteed linear time (immune to ReDoS). Features like backreferences (\1), lookaheads ((?=...)), and lookbehinds ((?<=...)) are not supported. Invalid patterns fail fast with a clear error when configuration is loaded. - Commas in Patterns: If a regex pattern contains commas (for example, quantifiers like
{1,3}or character classes like[a,b]), provide the configuration as a JSON array (["^team-[a,b]-.*$"]) rather than a comma-separated string to prevent splitting.
To enable live updates, set "namespaceFilterConfigMapName": "namespace-filters"
in the operator's startup config.json. An empty name keeps startup-only filtering
and requires no additional permissions. Enabling this option initially requires
deploying the updated operator configuration; subsequent filter edits do not.
Create the ConfigMap in the same namespace as the operator (kubescape below):
apiVersion: v1
kind: ConfigMap
metadata:
name: namespace-filters
namespace: kubescape
data:
namespaceFilters.json: |
{
"includeNamespaces": [],
"excludeNamespaces": ["kube-system", "kubescape"],
"includeNamespacesRegex": [],
"excludeNamespacesRegex": []
}Grant the operator service account permission to list and watch this ConfigMap.
The watcher includes a metadata.name field selector, allowing a Role restricted
to this resource name. Replace the service account name below with the one used
by your operator Deployment.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: operator-namespace-filters
namespace: kubescape
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["namespace-filters"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: operator-namespace-filters
namespace: kubescape
subjects:
- kind: ServiceAccount
name: operator
namespace: kubescape
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: operator-namespace-filtersEdit namespaceFilters.json with kubectl -n kubescape edit configmap namespace-filters.
Each valid document replaces all namespace filters together. includeNamespaces
and excludeNamespaces are required; omitted regex fields become empty lists.
Values accept comma-separated strings or arrays of strings. Empty lists allow
all namespaces, and inclusion rules still take precedence over exclusions.
Malformed JSON, unknown fields, wrong value types (including null), and invalid
regex are rejected without changing the active rules. Other operator settings
remain startup-only.
When enabled, the operator waits for the first valid document before starting
scan processing or reporting ready. Missing data and API/RBAC failures leave it
waiting; correct the ConfigMap or permissions to let startup continue. After a
valid document has loaded, invalid updates, ConfigMap deletion, or API outages
retain the last valid rules. The informer reconnects automatically and accepts
valid updates or a recreated ConfigMap. Applied changes are logged as
namespace filters updated.
Updates affect subsequent namespace checks and queued work when it is processed. They do not cancel dispatched scans, trigger rescans of existing workloads, clear deduplication state, or delete existing results. Newly allowed namespaces become eligible on their next event or request, subject to existing deduplication. Findings and remediation paths that already use the operator's namespace filter also see the new rules. SBOM scan dispatch checks the resolved workload namespace, not the namespace where an SBOM is stored.
This uses the Kubernetes API directly, so it does not depend on ConfigMap volume
refresh or the chart's existing subPath mounts. Helm wiring and live filtering
inside other components are separate changes: this feature only partially
addresses helm-charts#664.
Cluster-wide requests forwarded to downstream scanners and registry-only scans
retain their existing behavior.
- Create the ConfigMap and RBAC above, and start an operator built with this
feature and
namespaceFilterConfigMapNameconfigured. Record its pod UID and restart count withkubectl -n kubescape get pod <operator-pod> -o json. - Create a workload in
paymentsand trigger the relevant scan event/request. Confirm the operator dispatches the scan. - Add
paymentstoexcludeNamespaces, leaving inclusion lists empty. Wait for the applied-update log, then generate a new workload event/request and confirm no scan is dispatched for it. - Remove
paymentsand generate a fresh event/request (use a new workload to avoid deduplication). Confirm scanning resumes. - Submit an invalid regex and confirm the rejection log and unchanged filtering. Restore valid JSON. Check that the operator pod UID and restart count match step 1 throughout these edits.
The Operator provides an HTTP REST API on port 4002 (configurable).
Interactive API documentation is available at:
| UI | Endpoint |
|---|---|
| SwaggerUI | /openapi/v2/swaggerui |
| RapiDoc | /openapi/v2/rapi |
| Redoc | /openapi/v2/docs |
Access via port-forward:
kubectl port-forward -n kubescape svc/operator 4002:4002
# Then open: http://localhost:4002/openapi/v2/swaggeruiAll examples assume you have port-forwarded the Operator:
kubectl port-forward -n kubescape svc/operator 4002:4002 &curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "kubescapeScan",
"args": {
"scanV1": {
"submit": true
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "kubescapeScan",
"args": {
"scanV1": {
"submit": true,
"targetType": "framework",
"targetNames": ["nsa", "mitre"]
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "setKubescapeCronJob",
"args": {
"kubescapeJobParams": {
"cronTabSchedule": "0 0 * * *"
},
"scanV1": {
"submit": true,
"targetType": "framework",
"targetNames": ["nsa"]
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "updateKubescapeCronJob",
"args": {
"kubescapeJobParams": {
"cronTabSchedule": "0 6 * * *",
"name": "kubescape-scheduler"
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "deleteKubescapeCronJob",
"args": {
"kubescapeJobParams": {
"name": "kubescape-scheduler"
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "scan",
"wlid": "wlid://cluster-my-cluster/namespace-default/deployment-nginx"
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "setVulnScanCronJob",
"wlid": "wlid://cluster-my-cluster/namespace-default",
"args": {
"jobParams": {
"cronTabSchedule": "0 2 * * *"
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "updateVulnScanCronJob",
"args": {
"jobParams": {
"cronTabSchedule": "0 4 * * *",
"name": "vuln-scan-scheduled-123456789"
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "deleteVulnScanCronJob",
"args": {
"jobParams": {
"name": "vuln-scan-scheduled-123456789"
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "scanRegistry",
"args": {
"registryInfo-v1": {
"registryName": "ghcr.io/kubescape",
"registryProvider": "ghcr.io",
"depth": 1,
"kind": "ghcr.io",
"isHTTPS": true,
"skipTLSVerify": false,
"authMethod": {
"type": "public"
}
}
}
}]
}'curl -X POST http://localhost:4002/v1/triggerAction \
-H 'Content-Type: application/json' \
-d '{
"commands": [{
"commandName": "setRegistryScanCronJob",
"args": {
"jobParams": {
"cronTabSchedule": "0 0 * * *"
},
"registryInfo-v1": {
"registryName": "ghcr.io/kubescape",
"registryProvider": "ghcr.io",
"depth": 1,
"kind": "ghcr.io",
"isHTTPS": true,
"skipTLSVerify": false,
"authMethod": {
"type": "public"
}
}
}
}]
}'To scan images from private container registries, create a Kubernetes Secret with your credentials.
echo -n 'registry.example.com' | base64
# Output: cmVnaXN0cnkuZXhhbXBsZS5jb20=
echo -n 'myusername' | base64
# Output: bXl1c2VybmFtZQ==
echo -n 'mypassword' | base64
# Output: bXlwYXNzd29yZA==Create a file named registry-secret.yaml:
apiVersion: v1
kind: Secret
metadata:
name: my-registry-credentials
namespace: kubescape
labels:
kubescape.io/registry: creds
type: Opaque
data:
registry: cmVnaXN0cnkuZXhhbXBsZS5jb20= # registry.example.com
username: bXl1c2VybmFtZQ== # myusername
password: bXlwYXNzd29yZA== # mypasswordApply the secret:
kubectl apply -f registry-secret.yamlKubescape automatically discovers secrets with the label kubescape.io/registry=creds in the kubescape namespace and uses them for registry authentication during image scans.
Note: The
registryfield should contain the registry hostname without thehttp://orhttps://prefix.
When enabled, the Operator watches for changes to Kubernetes resources and automatically triggers rescans when configurations change.
Enable via Helm:
helm upgrade kubescape kubescape/kubescape-operator \
-n kubescape \
--set capabilities.continuousScan=enableConfigure matching rules to specify which resources to watch:
{
"match": [
{
"apiGroups": ["apps"],
"apiVersions": ["v1"],
"resources": ["deployments"]
}
],
"namespaces": ["default", "production"]
}The admission controller validates workloads against security policies before they are admitted to the cluster.
Enable via Helm:
helm upgrade kubescape kubescape/kubescape-operator \
-n kubescape \
--set capabilities.admissionController=enableWhen enabled, the Operator runs an HTTPS webhook server on port 8443 that integrates with Kubernetes admission control.
# Clone the repository
git clone https://github.com/kubescape/operator.git
cd operator
# Build
go build -o operator .
# Build for Linux (cross-compile)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o operator .
# Build Docker image
make docker-build TAG=dev.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Operator",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceRoot}",
"env": {
"CONFIG": "${workspaceRoot}/.vscode/clusterData.json"
},
"args": ["-alsologtostderr", "-v=4"]
}
]
}.vscode/clusterData.json
{
"kubevulnURL": "127.0.0.1:8081",
"kubescapeURL": "127.0.0.1:8080",
"accountID": "",
"clusterName": "dev-cluster"
}Remember to port-forward the required services:
kubectl port-forward -n kubescape service/kubescape 8080:8080 &
kubectl port-forward -n kubescape service/kubevuln 8081:8080 &go generate ./...This updates docs/swagger.yaml with the latest API specification.
Check the pod logs:
kubectl logs -n kubescape -l app.kubernetes.io/name=operatorCommon causes:
- Missing ConfigMaps (
kubescape-config,kubescape-capabilities) - Unable to connect to Kubernetes API server
- Invalid configuration files
-
Verify the Operator is running:
kubectl get pods -n kubescape -l app.kubernetes.io/name=operator
-
Check that Kubescape/Kubevuln services are accessible:
kubectl get svc -n kubescape
-
Check Operator logs for errors:
kubectl logs -n kubescape -l app.kubernetes.io/name=operator --tail=100
- Ensure you're using the correct port (default: 4002)
- Verify the request body matches the expected schema (see API Documentation)
- Check that the target components (Kubescape, Kubevuln) are enabled in capabilities
-
Verify continuous scanning is enabled:
kubectl get configmap -n kubescape kubescape-capabilities -o yaml | grep continuousScan -
Check that
matchingRules.jsonis properly configured -
Verify the watched namespaces contain the expected resources
Enable debug logging by setting the log level:
kubectl set env deployment/operator -n kubescape LOG_LEVEL=debugWhen debug mode is enabled, a pprof server starts on port 6060 for profiling.
- π Kubescape Documentation
- π¬ Slack Community
- π GitHub Issues
We welcome contributions! Please see our Contributing Guidelines for details.
Copyright 2021-2024 Kubescape Authors
Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.