Merge pull request #26 from jk-cluster/fix/security-hardening
Harden pod and container security contexts across all workloads
This commit is contained in:
31 files changed
+354
-13
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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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).
|
||||
@@ -4,5 +4,5 @@ description: A Helm chart to deploy Apache Tika on Kubernetes
|
||||
|
||||
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"
|
||||
@@ -16,10 +16,19 @@ spec:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.tika.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
image: "apache/tika:{{ .Chart.AppVersion }}"
|
||||
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:
|
||||
- containerPort: 9998
|
||||
protocol: TCP
|
||||
@@ -39,4 +48,7 @@ spec:
|
||||
memory: "{{ .Values.tika.resources.limits.memory }}"
|
||||
cpu: "{{ .Values.tika.resources.limits.cpu }}"
|
||||
ephemeral-storage: "{{ .Values.tika.resources.limits.ephemeralStorage }}"
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
restartPolicy: Always
|
||||
@@ -1,4 +1,18 @@
|
||||
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.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -4,7 +4,7 @@ description: A Helm chart to deploy Cyberchef on Kubernetes
|
||||
|
||||
type: application
|
||||
|
||||
version: 2.0.0+up10.22.1
|
||||
version: 3.0.0+up10.22.1
|
||||
appVersion: "10.22.1"
|
||||
|
||||
maintainers:
|
||||
|
||||
@@ -16,10 +16,21 @@ spec:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.cyberchef.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
image: "ghcr.io/gchq/cyberchef:{{ .Chart.AppVersion }}"
|
||||
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:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
@@ -39,4 +50,9 @@ spec:
|
||||
memory: "{{ .Values.cyberchef.resources.limits.memory }}"
|
||||
cpu: "{{ .Values.cyberchef.resources.limits.cpu }}"
|
||||
ephemeral-storage: "{{ .Values.cyberchef.resources.limits.ephemeralStorage }}"
|
||||
volumes:
|
||||
- name: nginx-cache
|
||||
emptyDir: {}
|
||||
- name: nginx-run
|
||||
emptyDir: {}
|
||||
restartPolicy: Always
|
||||
@@ -1,4 +1,23 @@
|
||||
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:
|
||||
httpGet:
|
||||
path: /
|
||||
|
||||
@@ -4,7 +4,7 @@ description: A Helm chart to deploy Gotenberg on Kubernetes
|
||||
|
||||
type: application
|
||||
|
||||
version: 2.0.0+up8.26.0
|
||||
version: 3.0.0+up8.26.0
|
||||
appVersion: "8.26.0"
|
||||
|
||||
maintainers:
|
||||
|
||||
@@ -16,10 +16,22 @@ spec:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.gotenberg.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
image: "gotenberg/gotenberg:{{ .Chart.AppVersion }}"
|
||||
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:
|
||||
- containerPort: 3000
|
||||
protocol: TCP
|
||||
@@ -39,4 +51,9 @@ spec:
|
||||
memory: "{{ .Values.gotenberg.resources.limits.memory }}"
|
||||
cpu: "{{ .Values.gotenberg.resources.limits.cpu }}"
|
||||
ephemeral-storage: "{{ .Values.gotenberg.resources.limits.ephemeralStorage }}"
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: gotenberg-home
|
||||
emptyDir: {}
|
||||
restartPolicy: Always
|
||||
@@ -1,4 +1,19 @@
|
||||
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.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
||||
@@ -3,7 +3,7 @@ name: homeassistant
|
||||
description: A Helm chart to deploy Home Assistant on Kubernetes
|
||||
type: application
|
||||
|
||||
version: 2.0.0+up2026.2.3
|
||||
version: 3.0.0+up2026.2.3
|
||||
|
||||
appVersion: "2026.2.3"
|
||||
|
||||
|
||||
@@ -17,10 +17,14 @@ spec:
|
||||
name: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
- name: "{{ .Release.Name }}-{{ .Chart.Name }}"
|
||||
image: "ghcr.io/home-assistant/home-assistant:{{ .Chart.AppVersion }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext.container | nindent 12 }}
|
||||
env:
|
||||
- name: TZ
|
||||
value: "{{ .Values.core.timezone }}"
|
||||
@@ -56,7 +60,17 @@ spec:
|
||||
- mountPath: /config
|
||||
name: hass-volume
|
||||
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:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: run
|
||||
emptyDir: {}
|
||||
- name: core-config
|
||||
secret:
|
||||
secretName: {{ .Release.Name }}-{{ .Chart.Name }}-config-secret
|
||||
|
||||
@@ -22,6 +22,26 @@ ingress:
|
||||
# hosts:
|
||||
# - 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:
|
||||
limits:
|
||||
cpu: 1
|
||||
|
||||
@@ -2,7 +2,7 @@ apiVersion: v2
|
||||
name: matrix-synapse
|
||||
description: A Helm chart to deploy a Matrix Synapse server on Kubernetes
|
||||
type: application
|
||||
version: 2.0.0+upv1.149.1
|
||||
version: 3.0.0+upv1.149.1
|
||||
appVersion: "v1.149.1"
|
||||
maintainers:
|
||||
- name: jklein
|
||||
@@ -6,14 +6,24 @@ metadata:
|
||||
data:
|
||||
nginx.conf: |
|
||||
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 {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
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 {
|
||||
listen 80;
|
||||
listen 8080;
|
||||
|
||||
location /.well-known/matrix/server {
|
||||
return 200 '{"m.server": "{{ .Values.ingress.domain }}:443"}';
|
||||
|
||||
@@ -16,16 +16,25 @@ spec:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.matrix.delegation.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
|
||||
image: "nginx:1.28.3"
|
||||
securityContext:
|
||||
{{- toYaml .Values.matrix.delegation.securityContext.container | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: nginx-config
|
||||
mountPath: /etc/nginx/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:
|
||||
limits:
|
||||
memory: "{{ .Values.matrix.delegation.resources.limits.memory }}"
|
||||
@@ -61,6 +70,8 @@ spec:
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 30
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: nginx-config
|
||||
configMap:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-config
|
||||
|
||||
@@ -5,6 +5,21 @@ matrix:
|
||||
delegation:
|
||||
enabled: false
|
||||
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:
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ description: A Helm chart to deploy Patchmon on Kubernetes
|
||||
|
||||
type: application
|
||||
|
||||
version: 2.0.0+up1.4.2
|
||||
version: 3.0.0+up1.4.2
|
||||
appVersion: "1.4.2"
|
||||
|
||||
maintainers:
|
||||
|
||||
@@ -16,10 +16,14 @@ spec:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.patchmon.frontend.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
|
||||
image: "ghcr.io/patchmon/patchmon-frontend:{{ .Chart.AppVersion }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
{{- toYaml .Values.patchmon.frontend.securityContext.container | nindent 12 }}
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
protocol: TCP
|
||||
@@ -59,8 +63,19 @@ spec:
|
||||
- name: patchmon-volume
|
||||
mountPath: /usr/share/nginx/html/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
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: nginx-conf-d
|
||||
emptyDir: {}
|
||||
- name: patchmon-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-pvc
|
||||
@@ -3,6 +3,24 @@ patchmon:
|
||||
size: 10Gi
|
||||
storageClass: null
|
||||
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:
|
||||
requests:
|
||||
cpu: 0.1
|
||||
|
||||
@@ -3,5 +3,5 @@ name: urlaubsverwaltung
|
||||
description: A Helm chart to deploy urlaubsverwaltung on Kubernetes
|
||||
type: application
|
||||
|
||||
version: 2.0.0+up6.5.0
|
||||
version: 3.0.0+up6.5.0
|
||||
appVersion: 6.5.0
|
||||
@@ -16,10 +16,19 @@ spec:
|
||||
app: {{ .Chart.Name }}-{{ .Release.Name }}-uv-deployment
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- toYaml .Values.urlaubsverwaltung.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}-{{ .Release.Name }}-uv-container
|
||||
image: "urlaubsverwaltung/urlaubsverwaltung:{{ .Chart.AppVersion }}"
|
||||
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:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
@@ -134,4 +143,7 @@ spec:
|
||||
periodSeconds: 10
|
||||
failureThreshold: 30
|
||||
timeoutSeconds: 5
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
restartPolicy: Always
|
||||
@@ -7,6 +7,21 @@ ingress:
|
||||
domain: null
|
||||
|
||||
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:
|
||||
requests:
|
||||
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
|
||||
# to the chart and its templates, including the app version.
|
||||
# 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
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
|
||||
@@ -13,9 +13,13 @@ spec:
|
||||
labels:
|
||||
app: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
||||
spec:
|
||||
securityContext:
|
||||
{{- toYaml .Values.zipline.securityContext.pod | nindent 8 }}
|
||||
containers:
|
||||
- name: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs"
|
||||
image: "ghcr.io/diced/zipline:{{ .Chart.AppVersion }}"
|
||||
securityContext:
|
||||
{{- toYaml .Values.zipline.securityContext.container | nindent 12 }}
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
@@ -60,8 +64,14 @@ spec:
|
||||
- name: zipline-volume
|
||||
mountPath: /zipline/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
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: zipline-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-vc
|
||||
@@ -7,6 +7,22 @@ config:
|
||||
domain: null
|
||||
maxUploadFileSize: 30m
|
||||
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);
|
||||
# the endpoint is enabled by default (config.features.healthcheck). Since it
|
||||
# includes the database, it is used for readiness/startup only; liveness
|
||||
|
||||
Reference in new issue
Block a user