Skip to content

Commit f3e41ea

Browse files
authored
Merge pull request #208 from Obmondo/feat/kubeaid-agent-cve-alerts
feat(kubeaid-security-exporter): add chart, move posture collection o…
2 parents d25ce23 + bd916ea commit f3e41ea

13 files changed

Lines changed: 607 additions & 148 deletions

File tree

argocd-helm-charts/kubeaid-agent/templates/rbac-cluster-read.yaml

Lines changed: 9 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
{{/*
22
The agent's cluster-scoped read access — one ClusterRole, one binding.
33

4-
Replaces the built-in `view` ClusterRole this chart used to bind. `view` was
5-
simultaneously too wide and too narrow:
6-
7-
too wide — read on nearly every namespaced resource in the cluster,
8-
plus `watch` on all of them. The agent needs neither.
9-
10-
too narrow — custom resources appear in `view` only when their vendor
11-
ships a ClusterRole labelled aggregate-to-view. Cilium 1.20
12-
and Tetragon ship none; trivy-operator aggregates only three
13-
of its report kinds, not the rbac/infra assessments. Relying
14-
on `view` half-worked, which is worse than failing outright.
4+
Replaces the built-in `view` ClusterRole this chart used to bind: `view`
5+
grants read on nearly every namespaced resource plus `watch` on all of them,
6+
and the agent needs neither.
157

168
Every rule names the call site that needs it. Verbs are exactly what the
179
code calls — the agent builds no informers, so `watch` is granted nowhere.
1810

1911
Secrets are deliberately absent: they stay on the namespaced Role in
2012
rbac-secret-reader.yaml, which is what keeps that access scoped.
13+
14+
Security-posture reads — Trivy, Cilium, Tetragon, KubeArmor — are NOT here.
15+
They belong to kubeaid-security-exporter, which collects that data and
16+
serves it to the agent over HTTP. The agent holds no CRD access at all.
2117
*/}}
2218
apiVersion: rbac.authorization.k8s.io/v1
2319
kind: ClusterRole
@@ -37,12 +33,10 @@ rules:
3733
resources: ["events"]
3834
verbs: ["list"]
3935

40-
# get — probing for the sealed-secrets controller (sealedsecret/seal.go)
41-
# list — locating version-checker's metrics endpoint by label
42-
# (securityposture/versionchecker.go)
36+
# Probing for the sealed-secrets controller (sealedsecret/seal.go).
4337
- apiGroups: [""]
4438
resources: ["services"]
45-
verbs: ["get", "list"]
39+
verbs: ["get"]
4640

4741
# Owner-ref walk targets and their container specs. ReplicaSet is read to
4842
# reach the Deployment above it; the rest for their pod templates.
@@ -60,72 +54,6 @@ rules:
6054
- jobs
6155
- cronjobs
6256
verbs: ["get"]
63-
{{- if .Values.appConfig.securityPosture.enabled }}
64-
65-
{{/*
66-
Security posture. Gated by the SAME flag the agent reads, so the RBAC and
67-
the CRON job can never disagree — granting these while the collector is
68-
off is dead access, and running the collector without them fails on every
69-
cycle.
70-
71-
IMPORTANT: every rule below is consumed through a DYNAMIC client. Nothing
72-
in the agent's Go code references these resource names as imported types,
73-
so a reader grepping for "vulnerabilityreports" finds only this file and
74-
can reasonably conclude the rule is unused. Removing any of them does not
75-
fail the build, does not fail startup, and does not log an obvious error
76-
— the collector returns no data and the Obmondo UI renders the cluster as
77-
clean. This is the same trap already hit in backup-exporter.
78-
*/}}
79-
80-
# Trivy Operator — vulnerabilities and least-privilege posture.
81-
- apiGroups: ["aquasecurity.github.io"]
82-
resources:
83-
- vulnerabilityreports
84-
- configauditreports
85-
- rbacassessmentreports
86-
- infraassessmentreports
87-
verbs: ["get", "list"]
88-
89-
# Upstream NetworkPolicy, read alongside CiliumNetworkPolicy to decide whether
90-
# an app ships a policy at all.
91-
- apiGroups: ["networking.k8s.io"]
92-
resources: ["networkpolicies"]
93-
verbs: ["get", "list"]
94-
95-
# Cilium — CiliumEndpoint carries the realised per-direction enforcement
96-
# state; CiliumNetworkPolicy is what an app ships; TracingPolicy is Tetragon's
97-
# runtime detection posture. All under cilium.io.
98-
#
99-
# The collector swallows a list error on the policy kinds, so omitting either
100-
# policy rule reports every app as shipping no network policy — no error, no
101-
# log, no metric.
102-
- apiGroups: ["cilium.io"]
103-
resources:
104-
- ciliumendpoints
105-
- ciliumnetworkpolicies
106-
- tracingpolicies
107-
- tracingpoliciesnamespaced
108-
verbs: ["get", "list"]
109-
110-
# KubeArmor — the second runtime engine, reported alongside Tetragon. All four
111-
# policy kinds are read: host and cluster policies are cluster-scoped, the
112-
# other two namespaced, and an operator can enforce through any of them.
113-
- apiGroups: ["security.kubearmor.com"]
114-
resources:
115-
- kubearmorpolicies
116-
- kubearmorclusterpolicies
117-
- kubearmorhostpolicies
118-
- kubearmornetworkpolicies
119-
verbs: ["get", "list"]
120-
121-
# KubeArmorConfig carries the cluster-wide default postures (file, network,
122-
# capabilities → audit or block). It lives under the OPERATOR's group, not the
123-
# policy group, and without it a cluster whose policies all audit is
124-
# indistinguishable from one whose defaults block.
125-
- apiGroups: ["operator.kubearmor.com"]
126-
resources: ["kubearmorconfigs"]
127-
verbs: ["get", "list"]
128-
{{- end }}
12957
---
13058
apiVersion: rbac.authorization.k8s.io/v1
13159
kind: ClusterRoleBinding

argocd-helm-charts/kubeaid-agent/values.schema.json

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,15 @@
4747
"type": "object",
4848
"properties": {
4949
"enabled": {
50-
"description": "When true, the agent collects vulnerability and posture snapshots and submits them to Obmondo API. Also gates the corresponding RBAC rules. Set false on clusters that must not ship vulnerability detail off-site.",
50+
"description": "When true, the agent polls kubeaid-security-exporter and forwards its snapshots to Obmondo API. Set false on clusters that must not ship vulnerability detail off-site.",
5151
"type": "boolean"
5252
},
53-
"interval": {
54-
"description": "Cadence of the full posture collection. Trivy refreshes its reports on a 24h TTL, so polling faster re-reads identical data.",
53+
"exporterURL": {
54+
"description": "In-cluster URL of kubeaid-security-exporter. A bare Service name resolves within the agent's own namespace.",
55+
"type": "string"
56+
},
57+
"pollInterval": {
58+
"description": "How often the agent polls the exporter. The submit is skipped when collectedAt has not advanced.",
5559
"type": "string"
5660
},
5761
"windowCheckInterval": {
@@ -313,29 +317,6 @@
313317
"type": "string"
314318
}
315319
}
316-
},
317-
"prometheusRule": {
318-
"description": "Alerts on the agent's security-posture metrics. Only rendered when appConfig.securityPosture.enabled is true.",
319-
"type": "object",
320-
"properties": {
321-
"enabled": {
322-
"type": "boolean"
323-
},
324-
"additionalLabels": {
325-
"type": "object"
326-
},
327-
"additionalAnnotations": {
328-
"type": "object"
329-
},
330-
"upgradableThreshold": {
331-
"description": "Alert when more than this many images have both a fixable Critical/High finding and a newer tag upstream. One alert per cluster.",
332-
"type": "integer"
333-
},
334-
"upgradableFor": {
335-
"description": "How long the count must stay above the threshold before alerting.",
336-
"type": "string"
337-
}
338-
}
339320
}
340321
}
341322
}

argocd-helm-charts/kubeaid-agent/values.yaml

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,28 @@ appConfig:
4545
# enough not to thrash the API when no window is open.
4646
checkInterval: 15m
4747

48-
# Security posture: vulnerabilities with full CVE detail, least-privilege
49-
# findings, per-app network enforcement and runtime detection posture,
50-
# submitted to the Obmondo API for display in the UI.
48+
# Security posture: vulnerabilities, least-privilege findings, network
49+
# enforcement and runtime detection posture. The agent collects none of this
50+
# itself — kubeaid-security-exporter does, and the agent forwards it.
5151
securityPosture:
52-
# -- When false, neither CRON job is scheduled, no snapshot is sent to
53-
# Obmondo API, and the corresponding RBAC rules are not created. This is
54-
# the switch for clusters that must not ship vulnerability detail off-site.
52+
# -- When false, the agent neither polls the exporter nor forwards anything
53+
# to Obmondo API. This is the switch for clusters that must not ship
54+
# vulnerability detail off-site.
5555
#
56-
# Safe to leave on where no scanner is installed: the collector detects
57-
# that through API discovery and does nothing, rather than erroring.
56+
# Safe to leave on where the exporter is not installed: the poll fails, a
57+
# metric records it, and nothing is submitted.
5858
enabled: true
5959

60-
# -- Cadence of the full posture collection. Trivy refreshes its reports on
61-
# a 24h scannerReportTTL, so polling faster re-reads identical data.
62-
interval: 12h
60+
# -- In-cluster URL of kubeaid-security-exporter. A bare Service name
61+
# resolves within the agent's own namespace; qualify it if the two charts
62+
# deploy to different namespaces.
63+
exporterURL: http://kubeaid-security-exporter
64+
65+
# -- How often the agent polls the exporter. Cheap, because the exporter
66+
# serves a cached snapshot and the agent skips the submit when collectedAt
67+
# has not advanced. End-to-end freshness is bounded by the EXPORTER's
68+
# collection interval, not by this one.
69+
pollInterval: 1h
6370

6471
# -- Cadence at which the agent checks for a service-window edge, to capture
6572
# a before/after pair around maintenance. This bounds how precisely an edge
@@ -162,26 +169,3 @@ serviceMonitor:
162169
interval: 30s
163170
scrapeTimeout: 10s
164171

165-
# Alerts on the agent's own security-posture metrics. Only rendered when
166-
# appConfig.securityPosture.enabled is true, since without collection the
167-
# metrics never appear and every rule would evaluate against nothing.
168-
prometheusRule:
169-
enabled: true
170-
171-
# -- Extra labels on the PrometheusRule object, for Prometheus selector
172-
# matching.
173-
additionalLabels: {}
174-
additionalAnnotations: {}
175-
176-
# -- Alert when more than this many images have BOTH a fixable Critical/High
177-
# finding and a newer tag upstream. One alert per cluster, not one per image.
178-
#
179-
# Deliberately high. Every real cluster carries a few of these at any moment,
180-
# so a low threshold fires everywhere on day one and gets ignored. The signal
181-
# worth acting on is a pile of easy upgrades, not the existence of one.
182-
upgradableThreshold: 20
183-
184-
# -- How long the count must stay above the threshold. Long by design: this
185-
# is a backlog to work through, not an incident.
186-
upgradableFor: 24h
187-
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
apiVersion: v2
2+
name: kubeaid-security-exporter
3+
description: Collects cluster security posture — vulnerabilities, least-privilege findings, network enforcement and runtime detection — and serves it over HTTP and Prometheus.
4+
icon: https://avatars.githubusercontent.com/u/13882947?s=250&v=4
5+
6+
type: application
7+
8+
version: 0.1.0
9+
appVersion: 0.1.0
10+
11+
keywords:
12+
- obmondo
13+
- kubeaid
14+
- security
15+
- trivy
16+
17+
maintainers:
18+
- name: Sanskar Bhushan
19+
email: sanskar@obmondo.com
20+
url: https://github.com/sbdtu5498
21+
- name: Archisman Mridha
22+
email: archisman@obmondo.com
23+
url: https://github.com/Archisman-Mridha
24+
- name: Basit Hasan
25+
email: basit@obmondo.com
26+
url: https://github.com/basit9958
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{{/* Determine the final chart name. */}}
2+
{{- define "kubeaid-security-exporter.name" -}}
3+
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
4+
{{- end -}}
5+
6+
{{/* Determine the final application name. */}}
7+
{{- define "kubeaid-security-exporter.fullname" -}}
8+
{{- if .Values.fullnameOverride -}}
9+
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
10+
{{- else -}}
11+
{{- $name := default .Chart.Name .Values.nameOverride -}}
12+
{{- if contains $name .Release.Name -}}
13+
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
14+
{{- else -}}
15+
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
16+
{{- end -}}
17+
{{- end -}}
18+
{{- end -}}
19+
20+
{{/* Common conventional labels. */}}
21+
{{- define "kubeaid-security-exporter.labels" -}}
22+
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
23+
app.kubernetes.io/name: {{ include "kubeaid-security-exporter.name" . }}
24+
app.kubernetes.io/instance: {{ .Release.Name }}
25+
app.kubernetes.io/managed-by: {{ .Release.Service }}
26+
app.kubernetes.io/version: {{ .Chart.AppVersion }}
27+
{{- end -}}
28+
29+
{{/* Selector labels. */}}
30+
{{- define "kubeaid-security-exporter.selectorLabels" -}}
31+
app.kubernetes.io/name: {{ include "kubeaid-security-exporter.name" . }}
32+
app.kubernetes.io/instance: {{ .Release.Name }}
33+
{{- end }}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: {{ include "kubeaid-security-exporter.fullname" . }}
5+
namespace: {{ .Release.Namespace }}
6+
labels:
7+
{{- include "kubeaid-security-exporter.labels" . | nindent 4 }}
8+
spec:
9+
replicas: {{ .Values.deployment.replicas }}
10+
selector:
11+
matchLabels:
12+
{{- include "kubeaid-security-exporter.selectorLabels" . | nindent 6 }}
13+
template:
14+
metadata:
15+
labels:
16+
{{- include "kubeaid-security-exporter.selectorLabels" . | nindent 8 }}
17+
{{- with .Values.deployment.podLabels }}
18+
{{- toYaml . | nindent 8 }}
19+
{{- end }}
20+
{{- with .Values.deployment.podAnnotations }}
21+
annotations:
22+
{{- toYaml . | nindent 8 }}
23+
{{- end }}
24+
spec:
25+
{{- with .Values.deployment.imagePullSecrets }}
26+
imagePullSecrets:
27+
{{- toYaml . | nindent 8 }}
28+
{{- end }}
29+
serviceAccountName: {{ include "kubeaid-security-exporter.fullname" . }}
30+
{{- with .Values.deployment.podSecurityContext }}
31+
securityContext:
32+
{{- toYaml . | nindent 8 }}
33+
{{- end }}
34+
containers:
35+
- name: {{ include "kubeaid-security-exporter.name" . }}
36+
image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag }}"
37+
imagePullPolicy: {{ .Values.deployment.image.pullPolicy }}
38+
env:
39+
- name: SECURITY_EXPORTER_PORT
40+
value: {{ .Values.exporter.port | quote }}
41+
- name: SECURITY_EXPORTER_INTERVAL
42+
value: {{ .Values.exporter.interval | quote }}
43+
{{- with .Values.deployment.env }}
44+
{{- toYaml . | nindent 12 }}
45+
{{- end }}
46+
ports:
47+
- name: http
48+
containerPort: {{ .Values.exporter.port }}
49+
protocol: TCP
50+
{{- with .Values.deployment.securityContext }}
51+
securityContext:
52+
{{- toYaml . | nindent 12 }}
53+
{{- end }}
54+
volumeMounts:
55+
- name: tmp
56+
mountPath: /tmp
57+
{{- with .Values.deployment.extraVolumeMounts }}
58+
{{- toYaml . | nindent 12 }}
59+
{{- end }}
60+
{{- with .Values.deployment.probes.readiness }}
61+
readinessProbe:
62+
{{- toYaml . | nindent 12 }}
63+
{{- end }}
64+
{{- with .Values.deployment.probes.liveness }}
65+
livenessProbe:
66+
{{- toYaml . | nindent 12 }}
67+
{{- end }}
68+
{{- with .Values.deployment.resources }}
69+
resources:
70+
{{- toYaml . | nindent 12 }}
71+
{{- end }}
72+
volumes:
73+
- name: tmp
74+
emptyDir: {}
75+
{{- with .Values.deployment.extraVolumes }}
76+
{{- toYaml . | nindent 8 }}
77+
{{- end }}
78+
terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds }}
79+
{{- with .Values.deployment.nodeSelector }}
80+
nodeSelector:
81+
{{- toYaml . | nindent 8 }}
82+
{{- end }}
83+
{{- with .Values.deployment.affinity }}
84+
affinity:
85+
{{- toYaml . | nindent 8 }}
86+
{{- end }}
87+
{{- with .Values.deployment.tolerations }}
88+
tolerations:
89+
{{- toYaml . | nindent 8 }}
90+
{{- end }}

0 commit comments

Comments
 (0)