Skip to content

Commit b43cf79

Browse files
tzhukovclaudeEItanyaCopilot
authored
feat(security): enable secure defaults for kagent chart (#1468)
Add security context defaults to improve pod and container security: - Set runAsNonRoot: true for pod security context - Set readOnlyRootFilesystem: true for container security context - Add UI-specific security context overrides - Add emptyDir volumes for Next.js cache and tmp (required for read-only filesystem) - Update tool charts (grafana-mcp, querydoc) to make securityContext optional - Add comprehensive security context tests This change enables secure-by-default configuration while maintaining backward compatibility through values.yaml overrides. --------- Signed-off-by: Timofey Zhukov-Khovanskiy <tzhukov87@gmail.com> Signed-off-by: Tim Zhukov <51675972+tzhukov@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Eitan Yarmush <eitan.yarmush@solo.io> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 385d6cf commit b43cf79

File tree

8 files changed

+275
-20
lines changed

8 files changed

+275
-20
lines changed

contrib/tools/mcp-grafana/templates/deployment.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ spec:
2727
- "-t"
2828
- "streamable-http"
2929
- -debug
30+
{{- with .Values.securityContext }}
3031
securityContext:
31-
{{- toYaml .Values.securityContext | nindent 12 }}
32+
{{- toYaml . | nindent 12 }}
33+
{{- end }}
3234
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
3335
imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}
3436
resources:

helm/kagent/templates/controller-deployment.yaml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ spec:
2525
imagePullSecrets:
2626
{{- toYaml . | nindent 8 }}
2727
{{- end }}
28+
{{- with (.Values.controller.podSecurityContext | default .Values.podSecurityContext) }}
2829
securityContext:
29-
{{- toYaml (.Values.controller.podSecurityContext | default .Values.podSecurityContext) | nindent 8 }}
30+
{{- toYaml . | nindent 8 }}
31+
{{- end }}
3032
serviceAccountName: {{ include "kagent.fullname" . }}-controller
3133
{{- if or (eq .Values.database.type "sqlite") (gt (len .Values.controller.volumes) 0) }}
3234
volumes:
@@ -65,6 +67,10 @@ spec:
6567
valueFrom:
6668
fieldRef:
6769
fieldPath: spec.nodeName
70+
{{- if eq .Values.database.type "sqlite" }}
71+
- name: XDG_CACHE_HOME
72+
value: /sqlite-volume/.cache
73+
{{- end }}
6874
{{- with .Values.controller.env }}
6975
{{- toYaml . | nindent 12 }}
7076
{{- end }}
@@ -80,8 +86,10 @@ spec:
8086
protocol: TCP
8187
resources:
8288
{{- toYaml .Values.controller.resources | nindent 12 }}
89+
{{- with (.Values.controller.securityContext | default .Values.securityContext) }}
8390
securityContext:
84-
{{- toYaml .Values.controller.securityContext | nindent 12 }}
91+
{{- toYaml . | nindent 12 }}
92+
{{- end }}
8593
startupProbe:
8694
httpGet:
8795
path: /health

helm/kagent/templates/ui-deployment.yaml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,18 @@ spec:
2323
imagePullSecrets:
2424
{{- toYaml . | nindent 8 }}
2525
{{- end }}
26+
{{- with (.Values.ui.podSecurityContext | default .Values.podSecurityContext) }}
2627
securityContext:
27-
{{- toYaml .Values.podSecurityContext | nindent 8 }}
28+
{{- toYaml . | nindent 8 }}
29+
{{- end }}
2830
serviceAccountName: {{ include "kagent.fullname" . }}-ui
31+
volumes:
32+
- name: nextjs-cache
33+
emptyDir:
34+
sizeLimit: {{ .Values.ui.volumes.nextjsCache }}
35+
- name: tmp
36+
emptyDir:
37+
sizeLimit: {{ .Values.ui.volumes.tmp }}
2938
{{- with .Values.ui.nodeSelector }}
3039
nodeSelector:
3140
{{- toYaml . | nindent 8 }}
@@ -36,8 +45,10 @@ spec:
3645
{{- end }}
3746
containers:
3847
- name: ui
48+
{{- with (.Values.ui.securityContext | default .Values.securityContext) }}
3949
securityContext:
40-
{{- toYaml .Values.securityContext | nindent 12 }}
50+
{{- toYaml . | nindent 12 }}
51+
{{- end }}
4152
image: "{{ .Values.ui.image.registry | default .Values.registry }}/{{ .Values.ui.image.repository }}:{{ coalesce .Values.tag .Values.ui.image.tag .Chart.Version }}"
4253
imagePullPolicy: {{ .Values.ui.image.pullPolicy | default .Values.imagePullPolicy }}
4354
env:
@@ -50,6 +61,11 @@ spec:
5061
- name: http
5162
containerPort: {{ .Values.ui.service.ports.targetPort }}
5263
protocol: TCP
64+
volumeMounts:
65+
- name: nextjs-cache
66+
mountPath: /app/ui/.next/cache
67+
- name: tmp
68+
mountPath: /tmp
5369
resources:
5470
{{- toYaml .Values.ui.resources | nindent 12 }}
5571
startupProbe:
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
suite: test security context configuration
2+
templates:
3+
- _helpers.tpl
4+
- controller-configmap.yaml
5+
- controller-deployment.yaml
6+
- ui-deployment.yaml
7+
tests:
8+
# =============================================================================
9+
# Controller Security Context Tests
10+
# =============================================================================
11+
- it: should apply default pod security context to controller
12+
template: controller-deployment.yaml
13+
asserts:
14+
- equal:
15+
path: spec.template.spec.securityContext.runAsNonRoot
16+
value: true
17+
18+
- it: should apply default container security context to controller
19+
template: controller-deployment.yaml
20+
asserts:
21+
- equal:
22+
path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
23+
value: true
24+
25+
- it: should allow controller pod security context override
26+
template: controller-deployment.yaml
27+
set:
28+
controller:
29+
podSecurityContext:
30+
runAsNonRoot: true
31+
fsGroup: 2000
32+
asserts:
33+
- equal:
34+
path: spec.template.spec.securityContext.runAsNonRoot
35+
value: true
36+
- equal:
37+
path: spec.template.spec.securityContext.fsGroup
38+
value: 2000
39+
40+
- it: should allow controller container security context override
41+
template: controller-deployment.yaml
42+
set:
43+
controller:
44+
securityContext:
45+
readOnlyRootFilesystem: false
46+
runAsUser: 1000
47+
asserts:
48+
- equal:
49+
path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
50+
value: false
51+
- equal:
52+
path: spec.template.spec.containers[0].securityContext.runAsUser
53+
value: 1000
54+
55+
- it: should fallback to global pod security context for controller
56+
template: controller-deployment.yaml
57+
set:
58+
podSecurityContext:
59+
runAsNonRoot: true
60+
fsGroup: 3000
61+
asserts:
62+
- equal:
63+
path: spec.template.spec.securityContext.runAsNonRoot
64+
value: true
65+
- equal:
66+
path: spec.template.spec.securityContext.fsGroup
67+
value: 3000
68+
69+
- it: should fallback to global container security context for controller
70+
template: controller-deployment.yaml
71+
set:
72+
securityContext:
73+
readOnlyRootFilesystem: true
74+
capabilities:
75+
drop:
76+
- ALL
77+
asserts:
78+
- equal:
79+
path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
80+
value: true
81+
- contains:
82+
path: spec.template.spec.containers[0].securityContext.capabilities.drop
83+
content: ALL
84+
85+
# =============================================================================
86+
# UI Security Context Tests
87+
# =============================================================================
88+
- it: should apply UI-specific container security context override
89+
template: ui-deployment.yaml
90+
asserts:
91+
- equal:
92+
path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
93+
value: true
94+
95+
- it: should have nextjs-cache volume for UI
96+
template: ui-deployment.yaml
97+
asserts:
98+
- contains:
99+
path: spec.template.spec.volumes
100+
content:
101+
name: nextjs-cache
102+
emptyDir:
103+
sizeLimit: 100Mi
104+
105+
- it: should have tmp volume for UI
106+
template: ui-deployment.yaml
107+
asserts:
108+
- contains:
109+
path: spec.template.spec.volumes
110+
content:
111+
name: tmp
112+
emptyDir:
113+
sizeLimit: 50Mi
114+
115+
- it: should have nextjs-cache volume mount for UI
116+
template: ui-deployment.yaml
117+
asserts:
118+
- contains:
119+
path: spec.template.spec.containers[0].volumeMounts
120+
content:
121+
name: nextjs-cache
122+
mountPath: /app/ui/.next/cache
123+
124+
- it: should have tmp volume mount for UI
125+
template: ui-deployment.yaml
126+
asserts:
127+
- contains:
128+
path: spec.template.spec.containers[0].volumeMounts
129+
content:
130+
name: tmp
131+
mountPath: /tmp
132+
133+
- it: should allow UI pod security context override
134+
template: ui-deployment.yaml
135+
set:
136+
ui:
137+
podSecurityContext:
138+
runAsNonRoot: true
139+
fsGroup: 5000
140+
asserts:
141+
- equal:
142+
path: spec.template.spec.securityContext.runAsNonRoot
143+
value: true
144+
- equal:
145+
path: spec.template.spec.securityContext.fsGroup
146+
value: 5000
147+
148+
- it: should allow UI container security context override
149+
template: ui-deployment.yaml
150+
set:
151+
ui:
152+
securityContext:
153+
readOnlyRootFilesystem: false
154+
runAsUser: 2000
155+
asserts:
156+
- equal:
157+
path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
158+
value: false
159+
- equal:
160+
path: spec.template.spec.containers[0].securityContext.runAsUser
161+
value: 2000
162+
163+
- it: should fallback to global pod security context for UI
164+
template: ui-deployment.yaml
165+
set:
166+
ui:
167+
podSecurityContext: {}
168+
podSecurityContext:
169+
runAsNonRoot: true
170+
runAsUser: 3001
171+
asserts:
172+
- equal:
173+
path: spec.template.spec.securityContext.runAsNonRoot
174+
value: true
175+
- equal:
176+
path: spec.template.spec.securityContext.runAsUser
177+
value: 3001
178+
179+
- it: should prefer UI security context over global
180+
template: ui-deployment.yaml
181+
set:
182+
ui:
183+
securityContext:
184+
readOnlyRootFilesystem: false
185+
securityContext:
186+
readOnlyRootFilesystem: true
187+
asserts:
188+
- equal:
189+
path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
190+
value: false

helm/kagent/values.yaml

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ labels: {}
2424
# environment: production
2525
# team: platform
2626

27-
podSecurityContext: {}
28-
# fsGroup: 2000
29-
30-
securityContext: {}
31-
# capabilities:
32-
# drop:
33-
# - ALL
34-
# readOnlyRootFilesystem: true
35-
# runAsNonRoot: true
36-
# runAsUser: 1000
27+
# -- Security context for all pods
28+
podSecurityContext:
29+
runAsNonRoot: true
30+
# fsGroup: 2000
31+
32+
# -- Security context for all containers
33+
securityContext:
34+
readOnlyRootFilesystem: true
35+
# capabilities:
36+
# drop:
37+
# - ALL
38+
# runAsUser: 1000
3739

3840
# ==============================================================================
3941
# CORE KAGENT COMPONENTS
@@ -166,7 +168,25 @@ ui:
166168
port: 8080
167169
targetPort: 8080
168170
env: {} # Additional configuration key-value pairs for the ui ConfigMap
169-
171+
# -- Pod-level security context for the UI pod. Overrides the global podSecurityContext.
172+
# @default -- (uses global podSecurityContext)
173+
podSecurityContext: {}
174+
# fsGroup: 2000
175+
# -- Container-level security context for the UI container. Overrides the global securityContext.
176+
# @default -- (uses global securityContext)
177+
securityContext: {}
178+
# readOnlyRootFilesystem: true
179+
# -- EmptyDir volume sizes for Next.js UI workload (typically used when enabling readOnlyRootFilesystem)
180+
volumes:
181+
# -- Size limit for Next.js build cache (.next/cache). Default 100Mi is sufficient for typical Next.js apps with moderate caching needs.
182+
nextjsCache: 100Mi
183+
# -- Size limit for temporary files (/tmp). Default 50Mi provides ample space for Next.js runtime temporary data.
184+
tmp: 50Mi
185+
# capabilities:
186+
# drop:
187+
# - ALL
188+
# runAsNonRoot: true
189+
# runAsUser: 1000
170190
# -- Node taints which will be tolerated for `Pod` [scheduling](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).
171191
tolerations: []
172192

helm/tools/querydoc/templates/deployment.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ spec:
3535
{{- end }}
3636
containers:
3737
- name: querydoc
38+
{{- with .Values.securityContext }}
3839
securityContext:
39-
{{- toYaml .Values.securityContext | nindent 12 }}
40+
{{- toYaml . | nindent 12 }}
41+
{{- end }}
4042
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
4143
imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}
4244
resources:

ui/Dockerfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ RUN mkdir -p /app/ui/public /tmp/nginx/client_temp /tmp/nginx/proxy_temp /tmp/ng
6060
WORKDIR /app
6161
COPY conf/nginx.conf /etc/nginx/nginx.conf
6262
COPY conf/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
63+
COPY scripts/init.sh /usr/local/bin/init.sh
6364

6465
WORKDIR /app/ui
6566
COPY --from=builder /app/ui/next.config.ts ./
@@ -70,7 +71,8 @@ COPY --from=builder --chown=nextjs:nginx /app/ui/.next/static ./.next/static
7071

7172
# Ensure correct permissions
7273
RUN chown -R nextjs:nginx /app/ui && \
73-
chmod -R 755 /app
74+
chmod -R 755 /app && \
75+
chmod +x /usr/local/bin/init.sh
7476

7577
EXPOSE 8080
7678
ARG VERSION
@@ -80,6 +82,6 @@ LABEL org.opencontainers.image.description="Kagent app is the UI and apiserver f
8082
LABEL org.opencontainers.image.authors="Kagent Creators 🤖"
8183
LABEL org.opencontainers.image.version="$VERSION"
8284

83-
USER nextjs
85+
USER 1001
8486

85-
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
87+
CMD ["/usr/local/bin/init.sh"]

ui/scripts/init.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
# Create nginx temp directories
5+
# These are required when running with readOnlyRootFilesystem: true
6+
# The /tmp emptyDir volume is mounted empty at runtime, so we need to
7+
# recreate the directory structure that was created during the Docker build
8+
mkdir -p /tmp/nginx/client_temp \
9+
/tmp/nginx/proxy_temp \
10+
/tmp/nginx/fastcgi_temp \
11+
/tmp/nginx/uwsgi_temp \
12+
/tmp/nginx/scgi_temp
13+
14+
# Start supervisord
15+
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

0 commit comments

Comments
 (0)