Harden pod and container security contexts across all workloads

Add securityContext hardening (runAsNonRoot, readOnlyRootFilesystem,
capabilities drop ALL, allowPrivilegeEscalation false) to the 10 workloads
that lacked it: apache-tika, cyberchef, gotenberg, homeassistant,
matrix-delegation-nginx, paperless-server, paperless-consume cronjob
(plus automountServiceAccountToken false), patchmon-frontend,
urlaubsverwaltung and zipline. All contexts are configurable via
securityContext.pod / securityContext.container in values.yaml; writable
paths are provided as emptyDirs. Image-forced deviations (Home Assistant
root start for s6-overlay + NET_RAW for discovery, paperless root init
container for /run ownership) are documented in the charts' values.yaml.
Major version bump for every changed published chart.
This commit is contained in:
jklein committed 2026-08-21 21:49:05 +02:00
1 parent 6dd80e3874
commit b5eba4cbf1
31 files changed
+357 -16

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ description: A Helm chart to deploy Paperless-NGX on Kubernetes
type: application
version: 5.0.0+up2.20.13
version: 6.0.0+up2.20.13
appVersion: 2.20.13
@@ -9,10 +9,15 @@ spec:
spec:
template:
spec:
automountServiceAccountToken: false
securityContext:
{{- toYaml .Values.smbConnection.cron.securityContext.pod | nindent 12 }}
containers:
- name: paperless-consume-cronjob
image: busybox:1.28
imagePullPolicy: IfNotPresent
securityContext:
{{- toYaml .Values.smbConnection.cron.securityContext.container | nindent 16 }}
command:
- /bin/sh
- -c
@@ -17,10 +17,49 @@ spec:
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
spec:
automountServiceAccountToken: false
securityContext:
{{- toYaml .Values.paperless.securityContext.pod | nindent 8 }}
initContainers:
# s6-overlay refuses to start unless /run is owned by the runtime uid,
# but emptyDirs are always created root-owned. This minimal init
# container hands /run over to the paperless uid (see
# paperless.securityContext in values.yaml) so the main container can
# run fully hardened (non-root, no privilege escalation).
- name: init-run-ownership
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- chown {{ .Values.paperless.securityContext.pod.runAsUser }}:{{ .Values.paperless.securityContext.pod.runAsGroup }} /run
securityContext:
runAsNonRoot: false
runAsUser: 0
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- CHOWN
volumeMounts:
- mountPath: /run
name: run
resources:
requests:
cpu: 10m
memory: 16Mi
ephemeral-storage: 1Mi
limits:
cpu: 100m
memory: 32Mi
ephemeral-storage: 10Mi
containers:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
image: "ghcr.io/paperless-ngx/paperless-ngx:{{ .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
securityContext:
{{- toYaml .Values.paperless.securityContext.container | nindent 12 }}
ports:
- containerPort: 8000
protocol: TCP
@@ -153,6 +192,12 @@ spec:
subPath: export/
- mountPath: "/usr/src/paperless/consume"
name: "paperless-consume-volume"
# Writable emptyDirs so the root filesystem can stay read-only:
# s6-overlay needs /run, OCR/consumption uses /tmp for scratch files.
- mountPath: /tmp
name: tmp
- mountPath: /run
name: run
resources:
requests:
memory: "{{ .Values.paperless.resources.requests.memory }}"
@@ -189,6 +234,10 @@ spec:
timeoutSeconds: 5
restartPolicy: Always
volumes:
- name: tmp
emptyDir: {}
- name: run
emptyDir: {}
- name: paperless-volume
persistentVolumeClaim:
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-volume-claim
+38
View File
@@ -42,11 +42,49 @@ paperless:
cpu: 1
memory: 2048Mi
ephemeralStorage: 400Mi
# Paperless-ngx supports rootless operation: the pod runs as uid/gid 1000
# (the image's default paperless user), fsGroup makes the PVCs writable and
# /tmp + /run are emptyDirs so the root filesystem stays read-only.
# Documented deviation: s6-overlay requires /run to be OWNED by the runtime
# uid (group-write is not enough), so a minimal root init container chowns
# the /run emptyDir to runAsUser/runAsGroup before the hardened main
# container starts.
securityContext:
pod:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: Always
container:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
smbConnection:
enabled: false
server: null
cronJob: "0 0,12 * * *"
cron:
# The consume mover only shuffles files between the SMB share (mounted
# with 0777 modes) and the consume PVC, so it runs as the same non-root
# uid/gid as paperless itself with a fully hardened context.
securityContext:
pod:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: Always
container:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: 10m