Files
helm-charts/matrix-synapse/values.yaml
jklein 1ddb5f457e matrix-synapse: strict schema, ingress gate and null-safe hardening
Three changes, across both workloads (Synapse StatefulSet and delegation
nginx Deployment).

1. values.schema.json (#68): additionalProperties: false on every
   chart-owned object level, including the whole matrix.delegation block
   with its own contexts, probes and resources.

2. Ingress gate (#93): the Ingress template is wrapped in
   {{- if .Values.ingress.enabled }} and the key is added to values.yaml
   with default true, so behaviour is unchanged.

   The domain checkpoint found a second reader: the delegation nginx
   serves ingress.domain in its /.well-known/matrix/{server,client}
   endpoints, and read it without a guard. Gating alone would therefore
   have published '{"m.server": ":443"}' and base_url "https://" - a
   federation-breaking answer that renders and starts cleanly. Both reads
   are now required(), so that case fails loudly. Only clusterIssuer
   actually becomes unnecessary when the Ingress is off.

3. Null-safety for hardening, probes and resources (#99) for both
   workloads. The rebuilt probes keep each workload's own endpoint:
   Synapse /health, delegation /.well-known/matrix/server - the endpoint
   that workload exists to serve - including its shorter 3s timeout. The
   delegation pod context keeps having no fsGroup, because it mounts no
   volume that needs one.

The nginx checksum/config annotation from #82 is untouched.

storage stays deliberately not null-safe: an erased block yields an
invalid PVC, which is the loud class #99 excludes.

Rendering is byte-identical to 6.0.0, both with the CI values (which
enable delegation) and with the production bundle values.
2026-08-23 22:05:56 +02:00

165 lines
5.6 KiB
YAML

# Scale the whole chart down without uninstalling it: scaleDown: true renders
# every Deployment/StatefulSet with replicas: 0 (and suspends CronJobs) and
# takes precedence over any per-workload replicas value. PVCs, Secrets and
# Services stay in place; scaleDown: false brings the workloads back up.
scaleDown: false
secrets:
# homeserver.yaml is mounted into the Synapse StatefulSet from the Secret the
# 1Password operator materializes from this item.
#
# Known limitation (#82): a change to the item in the vault does NOT restart
# Synapse. Helm cannot hash the content (it never sees it - the operator fills
# the Secret in-cluster after rendering), so the checksum/config mechanism the
# delegation nginx uses does not apply here. The operator's own
# "operator.1password.io/auto-restart" annotation is no help either: it only
# ever restarts Deployments, and this workload is a StatefulSet (verified
# against onepassword-operator 1.12.0). After changing the item, restart
# Synapse by hand:
# kubectl rollout restart statefulset <release>-matrix-synapse-sfs
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
# 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.
replicas: 1
enabled: false
serverUrl: null
# Auxiliary image, pinned independently of appVersion (which tracks
# Synapse). Reviewed alongside every app-version update round.
image:
repository: nginx
tag: 1.30.4
# 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
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
# CPU limit is only set when configured explicitly under "limits".
limitFactor: 3
requests:
memory: "64Mi"
cpu: "125m"
ephemeralStorage: "50Mi"
storage:
amount: 10Gi
storageClass: longhorn-encrypted
ingress:
# Set to false to omit the Ingress entirely. Note that ingress.domain is also
# read by the delegation nginx, which serves it in the /.well-known/matrix/*
# endpoints, so it stays required whenever matrix.delegation.enabled is true.
# Only clusterIssuer becomes unnecessary.
enabled: true
domain: null
clusterIssuer: null
className: nginx
# Extra annotations for the Ingress; merged with the cert-manager
# cluster-issuer annotation the chart always sets.
annotations: {}