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",
"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 |
OpenTelemetry collector address (e.g., otel-collector:4317) |
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 |
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 |
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 |
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.