fix(ess-pro/compose): align with matrix-stack 26.6.1 and fix routing/security gaps

Verified the role against the real matrix-stack Helm chart (pulled 26.6.1)
and fixed divergences found during review.

Bugs:
- Add MAS OIDC discovery router: /.well-known/openid-configuration must hit
  the MAS root listener (8082), not web (8080) — was 404, breaking OIDC login
- Add Synapse ip_range_blacklist (full SSRF blocklist for all outbound
  federation/identity requests; previously only url-preview blacklist present)
- Make federation_client_minimum_tls_version unconditional (chart sets it in
  shared-underrides; role only set it when federation was enabled)
- Restart only rendered fed-reader replicas in the handler instead of the
  whole compose project (missing services: filter)

Chart alignment (26.5.1 -> 26.6.1):
- Bump chart version and all image tags (mas -> matrix-authentication-service
  -pro:1.18.0, postgres 17, synapse v1.154.0-pro.1, element-web v1.12.21, etc.)
- redis maxmemory 256mb -> chart default 40mb (configurable)
- Add element-web map_style_url (configurable)

Hardening / hygiene:
- Validate ess_rtc_external_ip is a real IP (regex, no extra collection dep)
- Read admin password from the in-container secret file instead of passing it
  on the host process list during mas-cli register-user
- apt cache_valid_time, postgres first-boot-only comment, haproxy failover note
- Add meta/argument_specs.yml documenting all public variables
- README: chart version, service count, OIDC discovery verification step

Signed-off-by: Simon Bärlocher <simon@whatwedo.ch>
This commit is contained in:
Simon Bärlocher 2026-06-15 16:34:03 +02:00
parent 32eca6b923
commit ba220d6cd6
No known key found for this signature in database
GPG key ID: 63DE20495932047A
13 changed files with 331 additions and 46 deletions

View file

@ -9,10 +9,10 @@
dest: "{{ ess_compose_conf_dir }}/haproxy/{{ item.dest }}"
mode: "0640"
loop:
- { src: haproxy/haproxy.cfg.j2, dest: haproxy.cfg }
- { src: haproxy/429.http.j2, dest: 429.http }
- { src: haproxy/path_map_file.j2, dest: path_map_file }
- { src: haproxy/path_map_file_get.j2, dest: path_map_file_get }
- { src: haproxy/haproxy.cfg.j2, dest: haproxy.cfg }
- { src: haproxy/429.http.j2, dest: 429.http }
- { src: haproxy/path_map_file.j2, dest: path_map_file }
- { src: haproxy/path_map_file_get.j2, dest: path_map_file_get }
- { src: haproxy/admin-allow-ips.lst.j2, dest: admin-allow-ips.lst }
notify: Restart haproxy
@ -34,8 +34,8 @@
dest: "{{ ess_compose_conf_dir }}/synapse/{{ item.dest }}"
mode: "0640"
loop:
- { src: synapse/homeserver.yaml.j2, dest: homeserver.yaml }
- { src: synapse/log_config.yaml.j2, dest: log_config.yaml }
- { src: synapse/homeserver.yaml.j2, dest: homeserver.yaml }
- { src: synapse/log_config.yaml.j2, dest: log_config.yaml }
- { src: synapse/federation-reader.yaml.j2, dest: federation-reader.yaml }
no_log: true
notify:
@ -65,7 +65,12 @@
mode: "0644"
notify: Restart element-web
- name: Render Postgres init script
# NOTE: This script runs only on FIRST init of an empty PGDATA volume (via
# /docker-entrypoint-initdb.d/). Re-rendering it on later runs has no effect
# on an already-initialised database — the chart's postgres-ess-updater
# sidecar that re-applies it is intentionally omitted (see README). To change
# DBs/roles after init, run the SQL manually or recreate the volume.
- name: Render Postgres init script (first-boot only)
ansible.builtin.template:
src: postgres/configure-dbs.sh.j2
dest: "{{ ess_compose_conf_dir }}/postgres/configure-dbs.sh"

View file

@ -13,6 +13,20 @@
ess_rtc_external_ip in group_vars/ess_servers.yml.
quiet: true
- name: Validate ess_rtc_external_ip looks like an IP address
ansible.builtin.assert:
that:
# IPv4 dotted-quad or IPv6 (contains a colon). Cheap sanity check that
# avoids pulling in the ansible.utils collection just for ipaddr.
- >-
ess_rtc_external_ip is match('^\d{1,3}(\.\d{1,3}){3}$')
or ':' in ess_rtc_external_ip
fail_msg: >-
ess_rtc_external_ip ({{ ess_rtc_external_ip }}) is not a valid IP
address. LiveKit advertises this verbatim in ICE candidates; a bad
value silently breaks Element Call.
quiet: true
- name: Validate OIDC variables when OIDC is enabled
ansible.builtin.assert:
that:

View file

@ -3,12 +3,6 @@
# Create @localadmin via mas-cli, using the ADMIN_USER_PASSWORD generated
# by secrets.yml. Idempotent: mas-cli rejects duplicates, we ignore that.
- name: Read generated admin password
ansible.builtin.slurp:
src: "{{ ess_compose_secrets_dir }}/ADMIN_USER_PASSWORD"
register: _ess_admin_pw_slurp
no_log: true
- name: Check whether the admin user already exists
ansible.builtin.command:
cmd: >
@ -20,16 +14,26 @@
changed_when: false
failed_when: false
# The password is read from the secrets file *inside* the mas container (it is
# already bind-mounted there read-only). Passing it as $(cat ...) inside the
# container shell keeps it off the host's process list — only the host root can
# read it anyway, but this avoids leaking it via `ps` during the exec.
- name: Register admin user (mas-cli)
ansible.builtin.command:
cmd: >
docker compose -f {{ _ess_compose_file }}
exec -T mas
mas-cli --config /conf/mas-config.yaml
manage register-user --yes
--password {{ (_ess_admin_pw_slurp.content | b64decode).strip() | quote }}
--admin
{{ ess_admin_localpart }}
argv:
- docker
- compose
- -f
- "{{ _ess_compose_file }}"
- exec
- -T
- mas
- sh
- -c
- >-
mas-cli --config /conf/mas-config.yaml manage register-user --yes
--password "$(cat /secrets/ess-generated/ADMIN_USER_PASSWORD)"
--admin {{ ess_admin_localpart | quote }}
register: _ess_admin_create
changed_when: "'created' in (_ess_admin_create.stdout + _ess_admin_create.stderr) | lower"
failed_when:

View file

@ -8,6 +8,7 @@
- python3-cryptography
state: present
update_cache: true
cache_valid_time: 3600
- name: Verify docker compose plugin is available
ansible.builtin.command: docker compose version