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:
31 files changed
+357
-16
No files matched your search
@@ -51,6 +51,6 @@ Copy `template-chart/` and replace every `xxx` placeholder — in file names (`t
|
|||||||
- Secrets are 1Password `OnePasswordItem` CRs: values expose `secrets.<name>SecretRef` holding an `op://` item path; the operator materializes the Kubernetes Secret.
|
- Secrets are 1Password `OnePasswordItem` CRs: values expose `secrets.<name>SecretRef` holding an `op://` item path; the operator materializes the Kubernetes Secret.
|
||||||
- Ingress assumes `ingressClassName: nginx` and cert-manager: `ingress.clusterIssuer` and `ingress.domain` are `required` values, TLS secret named `tls-<release>-<chart>-ingress`.
|
- Ingress assumes `ingressClassName: nginx` and cert-manager: `ingress.clusterIssuer` and `ingress.domain` are `required` values, TLS secret named `tls-<release>-<chart>-ingress`.
|
||||||
- `env` entries in values support Helm templating — the deployment renders them through `tpl`, and supports `value`, `secretKeyRef`, and `configMapKeyRef` forms.
|
- `env` entries in values support Helm templating — the deployment renders them through `tpl`, and supports `value`, `secretKeyRef`, and `configMapKeyRef` forms.
|
||||||
- Hardened defaults: `automountServiceAccountToken: false`, non-root user, `readOnlyRootFilesystem: true`, all capabilities dropped; probes and resource requests/limits always set.
|
- Hardened defaults: `automountServiceAccountToken: false`, non-root user, `readOnlyRootFilesystem: true`, all capabilities dropped; probes and resource requests/limits always set. Security contexts are values-configurable (`securityContext.pod` / `securityContext.container`); image-forced deviations are documented with a comment in the chart's `values.yaml`.
|
||||||
- Stateful apps use a StatefulSet in a `<app>.sfs.yaml` template instead of a deployment.
|
- Stateful apps use a StatefulSet in a `<app>.sfs.yaml` template instead of a deployment.
|
||||||
- No chart has dependencies; `charts/` directories are empty.
|
- No chart has dependencies; `charts/` directories are empty.
|
||||||
@@ -62,5 +62,5 @@ For runtime verification (do the workloads actually start, do probes/security se
|
|||||||
- Secrets are **1Password `OnePasswordItem` CRs**: values expose `secrets.<name>SecretRef` holding an `op://` item path; the 1Password operator materializes the Kubernetes Secret in-cluster.
|
- Secrets are **1Password `OnePasswordItem` CRs**: values expose `secrets.<name>SecretRef` holding an `op://` item path; the 1Password operator materializes the Kubernetes Secret in-cluster.
|
||||||
- Ingress assumes ingress-nginx (`ingressClassName: nginx`) and cert-manager: `ingress.clusterIssuer` and `ingress.domain` are required values; the TLS secret is named `tls-<release>-<chart>-ingress`.
|
- Ingress assumes ingress-nginx (`ingressClassName: nginx`) and cert-manager: `ingress.clusterIssuer` and `ingress.domain` are required values; the TLS secret is named `tls-<release>-<chart>-ingress`.
|
||||||
- `env` entries in `values.yaml` are rendered through `tpl`, so values may contain Helm templating; `value`, `secretKeyRef` and `configMapKeyRef` forms are supported.
|
- `env` entries in `values.yaml` are rendered through `tpl`, so values may contain Helm templating; `value`, `secretKeyRef` and `configMapKeyRef` forms are supported.
|
||||||
- Hardened defaults: `automountServiceAccountToken: false`, non-root user, `readOnlyRootFilesystem: true`, all capabilities dropped, probes and resource requests/limits always set.
|
- Hardened defaults: `automountServiceAccountToken: false`, non-root user, `readOnlyRootFilesystem: true`, all capabilities dropped, probes and resource requests/limits always set. Pod and container contexts are configurable via `securityContext.pod` / `securityContext.container` in each chart's values; writable paths (e.g. `/tmp`, nginx cache dirs, s6-overlay's `/run`) are emptyDirs. Where an image cannot satisfy the full standard (e.g. Home Assistant must start as root for s6-overlay, paperless needs a root init container to hand over `/run`), the deviation is minimal and justified with a comment in that chart's `values.yaml`.
|
||||||
- Charts have no dependencies (`charts/` directories are empty).
|
- Charts have no dependencies (`charts/` directories are empty).
|
||||||
@@ -4,5 +4,5 @@ description: A Helm chart to deploy Apache Tika on Kubernetes
|
|||||||
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 5.0.0+up3.2.3.0.full
|
version: 6.0.0+up3.2.3.0.full
|
||||||
appVersion: "3.2.3.0-full"
|
appVersion: "3.2.3.0-full"
|
||||||
@@ -16,10 +16,19 @@ spec:
|
|||||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.tika.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||||
image: "apache/tika:{{ .Chart.AppVersion }}"
|
image: "apache/tika:{{ .Chart.AppVersion }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.tika.securityContext.container | nindent 12 }}
|
||||||
|
volumeMounts:
|
||||||
|
# Tika writes temporary files during parsing; with a read-only
|
||||||
|
# root filesystem /tmp must be a writable emptyDir.
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 9998
|
- containerPort: 9998
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
@@ -39,4 +48,7 @@ spec:
|
|||||||
memory: "{{ .Values.tika.resources.limits.memory }}"
|
memory: "{{ .Values.tika.resources.limits.memory }}"
|
||||||
cpu: "{{ .Values.tika.resources.limits.cpu }}"
|
cpu: "{{ .Values.tika.resources.limits.cpu }}"
|
||||||
ephemeral-storage: "{{ .Values.tika.resources.limits.ephemeralStorage }}"
|
ephemeral-storage: "{{ .Values.tika.resources.limits.ephemeralStorage }}"
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
@@ -1,4 +1,18 @@
|
|||||||
tika:
|
tika:
|
||||||
|
# The official image runs as the dedicated user 35002:35002. /tmp is a
|
||||||
|
# writable emptyDir because Tika writes temporary files while parsing.
|
||||||
|
securityContext:
|
||||||
|
pod:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 35002
|
||||||
|
runAsGroup: 35002
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
# Tika answers "GET /" on its server port with HTTP 200.
|
# Tika answers "GET /" on its server port with HTTP 200.
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description: A Helm chart to deploy Cyberchef on Kubernetes
|
|||||||
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 2.0.0+up10.22.1
|
version: 3.0.0+up10.22.1
|
||||||
appVersion: "10.22.1"
|
appVersion: "10.22.1"
|
||||||
|
|
||||||
maintainers:
|
maintainers:
|
||||||
|
|||||||
@@ -16,10 +16,21 @@ spec:
|
|||||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.cyberchef.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||||
image: "ghcr.io/gchq/cyberchef:{{ .Chart.AppVersion }}"
|
image: "ghcr.io/gchq/cyberchef:{{ .Chart.AppVersion }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.cyberchef.securityContext.container | nindent 12 }}
|
||||||
|
volumeMounts:
|
||||||
|
# nginx needs writable cache/temp dirs and a writable pid file
|
||||||
|
# location when the root filesystem is read-only.
|
||||||
|
- name: nginx-cache
|
||||||
|
mountPath: /var/cache/nginx
|
||||||
|
- name: nginx-run
|
||||||
|
mountPath: /run
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
@@ -39,4 +50,9 @@ spec:
|
|||||||
memory: "{{ .Values.cyberchef.resources.limits.memory }}"
|
memory: "{{ .Values.cyberchef.resources.limits.memory }}"
|
||||||
cpu: "{{ .Values.cyberchef.resources.limits.cpu }}"
|
cpu: "{{ .Values.cyberchef.resources.limits.cpu }}"
|
||||||
ephemeral-storage: "{{ .Values.cyberchef.resources.limits.ephemeralStorage }}"
|
ephemeral-storage: "{{ .Values.cyberchef.resources.limits.ephemeralStorage }}"
|
||||||
|
volumes:
|
||||||
|
- name: nginx-cache
|
||||||
|
emptyDir: {}
|
||||||
|
- name: nginx-run
|
||||||
|
emptyDir: {}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
@@ -1,4 +1,23 @@
|
|||||||
cyberchef:
|
cyberchef:
|
||||||
|
# The upstream image is stock nginx (root, listening on port 80). To still
|
||||||
|
# run fully non-root, the pod runs as the image's nginx user (uid 101) and
|
||||||
|
# the safe sysctl net.ipv4.ip_unprivileged_port_start=80 lets the unprivileged
|
||||||
|
# nginx bind port 80 without NET_BIND_SERVICE.
|
||||||
|
securityContext:
|
||||||
|
pod:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 101
|
||||||
|
runAsGroup: 101
|
||||||
|
sysctls:
|
||||||
|
- name: net.ipv4.ip_unprivileged_port_start
|
||||||
|
value: "80"
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description: A Helm chart to deploy Gotenberg on Kubernetes
|
|||||||
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 2.0.0+up8.26.0
|
version: 3.0.0+up8.26.0
|
||||||
appVersion: "8.26.0"
|
appVersion: "8.26.0"
|
||||||
|
|
||||||
maintainers:
|
maintainers:
|
||||||
|
|||||||
@@ -16,10 +16,22 @@ spec:
|
|||||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.gotenberg.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||||
image: "gotenberg/gotenberg:{{ .Chart.AppVersion }}"
|
image: "gotenberg/gotenberg:{{ .Chart.AppVersion }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.gotenberg.securityContext.container | nindent 12 }}
|
||||||
|
volumeMounts:
|
||||||
|
# Gotenberg (and the Chromium/LibreOffice it drives) needs writable
|
||||||
|
# /tmp and home directories; with a read-only root filesystem these
|
||||||
|
# are provided as emptyDirs.
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: gotenberg-home
|
||||||
|
mountPath: /home/gotenberg
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
@@ -39,4 +51,9 @@ spec:
|
|||||||
memory: "{{ .Values.gotenberg.resources.limits.memory }}"
|
memory: "{{ .Values.gotenberg.resources.limits.memory }}"
|
||||||
cpu: "{{ .Values.gotenberg.resources.limits.cpu }}"
|
cpu: "{{ .Values.gotenberg.resources.limits.cpu }}"
|
||||||
ephemeral-storage: "{{ .Values.gotenberg.resources.limits.ephemeralStorage }}"
|
ephemeral-storage: "{{ .Values.gotenberg.resources.limits.ephemeralStorage }}"
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: gotenberg-home
|
||||||
|
emptyDir: {}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
@@ -1,4 +1,19 @@
|
|||||||
gotenberg:
|
gotenberg:
|
||||||
|
# The official image runs as the dedicated gotenberg user (uid 1001). /tmp
|
||||||
|
# and /home/gotenberg are writable emptyDirs so Chromium/LibreOffice keep
|
||||||
|
# working with a read-only root filesystem.
|
||||||
|
securityContext:
|
||||||
|
pod:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1001
|
||||||
|
runAsGroup: 1001
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
# Gotenberg exposes a documented health endpoint at /health.
|
# Gotenberg exposes a documented health endpoint at /health.
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: homeassistant
|
|||||||
description: A Helm chart to deploy Home Assistant on Kubernetes
|
description: A Helm chart to deploy Home Assistant on Kubernetes
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 2.0.0+up2026.2.3
|
version: 3.0.0+up2026.2.3
|
||||||
|
|
||||||
appVersion: "2026.2.3"
|
appVersion: "2026.2.3"
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ spec:
|
|||||||
name: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
name: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: "{{ .Release.Name }}-{{ .Chart.Name }}"
|
- name: "{{ .Release.Name }}-{{ .Chart.Name }}"
|
||||||
image: "ghcr.io/home-assistant/home-assistant:{{ .Chart.AppVersion }}"
|
image: "ghcr.io/home-assistant/home-assistant:{{ .Chart.AppVersion }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext.container | nindent 12 }}
|
||||||
env:
|
env:
|
||||||
- name: TZ
|
- name: TZ
|
||||||
value: "{{ .Values.core.timezone }}"
|
value: "{{ .Values.core.timezone }}"
|
||||||
@@ -56,7 +60,17 @@ spec:
|
|||||||
- mountPath: /config
|
- mountPath: /config
|
||||||
name: hass-volume
|
name: hass-volume
|
||||||
subPath: hass-config
|
subPath: hass-config
|
||||||
|
# Writable emptyDirs so the root filesystem can stay read-only:
|
||||||
|
# s6-overlay needs /run, Home Assistant writes temp files to /tmp.
|
||||||
|
- mountPath: /tmp
|
||||||
|
name: tmp
|
||||||
|
- mountPath: /run
|
||||||
|
name: run
|
||||||
volumes:
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: run
|
||||||
|
emptyDir: {}
|
||||||
- name: core-config
|
- name: core-config
|
||||||
secret:
|
secret:
|
||||||
secretName: {{ .Release.Name }}-{{ .Chart.Name }}-config-secret
|
secretName: {{ .Release.Name }}-{{ .Chart.Name }}-config-secret
|
||||||
|
|||||||
@@ -22,6 +22,26 @@ ingress:
|
|||||||
# hosts:
|
# hosts:
|
||||||
# - chart-example.local
|
# - chart-example.local
|
||||||
|
|
||||||
|
# Documented deviation from the repo-wide hardening standard: the official
|
||||||
|
# Home Assistant container must start as root — it boots via the s6-overlay
|
||||||
|
# init system, which performs its setup as uid 0 (the image supports no
|
||||||
|
# non-root user), so runAsNonRoot/runAsUser are deliberately not set.
|
||||||
|
# Everything else is hardened: no privilege escalation, all capabilities
|
||||||
|
# dropped and a read-only root filesystem with writable emptyDirs for
|
||||||
|
# /tmp and /run (s6-overlay state). NET_RAW is added back because Home
|
||||||
|
# Assistant's device discovery (DHCP watcher, ping/arp integrations) uses
|
||||||
|
# raw sockets and logs "Operation not permitted" without it.
|
||||||
|
securityContext:
|
||||||
|
pod: {}
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- NET_RAW
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: 1
|
cpu: 1
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ apiVersion: v2
|
|||||||
name: matrix-synapse
|
name: matrix-synapse
|
||||||
description: A Helm chart to deploy a Matrix Synapse server on Kubernetes
|
description: A Helm chart to deploy a Matrix Synapse server on Kubernetes
|
||||||
type: application
|
type: application
|
||||||
version: 2.0.0+upv1.149.1
|
version: 3.0.0+upv1.149.1
|
||||||
appVersion: "v1.149.1"
|
appVersion: "v1.149.1"
|
||||||
maintainers:
|
maintainers:
|
||||||
- name: jklein
|
- name: jklein
|
||||||
@@ -6,15 +6,25 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
nginx.conf: |
|
nginx.conf: |
|
||||||
worker_processes 1;
|
worker_processes 1;
|
||||||
|
# The container runs as the unprivileged nginx user with a read-only root
|
||||||
|
# filesystem: pid file and temp paths live in the /tmp emptyDir, and the
|
||||||
|
# listen port is unprivileged (8080).
|
||||||
|
pid /tmp/nginx.pid;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
worker_connections 1024;
|
worker_connections 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
|
client_body_temp_path /tmp/client_temp;
|
||||||
|
proxy_temp_path /tmp/proxy_temp;
|
||||||
|
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||||
|
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||||
|
scgi_temp_path /tmp/scgi_temp;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 8080;
|
||||||
|
|
||||||
location /.well-known/matrix/server {
|
location /.well-known/matrix/server {
|
||||||
return 200 '{"m.server": "{{ .Values.ingress.domain }}:443"}';
|
return 200 '{"m.server": "{{ .Values.ingress.domain }}:443"}';
|
||||||
add_header Content-Type application/json;
|
add_header Content-Type application/json;
|
||||||
|
|||||||
@@ -16,16 +16,25 @@ spec:
|
|||||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.matrix.delegation.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
|
||||||
image: "nginx:1.28.3"
|
image: "nginx:1.28.3"
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.matrix.delegation.securityContext.container | nindent 12 }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 8080
|
||||||
name: http
|
name: http
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: nginx-config
|
- name: nginx-config
|
||||||
mountPath: /etc/nginx/nginx.conf
|
mountPath: /etc/nginx/nginx.conf
|
||||||
subPath: nginx.conf
|
subPath: nginx.conf
|
||||||
|
# The mounted nginx.conf points the pid file and all temp paths to
|
||||||
|
# /tmp, so a single writable emptyDir suffices with a read-only
|
||||||
|
# root filesystem.
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: "{{ .Values.matrix.delegation.resources.limits.memory }}"
|
memory: "{{ .Values.matrix.delegation.resources.limits.memory }}"
|
||||||
@@ -61,6 +70,8 @@ spec:
|
|||||||
timeoutSeconds: 3
|
timeoutSeconds: 3
|
||||||
failureThreshold: 30
|
failureThreshold: 30
|
||||||
volumes:
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
- name: nginx-config
|
- name: nginx-config
|
||||||
configMap:
|
configMap:
|
||||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-config
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-config
|
||||||
|
|||||||
@@ -5,6 +5,21 @@ matrix:
|
|||||||
delegation:
|
delegation:
|
||||||
enabled: false
|
enabled: false
|
||||||
serverUrl: null
|
serverUrl: null
|
||||||
|
# The delegation nginx runs as the image's unprivileged nginx user
|
||||||
|
# (uid 101) and listens on the unprivileged port 8080; pid file and temp
|
||||||
|
# paths are redirected to the /tmp emptyDir by the mounted nginx.conf.
|
||||||
|
securityContext:
|
||||||
|
pod:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 101
|
||||||
|
runAsGroup: 101
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: "128Mi"
|
memory: "128Mi"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description: A Helm chart to deploy Paperless-NGX on Kubernetes
|
|||||||
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 5.0.0+up2.20.13
|
version: 6.0.0+up2.20.13
|
||||||
|
|
||||||
appVersion: 2.20.13
|
appVersion: 2.20.13
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.smbConnection.cron.securityContext.pod | nindent 12 }}
|
||||||
containers:
|
containers:
|
||||||
- name: paperless-consume-cronjob
|
- name: paperless-consume-cronjob
|
||||||
image: busybox:1.28
|
image: busybox:1.28
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.smbConnection.cron.securityContext.container | nindent 16 }}
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
|
|||||||
@@ -17,10 +17,49 @@ spec:
|
|||||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: false
|
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:
|
containers:
|
||||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
image: "ghcr.io/paperless-ngx/paperless-ngx:{{ .Chart.AppVersion }}"
|
image: "ghcr.io/paperless-ngx/paperless-ngx:{{ .Chart.AppVersion }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.paperless.securityContext.container | nindent 12 }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8000
|
- containerPort: 8000
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
@@ -153,6 +192,12 @@ spec:
|
|||||||
subPath: export/
|
subPath: export/
|
||||||
- mountPath: "/usr/src/paperless/consume"
|
- mountPath: "/usr/src/paperless/consume"
|
||||||
name: "paperless-consume-volume"
|
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:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: "{{ .Values.paperless.resources.requests.memory }}"
|
memory: "{{ .Values.paperless.resources.requests.memory }}"
|
||||||
@@ -189,6 +234,10 @@ spec:
|
|||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
volumes:
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: run
|
||||||
|
emptyDir: {}
|
||||||
- name: paperless-volume
|
- name: paperless-volume
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-volume-claim
|
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-volume-claim
|
||||||
|
|||||||
@@ -42,11 +42,49 @@ paperless:
|
|||||||
cpu: 1
|
cpu: 1
|
||||||
memory: 2048Mi
|
memory: 2048Mi
|
||||||
ephemeralStorage: 400Mi
|
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:
|
smbConnection:
|
||||||
enabled: false
|
enabled: false
|
||||||
server: null
|
server: null
|
||||||
cronJob: "0 0,12 * * *"
|
cronJob: "0 0,12 * * *"
|
||||||
cron:
|
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:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 10m
|
cpu: 10m
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ description: A Helm chart to deploy Patchmon on Kubernetes
|
|||||||
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 2.0.0+up1.4.2
|
version: 3.0.0+up1.4.2
|
||||||
appVersion: "1.4.2"
|
appVersion: "1.4.2"
|
||||||
|
|
||||||
maintainers:
|
maintainers:
|
||||||
|
|||||||
@@ -16,10 +16,14 @@ spec:
|
|||||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.patchmon.frontend.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
|
||||||
image: "ghcr.io/patchmon/patchmon-frontend:{{ .Chart.AppVersion }}"
|
image: "ghcr.io/patchmon/patchmon-frontend:{{ .Chart.AppVersion }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.patchmon.frontend.securityContext.container | nindent 12 }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
@@ -59,8 +63,19 @@ spec:
|
|||||||
- name: patchmon-volume
|
- name: patchmon-volume
|
||||||
mountPath: /usr/share/nginx/html/assets
|
mountPath: /usr/share/nginx/html/assets
|
||||||
subPath: patchmon-assets
|
subPath: patchmon-assets
|
||||||
|
# nginx-unprivileged keeps its pid file and temp dirs in /tmp, and
|
||||||
|
# its entrypoint renders config templates into /etc/nginx/conf.d;
|
||||||
|
# both must be writable with a read-only root filesystem.
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: nginx-conf-d
|
||||||
|
mountPath: /etc/nginx/conf.d
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
volumes:
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: nginx-conf-d
|
||||||
|
emptyDir: {}
|
||||||
- name: patchmon-volume
|
- name: patchmon-volume
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-pvc
|
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-pvc
|
||||||
@@ -3,6 +3,24 @@ patchmon:
|
|||||||
size: 10Gi
|
size: 10Gi
|
||||||
storageClass: null
|
storageClass: null
|
||||||
frontend:
|
frontend:
|
||||||
|
# The frontend image is based on nginx-unprivileged; the pod runs as its
|
||||||
|
# nginx user (uid 101). The image's root-only init script (it would chown
|
||||||
|
# the shared assets volume) is replaced by fsGroup 1000: the same group
|
||||||
|
# the backend runs with, so both can read/write the shared assets.
|
||||||
|
securityContext:
|
||||||
|
pod:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 101
|
||||||
|
runAsGroup: 101
|
||||||
|
fsGroup: 1000
|
||||||
|
fsGroupChangePolicy: Always
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 0.1
|
cpu: 0.1
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ name: urlaubsverwaltung
|
|||||||
description: A Helm chart to deploy urlaubsverwaltung on Kubernetes
|
description: A Helm chart to deploy urlaubsverwaltung on Kubernetes
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 2.0.0+up6.5.0
|
version: 3.0.0+up6.5.0
|
||||||
appVersion: 6.5.0
|
appVersion: 6.5.0
|
||||||
@@ -16,10 +16,19 @@ spec:
|
|||||||
app: {{ .Chart.Name }}-{{ .Release.Name }}-uv-deployment
|
app: {{ .Chart.Name }}-{{ .Release.Name }}-uv-deployment
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.urlaubsverwaltung.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}-{{ .Release.Name }}-uv-container
|
- name: {{ .Chart.Name }}-{{ .Release.Name }}-uv-container
|
||||||
image: "urlaubsverwaltung/urlaubsverwaltung:{{ .Chart.AppVersion }}"
|
image: "urlaubsverwaltung/urlaubsverwaltung:{{ .Chart.AppVersion }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.urlaubsverwaltung.securityContext.container | nindent 12 }}
|
||||||
|
volumeMounts:
|
||||||
|
# The JVM (Tomcat work dir, temp files) needs a writable /tmp with
|
||||||
|
# a read-only root filesystem.
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
name: http
|
name: http
|
||||||
@@ -134,4 +143,7 @@ spec:
|
|||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
failureThreshold: 30
|
failureThreshold: 30
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
@@ -7,6 +7,21 @@ ingress:
|
|||||||
domain: null
|
domain: null
|
||||||
|
|
||||||
urlaubsverwaltung:
|
urlaubsverwaltung:
|
||||||
|
# The official image is built with Cloud Native Buildpacks and runs as the
|
||||||
|
# non-root cnb user (uid 1002, gid 1001). /tmp is a writable emptyDir for
|
||||||
|
# the JVM's temp/work files.
|
||||||
|
securityContext:
|
||||||
|
pod:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1002
|
||||||
|
runAsGroup: 1001
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 0.1
|
cpu: 0.1
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 2.0.0+up4.6.4
|
version: 3.0.0+up4.6.4
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
app: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
||||||
spec:
|
spec:
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.zipline.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
- name: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
||||||
image: "ghcr.io/diced/zipline:{{ .Chart.AppVersion }}"
|
image: "ghcr.io/diced/zipline:{{ .Chart.AppVersion }}"
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.zipline.securityContext.container | nindent 12 }}
|
||||||
env:
|
env:
|
||||||
- name: DATABASE_URL
|
- name: DATABASE_URL
|
||||||
valueFrom:
|
valueFrom:
|
||||||
@@ -60,8 +64,14 @@ spec:
|
|||||||
- name: zipline-volume
|
- name: zipline-volume
|
||||||
mountPath: /zipline/themes
|
mountPath: /zipline/themes
|
||||||
subPath: themes/
|
subPath: themes/
|
||||||
|
# Zipline writes temporary chunks/files below /tmp; with a
|
||||||
|
# read-only root filesystem this must be a writable emptyDir.
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
volumes:
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
- name: zipline-volume
|
- name: zipline-volume
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-vc
|
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-vc
|
||||||
@@ -7,6 +7,22 @@ config:
|
|||||||
domain: null
|
domain: null
|
||||||
maxUploadFileSize: 30m
|
maxUploadFileSize: 30m
|
||||||
zipline:
|
zipline:
|
||||||
|
# The upstream image ships no dedicated user, but the Node.js server runs
|
||||||
|
# fine as an arbitrary non-root uid; fsGroup makes the uploads PVC writable.
|
||||||
|
securityContext:
|
||||||
|
pod:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
fsGroup: 1000
|
||||||
|
fsGroupChangePolicy: Always
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
# Zipline v4 exposes /api/healthcheck (checks server and database health);
|
# Zipline v4 exposes /api/healthcheck (checks server and database health);
|
||||||
# the endpoint is enabled by default (config.features.healthcheck). Since it
|
# the endpoint is enabled by default (config.features.healthcheck). Since it
|
||||||
# includes the database, it is used for readiness/startup only; liveness
|
# includes the database, it is used for readiness/startup only; liveness
|
||||||
|
|||||||
Reference in new issue
Block a user