Matrix-Synapse chart: security hardening, naming scheme, configurable probes

- Values-driven securityContext for the Synapse pod/container with hardened
  defaults: runAsNonRoot as uid/gid 1000 (matching the image's previous gosu
  drop target), readOnlyRootFilesystem, allowPrivilegeEscalation false, all
  capabilities dropped; /tmp mounted as emptyDir. The now-ineffective UID/GID
  env vars are removed (the securityContext controls the user).
- Naming scheme: delegation Deployment renamed to ...-delegation-nginx-deployment,
  ConfigMap to ...-delegation-nginx-configmap (references and service selector
  updated).
- matrix.configSecretRef moved to secrets.configSecretRef (repo standard).
- Probes for Synapse and the delegation nginx are values-configurable;
  defaults are the previous hardcoded settings.
- Flexible ingress standard: ingress.className (default nginx) and
  ingress.annotations merged with the cert-manager annotation.
- Additive matrix.env block rendered through tpl (value/secretKeyRef/
  configMapKeyRef forms).
- CI fixtures: secret ref moved, seed job image synced to appVersion.
- Chart 4.2.1+upv1.159.0 -> 5.0.0+upv1.159.0 (major; app version unchanged).
This commit is contained in:
jklein committed 2026-08-22 19:16:21 +02:00
1 parent 69c893599a
commit 0021becba2
10 files changed
+144 -73

No files matched your search

+1 -1
View File
@@ -40,7 +40,7 @@ spec:
containers:
- name: generate
# Same image the chart deploys (appVersion of matrix-synapse).
image: ghcr.io/element-hq/synapse:v1.149.1
image: ghcr.io/element-hq/synapse:v1.159.0
args: ["generate"]
env:
- name: SYNAPSE_SERVER_NAME
+3 -1
View File
@@ -1,6 +1,8 @@
secrets:
configSecretRef: op://ci/matrix-synapse-config
matrix:
serverName: synapse.ci.local
configSecretRef: op://ci/matrix-synapse-config
delegation:
enabled: true
serverUrl: ci.local
+1 -1
View File
@@ -3,7 +3,7 @@ name: matrix-synapse
description: A Helm chart to deploy a Matrix Synapse server on Kubernetes
icon: https://raw.githubusercontent.com/element-hq/synapse/develop/docs/favicon.png
type: application
version: 4.2.1+upv1.159.0
version: 5.0.0+upv1.159.0
appVersion: "v1.159.0"
maintainers:
- name: jklein
@@ -2,7 +2,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-config
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-configmap
data:
nginx.conf: |
worker_processes 1;
@@ -2,18 +2,18 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-deployment
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-deployment
spec:
replicas: {{ include "matrix-synapse.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.matrix.delegation.replicas) }}
selector:
matchLabels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-deployment
template:
metadata:
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-deployment
spec:
automountServiceAccountToken: false
securityContext:
@@ -38,36 +38,17 @@ spec:
resources:
{{- include "matrix-synapse.resources" .Values.matrix.delegation.resources | nindent 12 }}
readinessProbe:
httpGet:
path: /.well-known/matrix/server
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
{{- toYaml .Values.matrix.delegation.readinessProbe | nindent 12 }}
livenessProbe:
httpGet:
path: /.well-known/matrix/server
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
{{- toYaml .Values.matrix.delegation.livenessProbe | nindent 12 }}
startupProbe:
httpGet:
path: /.well-known/matrix/server
port: http
scheme: HTTP
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
{{- toYaml .Values.matrix.delegation.startupProbe | nindent 12 }}
volumes:
- name: tmp
emptyDir: {}
- name: nginx-config
configMap:
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-config
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-configmap
items:
- key: nginx.conf
path: nginx.conf
@@ -5,7 +5,7 @@ metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-service
spec:
selector:
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx
app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-deployment
ports:
- protocol: TCP
port: 80
@@ -3,4 +3,4 @@ kind: OnePasswordItem
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-config-secret
spec:
itemPath: {{ required "You must provide a configSecret Reference" .Values.matrix.configSecretRef }}
itemPath: {{ required "You must provide a configSecret Reference" .Values.secrets.configSecretRef }}
@@ -4,8 +4,11 @@ metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress
annotations:
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided" .Values.ingress.clusterIssuer }}
{{- with .Values.ingress.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
ingressClassName: nginx
ingressClassName: {{ .Values.ingress.className }}
rules:
- host: {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
http:
@@ -18,52 +18,49 @@ spec:
spec:
automountServiceAccountToken: false
securityContext:
fsGroup: 1000
fsGroupChangePolicy: Always
{{- toYaml .Values.matrix.securityContext.pod | nindent 8 }}
containers:
- name: "{{ .Release.Name }}-{{ .Chart.Name }}-container"
image: "ghcr.io/element-hq/synapse:{{ .Chart.AppVersion }}"
securityContext:
{{- toYaml .Values.matrix.securityContext.container | nindent 12 }}
env:
- name: SYNAPSE_SERVER_NAME
value: "{{ required "You must provide a Server-Name" .Values.matrix.serverName }}"
- name: SYNAPSE_REPORT_STATS
value: "no"
- name: UID
value: "1000"
- name: GID
value: "1000"
- name: TZ
value: "{{ .Values.matrix.timezone }}"
{{- range $value := .Values.matrix.env }}
{{- if $value.value }}
- name: {{ $value.name }}
value: {{ tpl $value.value $ }}
{{- else if $value.valueFrom }}
{{- if $value.valueFrom.secretKeyRef }}
- name: {{ $value.name }}
valueFrom:
secretKeyRef:
name: {{ tpl $value.valueFrom.secretKeyRef.name $ }}
key: {{ tpl $value.valueFrom.secretKeyRef.key $ }}
{{- else if $value.valueFrom.configMapKeyRef }}
- name: {{ $value.name }}
valueFrom:
configMapKeyRef:
name: {{ tpl $value.valueFrom.configMapKeyRef.name $ }}
key: {{ tpl $value.valueFrom.configMapKeyRef.key $ }}
{{- end }}
{{- end }}
{{- end }}
ports:
- name: http
containerPort: 8008
protocol: TCP
readinessProbe:
httpGet:
path: /health
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
{{- toYaml .Values.matrix.readinessProbe | nindent 12 }}
livenessProbe:
httpGet:
path: /health
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# The startup probe gates liveness/readiness. Synapse can take a
# while to start (migrations, cache warmup): budget 10s * 30 = 300s.
{{- toYaml .Values.matrix.livenessProbe | nindent 12 }}
startupProbe:
httpGet:
path: /health
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
{{- toYaml .Values.matrix.startupProbe | nindent 12 }}
resources:
{{- include "matrix-synapse.resources" .Values.matrix.resources | nindent 12 }}
volumeMounts:
@@ -76,7 +73,13 @@ spec:
- name: matrix-synapse-config-secret
mountPath: /data/homeserver.yaml
subPath: homeserver.yaml
# Scratch space so the read-only root filesystem never blocks
# temp-file writes (e.g. URL-preview downloads).
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
- name: matrix-synapse-volume
persistentVolumeClaim:
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-pvc
+92 -10
View File
@@ -4,13 +4,75 @@
# Services stay in place; scaleDown: false brings the workloads back up.
scaleDown: false
secrets:
configSecretRef: null
matrix:
# Replica count for this workload; 0 is allowed and scales it down.
# The chart-global scaleDown flag takes precedence.
replicas: 1
serverName: null
timezone: UTC
configSecretRef: null
# The upstream image (ghcr.io/element-hq/synapse) only drops privileges
# itself (via gosu, driven by UID/GID env vars) when started as root; with
# an explicit non-root runAsUser it skips that step and runs directly as
# that user. Synapse writes to /data and /media_store (both on the PVC) and
# to /tmp (mounted as an emptyDir by the chart), so the root filesystem
# stays read-only. The uid/gid 1000 matches the previous gosu default, so
# existing PVC data stays accessible.
securityContext:
pod:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: Always
container:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
livenessProbe:
httpGet:
path: /health
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# The startup probe gates liveness/readiness. Synapse can take a
# while to start (migrations, cache warmup): budget 10s * 30 = 300s.
startupProbe:
httpGet:
path: /health
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
# Extra environment variables, appended to the ones the chart always sets.
# Values are rendered through tpl; value, secretKeyRef and configMapKeyRef
# forms are supported.
env: []
resources:
# Limits are optional: memory and ephemeral-storage limits default to
# limitFactor x the request, an explicitly set limit always wins, and a
# CPU limit is only set when configured explicitly under "limits".
limitFactor: 3
requests:
memory: "1Gi"
cpu: "1"
ephemeralStorage: "1Gi"
delegation:
# Replica count for this workload; 0 is allowed and scales it down.
# The chart-global scaleDown flag takes precedence.
@@ -37,6 +99,31 @@ matrix:
capabilities:
drop:
- ALL
livenessProbe:
httpGet:
path: /.well-known/matrix/server
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /.well-known/matrix/server
port: http
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
startupProbe:
httpGet:
path: /.well-known/matrix/server
port: http
scheme: HTTP
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
resources:
# Limits are optional: memory and ephemeral-storage limits default to
# limitFactor x the request, an explicitly set limit always wins, and a
@@ -46,15 +133,6 @@ matrix:
memory: "64Mi"
cpu: "125m"
ephemeralStorage: "50Mi"
resources:
# Limits are optional: memory and ephemeral-storage limits default to
# limitFactor x the request, an explicitly set limit always wins, and a
# CPU limit is only set when configured explicitly under "limits".
limitFactor: 3
requests:
memory: "1Gi"
cpu: "1"
ephemeralStorage: "1Gi"
storage:
amount: 10Gi
@@ -63,3 +141,7 @@ storage:
ingress:
domain: null
clusterIssuer: null
className: nginx
# Extra annotations for the Ingress; merged with the cert-manager
# cluster-issuer annotation the chart always sets.
annotations: {}