A Kubernetes-native watcher that detects container image vulnerabilities and publishes actionable security events to Komodor.
This application provides a secure, low-noise bridge between running Kubernetes workloads, vulnerability scanners (Trivy, Trivy Operator reports, Snyk, Wiz, Clair), and Komodor. It:
- Watches Kubernetes workloads (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs)
- Extracts container images from running workloads
- Resolves mutable tags to immutable digests
- Scans images for vulnerabilities using pluggable scanner drivers
- Normalises scanner-specific results into a common model
- Publishes meaningful vulnerability events to Komodor
- Deduplicates scan results to avoid alert fatigue
The watcher is designed with a strong security posture:
- No host access: No hostPath mounts, runtime sockets, or privileged containers
- No Secret access: Does not require Kubernetes API permissions to read Secrets
- No impersonation: Does not impersonate workload ServiceAccounts
- Minimal RBAC: Read-only access to workload metadata only
- Restricted container: Runs as non-root with read-only filesystem and dropped capabilities
See RFC for full security principles.
- Kubernetes 1.24+
- A container image scanner (e.g., Trivy)
- Komodor account and API key
# Using make
make install-tools
make build
# Or manually
go install ./cmd/komodor-security-reporterCopy and customise the example configuration:
cp docs/example-config.yaml config.yamlKey configuration options:
clusterName: prod-eks-01
# Namespaces to watch
namespaces:
include:
- production
- platform
exclude:
- kube-system
# Workload kinds to watch
workloads:
kinds:
- Deployment
- StatefulSet
# Scanners to use
scanners:
scanners:
- name: trivy
type: trivy
enabled: true
command:
binary: /usr/local/bin/trivy
timeout: 5m
# Publishing policies
publishing:
mode: komodor
minimumSeverity: high
includeTopFindings: 5
publishCleanScans: false
dedupeTTL: 24h
komodor:
baseURL: https://app.komodor.io# Set Komodor API key (required for publish mode: komodor or both)
export KOMODOR_API_KEY=your-api-key
# Run with config
./bin/komodor-security-reporter -config config.yaml
# Enable debug logging
./bin/komodor-security-reporter -config config.yaml -log-level debugSee DEVELOPMENT.md for more info
- Kubernetes Workload Watcher: Observes supported workload resources and detects image references
- Image Extractor: Reads images from containers, initContainers, and ephemeralContainers
- Digest Resolver: Resolves image tags to immutable digests using go-containerregistry
- Scanner Registry: Loads configured scanner drivers and invokes them
- Scanner Drivers: Scan images and return structured results (Trivy, Trivy Operator, Snyk, Wiz, Clair)
- Finding Normaliser: Converts scanner-specific results to a common model
- Policy Evaluator: Determines whether to publish based on severity and deduplication
- Komodor Publisher: Publishes normalised events to Komodor
- State Store: ConfigMap-backed deduplication state
Kubernetes Workload
↓
Image Extractor
↓
Digest Resolver
↓
Scanner Registry
↓
Finding Normaliser
↓
Policy Evaluator
↓
Komodor Event Publisher
↓
State / Dedupe Store
# Run all tests
make test
# Run with coverage
make coverage
# Run short tests only (excludes integration tests)
make test-short
# Run specific test
go test -run TestFingerprint ./internal/policy- Unit tests: All core logic components (config, scanners, registry, policy)
- Table-driven tests: For parsing logic and policy evaluation
- Mock/fake clients: For Kubernetes and Komodor interactions
- Integration tests: Optional for scanner drivers (skipped by default)
See helm/ directory for complete Helm chart.
Minimal ServiceAccount and RBAC:
apiVersion: v1
kind: ServiceAccount
metadata:
name: komodor-security-reporter
namespace: security
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: komodor-security-reporter
rules:
- apiGroups: [""]
resources: [namespaces]
verbs: [get, list, watch]
- apiGroups: [apps]
resources: [deployments, statefulsets, daemonsets]
verbs: [get, list, watch]
- apiGroups: [batch]
resources: [jobs, cronjobs]
verbs: [get, list, watch]
# For ConfigMap-backed state store
- apiGroups: [""]
resources: [configmaps]
verbs: [get, list, create, update]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: komodor-security-reporter
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: komodor-security-reporter
subjects:
- kind: ServiceAccount
name: komodor-security-reporter
namespace: securityspec:
securityContext:
runAsNonRoot: true
runAsUser: 65534
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
containers:
- name: watcher
image: komodor-security-reporter:latest
imagePullPolicy: IfNotPresent
env:
- name: KOMODOR_API_KEY
valueFrom:
secretKeyRef:
name: komodor-credentials
key: api-key
ports:
- name: metrics
containerPort: 8080
volumeMounts:
- name: config
mountPath: /etc/komodor-security-reporter
readOnly: true
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache/komodor-security-reporter
volumes:
- name: config
configMap:
name: komodor-security-reporter
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}Prometheus metrics are exposed on :8080/metrics:
image_vuln_watcher_images_observed_total- Total images observedimage_vuln_watcher_images_resolved_total- Total images resolved to digestimage_vuln_watcher_image_resolution_errors_total- Resolution failuresimage_vuln_watcher_scans_total- Total scans performedimage_vuln_watcher_scan_errors_total- Scan failuresimage_vuln_watcher_scan_duration_seconds- Scan duration histogramimage_vuln_watcher_events_published_total- Events publishedimage_vuln_watcher_event_publish_errors_total- Publishing failuresimage_vuln_watcher_dedupe_hits_total- Deduplicated events
When running with --publish-mode=events or --publish-mode=both, the reporter creates native Kubernetes Warning Events on the affected workload. You can inspect these with kubectl:
# List all VulnerabilityScan events across all namespaces
kubectl get events -A --field-selector reason=VulnerabilityScan
# Filter to a specific namespace
kubectl get events -n <namespace> --field-selector reason=VulnerabilityScan
# Watch events in real time
kubectl get events -A --field-selector reason=VulnerabilityScan -w
# Show full event details for a specific workload
kubectl describe deployment/<name> -n <namespace> | grep -A5 VulnerabilityScan
# Filter by the reporting component
kubectl get events -A \
--field-selector reportingComponent=komodor-security-reporterEvents use reason=VulnerabilityScan and type=Warning (or Normal for clean scans). The MESSAGE column contains the vulnerability summary, e.g.:
summary="critical=3 high=21 medium=16 low=1 total=49" scanner=trivy image=... findings=49 (critical=3 high=21 medium=16 low=1)
Note: For human-readable local output, add
--log-format=textto the reporter flags.
Structured logging uses JSON output with contextual fields:
{"level":"info","msg":"loaded configuration","cluster":"prod-eks-01","time":"2024-01-15T10:30:45Z"}
{"level":"warn","msg":"failed to resolve image digest","image":"ghcr.io/acme/app:1.0","error":"404 not found"}Log levels: debug, info (default), warn, error
GET /healthz - Returns 200 OK if service is healthy
GET /metrics - Prometheus-format metrics
All events are structured with fields:
cluster- Cluster namenamespace- Workload namespaceworkloadKind- Kind of workload (Deployment, etc)workloadName- Workload namecontainer- Container nameimage- Image referencedigest- Image digestscanner- Scanner name
# Build snapshot (development)
make release-snapshot
# Create a release (requires git tag)
# 1. Tag the release: git tag v0.1.0
# 2. Run release:
make releaseThe release process:
- Builds binaries for linux/amd64, linux/arm64, darwin/amd64, darwin/arm64
- Creates archives and checksums
- Builds and pushes Docker images (requires Docker credentials)
- Creates GitHub release with binaries
- Generates changelog from commits
Images are published to: ghcr.io/davidcollom/komodor-security-reporter
Tags:
vX.Y.Z- Specific releaselatest- Latest release
See docs/example-config.yaml for full configuration options.
See docs/rfc.md for design decisions:
- No CRDs in v1
- No Kubernetes Secret read RBAC
- No impersonation
- No host-level access
- Ensure tests pass:
make test - Format code:
make fmt - Run linters:
make lint - Create a pull request
Apache License 2.0
For issues, questions, or suggestions, please open a GitHub issue.