From c6a728fa7092f251f7d85bc5dbc851eb2f5ff761 Mon Sep 17 00:00:00 2001 From: Daniel Berteaud Date: Sat, 16 Dec 2023 00:32:03 +0100 Subject: [PATCH] Start working on Synapse + Element --- bundles.yml | 4 + consul/config/service-defaults/matrix.hcl | 3 + consul/config/service-intentions/matrix.hcl | 16 +++ images/matrix-element/Dockerfile | 17 +++ .../root/nginx/conf.d/default.conf.template | 15 ++ images/matrix-synapse/Dockerfile | 65 +++++++++ .../root/entrypoint.d/40-mkdir.sh | 5 + .../root/entrypoint.d/45-synapse.env | 3 + .../root/entrypoint.d/50-synapse-conf.sh | 23 ++++ .../root/opt/synapse/templates/homeserver.yml | 41 ++++++ .../root/opt/synapse/templates/logging.conf | 30 ++++ .../root/usr/local/bin/matrix-synapse | 6 + init/vault-database | 8 ++ matrix.nomad.hcl | 130 ++++++++++++++++++ prep.d/10-mv-conf.sh | 1 + prep.d/20-rand-pwd.sh | 18 +++ templates/homeserver.yml.tpl | 98 +++++++++++++ templates/nginx.conf.tpl | 43 ++++++ variables.yml | 60 ++++++++ vault/policies/matrix-synapse.hcl | 7 + 20 files changed, 593 insertions(+) create mode 100644 bundles.yml create mode 100644 consul/config/service-defaults/matrix.hcl create mode 100644 consul/config/service-intentions/matrix.hcl create mode 100644 images/matrix-element/Dockerfile create mode 100644 images/matrix-element/root/nginx/conf.d/default.conf.template create mode 100644 images/matrix-synapse/Dockerfile create mode 100755 images/matrix-synapse/root/entrypoint.d/40-mkdir.sh create mode 100644 images/matrix-synapse/root/entrypoint.d/45-synapse.env create mode 100755 images/matrix-synapse/root/entrypoint.d/50-synapse-conf.sh create mode 100644 images/matrix-synapse/root/opt/synapse/templates/homeserver.yml create mode 100644 images/matrix-synapse/root/opt/synapse/templates/logging.conf create mode 100755 images/matrix-synapse/root/usr/local/bin/matrix-synapse create mode 100755 init/vault-database create mode 100644 matrix.nomad.hcl create mode 100755 prep.d/10-mv-conf.sh create mode 100755 prep.d/20-rand-pwd.sh create mode 100644 templates/homeserver.yml.tpl create mode 100644 templates/nginx.conf.tpl create mode 100644 variables.yml create mode 100644 vault/policies/matrix-synapse.hcl diff --git a/bundles.yml b/bundles.yml new file mode 100644 index 0000000..5b9120e --- /dev/null +++ b/bundles.yml @@ -0,0 +1,4 @@ +--- + +dependencies: + - url: ../common.git diff --git a/consul/config/service-defaults/matrix.hcl b/consul/config/service-defaults/matrix.hcl new file mode 100644 index 0000000..f3ad32f --- /dev/null +++ b/consul/config/service-defaults/matrix.hcl @@ -0,0 +1,3 @@ +Kind = "service-defaults" +Name = "[[ .instance ]][[ .consul.suffix ]]" +Protocol = "http" diff --git a/consul/config/service-intentions/matrix.hcl b/consul/config/service-intentions/matrix.hcl new file mode 100644 index 0000000..e61a81a --- /dev/null +++ b/consul/config/service-intentions/matrix.hcl @@ -0,0 +1,16 @@ +Kind = "service-intentions" +Name = "[[ .instance ]][[ .consul.suffix ]]" +Sources = [ + { + Name = "[[ .traefik.instance ]]" + Permissions = [ + { + Action = "allow" + HTTP { + PathRegex = "^/_(matrix|synapse)/.*" + Methods = ["GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE"] + } + } + ] + } +] diff --git a/images/matrix-element/Dockerfile b/images/matrix-element/Dockerfile new file mode 100644 index 0000000..7729e5f --- /dev/null +++ b/images/matrix-element/Dockerfile @@ -0,0 +1,17 @@ +FROM nginxinc/nginx-unprivileged:alpine +MAINTAINER [[ .docker.maintainer ]] + +ARG ELEMENT_VERSION=1.11.51 + +ENV ELEMENT_NGINX_BIND_ADDR=0.0.0.0 \ + ELEMENT_NGINX_BIND_PORT=8710 + +USER root + +RUN set -eux &&\ + mkdir -p /opt/element &&\ + curl -sSL https://github.com/element-hq/element-web/releases/download/v${ELEMENT_VERSION}/element-v${ELEMENT_VERSION}.tar.gz |\ + tar xvz -C /opt/element/ --strip-components 1 + +USER nginx +EXPOSE ${ELEMENT_BIND_PORT} diff --git a/images/matrix-element/root/nginx/conf.d/default.conf.template b/images/matrix-element/root/nginx/conf.d/default.conf.template new file mode 100644 index 0000000..db0916b --- /dev/null +++ b/images/matrix-element/root/nginx/conf.d/default.conf.template @@ -0,0 +1,15 @@ +server { + listen ${ELEMENT_NGINX_BIND_ADDR}:${ELEMENT_NGINX_BIND_PORT} default_server; + + location / { + root /opt/element; + index index.html index.htm; + } + + # redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} + diff --git a/images/matrix-synapse/Dockerfile b/images/matrix-synapse/Dockerfile new file mode 100644 index 0000000..478ede6 --- /dev/null +++ b/images/matrix-synapse/Dockerfile @@ -0,0 +1,65 @@ +# syntax=docker/dockerfile:labs + +FROM python:3.11-alpine AS builder + +ARG SYNAPSE_VERSION=1.98.0 + +RUN set -euxo pipefail &&\ + apk --no-cache add \ + curl \ + ca-certificates \ + postgresql15-dev \ + build-base \ + &&\ + mkdir /opt/synapse &&\ + cd /opt/synapse &&\ + python3 -m venv venv &&\ + source venv/bin/activate &&\ + pip --no-cache install \ + bleach \ + matrix-synapse-ldap3 \ + psycopg2 \ + authlib \ + lxml \ + twisted \ + txacme \ + Jinja2 \ + pysaml2 \ + &&\ + pip --no-cache install matrix-synapse==${SYNAPSE_VERSION} + +FROM python:3.11-alpine +MAINTAINER [[ .docker.maintainer ]] + +ENV PATH=/opt/synapse/venv/bin:${PATH} \ + LANG=[[ .locale.lang ]] \ + TZ=[[ .locale.tz ]] \ + SYNAPSE_LISTEN_ADDR=0.0.0.0 \ + SYNAPSE_CONFIG=/data/conf/homeserver.yml \ + SYNAPSE_PG_DB=synapse \ + SYNAPSE_PG_HOST=127.0.0.1 \ + SYNAPSE_PG_PORT=5432 \ + SYNAPSE_PG_USER=synapse \ + SYNAPSE_PG_PASSWORD=synapse + +ADD https://git.lapiole.org/nomad/base_tools.git#master / +COPY --from=builder /opt /opt + +RUN set -euxo pipefail &&\ + apk --no-cache add \ + tini \ + libpq \ + &&\ + addgroup --gid 8008 synapse &&\ + adduser --system --ingroup synapse --disabled-password --uid 8008 --home /opt/synapse --shell /sbin/nologin synapse &&\ + mkdir /data &&\ + chown synapse:synapse /data &&\ + chmod 700 /data + +COPY root/ / + +EXPOSE 8008 +USER synapse +ENTRYPOINT ["tini", "--", "/entrypoint.sh"] +CMD ["matrix-synapse"] + diff --git a/images/matrix-synapse/root/entrypoint.d/40-mkdir.sh b/images/matrix-synapse/root/entrypoint.d/40-mkdir.sh new file mode 100755 index 0000000..8dbd97a --- /dev/null +++ b/images/matrix-synapse/root/entrypoint.d/40-mkdir.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -euo pipefail + +mkdir -p /data/uploads /data/media_store /data/conf diff --git a/images/matrix-synapse/root/entrypoint.d/45-synapse.env b/images/matrix-synapse/root/entrypoint.d/45-synapse.env new file mode 100644 index 0000000..82fa8d9 --- /dev/null +++ b/images/matrix-synapse/root/entrypoint.d/45-synapse.env @@ -0,0 +1,3 @@ +#!/bin/sh + +export SYNAPSE_PUBLIC_URL=${SYNAPSE_PUBLIC_URL:-https://${SYNAPSE_SERVER_NAME}} diff --git a/images/matrix-synapse/root/entrypoint.d/50-synapse-conf.sh b/images/matrix-synapse/root/entrypoint.d/50-synapse-conf.sh new file mode 100755 index 0000000..0b13891 --- /dev/null +++ b/images/matrix-synapse/root/entrypoint.d/50-synapse-conf.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +set -euo pipefail + +source /opt/synapse/venv/bin/activate + +if [ ! -e "${SYNAPSE_CONFIG}" ]; then + echo "Generating a default ${SYNAPSE_CONFIG}" + if [ -z "${SYNAPSE_SERVER_NAME}" ]; then + echo "You must set SYNAPSE_SERVER_NAME environment variable" + exit 1 + fi + envsubst < /opt/synapse/templates/homeserver.yml > ${SYNAPSE_CONFIG} +fi + +echo "Generate missing keys if needed" +python3 -m synapse.app.homeserver \ + --config-path ${SYNAPSE_CONFIG} \ + --generate-missing-configs \ + --report-stats=no \ + --config-directory=/data/conf \ + --keys-directory=/data/conf \ + --data-directory=/data diff --git a/images/matrix-synapse/root/opt/synapse/templates/homeserver.yml b/images/matrix-synapse/root/opt/synapse/templates/homeserver.yml new file mode 100644 index 0000000..0a20961 --- /dev/null +++ b/images/matrix-synapse/root/opt/synapse/templates/homeserver.yml @@ -0,0 +1,41 @@ +server_name: "${SYNAPSE_SERVER_NAME}" +public_baseurl: '${SYNAPSE_PUBLIC_URL}' +listeners: + - port: 8008 + tls: false + type: http + x_forwarded: true + bind_addresses: [${SYNAPSE_LISTEN_ADDR}] + resources: + - names: [client, federation] + compress: false + +database: + name: psycopg2 + args: + database: '${SYNAPSE_PG_DB}' + host: '${SYNAPSE_PG_HOST}' + port: ${SYNAPSE_PG_PORT} + user: '${SYNAPSE_PG_USER}' + password: '${SYNAPSE_PG_PASSWORD}' + +web_client: False +soft_file_limit: 0 +filter_timeline_limit: 5000 +log_config: /opt/synapse/templates/logging.conf +media_store_path: /data/media_store +uploads_path: /data/uploads +registration_shared_secret: "${SYNAPSE_SHARED_SECRET}" +macaroon_secret_key: "${SYNAPSE_MACAROON_SECRET}" +form_secret: "${SYNAPSE_FORM_SECERT}" + +signing_key_path: "/data/conf/signing.key" + +report_stats: false + +trusted_key_servers: + - server_name: "matrix.org" +suppress_key_server_warning: true + +serve_server_wellknown: true + diff --git a/images/matrix-synapse/root/opt/synapse/templates/logging.conf b/images/matrix-synapse/root/opt/synapse/templates/logging.conf new file mode 100644 index 0000000..84ae65b --- /dev/null +++ b/images/matrix-synapse/root/opt/synapse/templates/logging.conf @@ -0,0 +1,30 @@ +version: 1 + +formatters: + precise: + format: '%(name)s - %(lineno)d - %(levelname)s - %(request)s- %(message)s' + +filters: + context: + (): synapse.util.logcontext.LoggingContextFilter + request: "" + +handlers: + console: + class: logging.StreamHandler + formatter: precise + filters: [context] + +loggers: + synapse: + level: ERROR + + synapse.storage.SQL: + # beware: increasing this to DEBUG will make synapse log sensitive + # information such as access tokens. + level: ERROR + +root: + level: ERROR + handlers: [console] + diff --git a/images/matrix-synapse/root/usr/local/bin/matrix-synapse b/images/matrix-synapse/root/usr/local/bin/matrix-synapse new file mode 100755 index 0000000..576251f --- /dev/null +++ b/images/matrix-synapse/root/usr/local/bin/matrix-synapse @@ -0,0 +1,6 @@ +#!/bin/sh + +set -euo pipefail + +source /opt/synapse/venv/bin/activate +exec python3 -m synapse.app.homeserver -c ${SYNAPSE_CONFIG} diff --git a/init/vault-database b/init/vault-database new file mode 100755 index 0000000..269c513 --- /dev/null +++ b/init/vault-database @@ -0,0 +1,8 @@ +#!/bin/sh + +set -euo pipefail + +[[- template "common/vault.mkpgrole.sh.tpl" + dict "ctx" . + "config" (dict "role" (printf "%s-synapse" .instance) "database" "postgres") +]] diff --git a/matrix.nomad.hcl b/matrix.nomad.hcl new file mode 100644 index 0000000..72f033d --- /dev/null +++ b/matrix.nomad.hcl @@ -0,0 +1,130 @@ +[[ $c := merge .matrix.synapse . -]] +job [[ .instance | toJSON ]] { + +[[ template "common/job_start" $c ]] + + group "matrix" { + + network { + mode = "bridge" + } + +[[ template "common/volumes" .matrix.volumes ]] + + service { + name = "[[ .instance ]][[ .consul.suffix ]]" + port = 8008 + + check { + type = "http" + path = "/health" + expose = true + interval = "10s" + timeout = "5s" + task = "synapse" + + check_restart { + limit = 12 + grace = "10m" + } + } + +[[ template "common/connect" merge .matrix . ]] + } + +[[ template "common/task.wait_for" $c ]] + + task "synapse" { + driver = [[ $c.nomad.driver | toJSON ]] + leader = true + + config { + image = [[ $c.image | toJSON ]] + pids_limit = 200 + readonly_rootfs = true + } + + vault { + policies = ["[[ .instance ]]-synapse[[ .consul.suffix ]]"] + env = false + disable_file = true + } + + env { + SYNAPSE_CONFIG = "/secrets/homeserver.yml" + [[ template "common/proxy_env" $c ]] + } + +[[ template "common/file_env" $c.env ]] + + template { + data =<<_EOT +[[ (merge $c.config ((tmpl.Exec "matrix/homeserver.yml.tpl" .) | yaml)) | toYAML ]] +_EOT + destination = "secrets/homeserver.yml" + uid = 100000 + gid = 108008 + perms = 0640 + } + + volume_mount { + volume = "data" + destination = "/data" + } + +[[ template "common/resources" $c.resources ]] + } + +[[ $c = merge .matrix.element . ]] + task "element" { + driver = [[ $c.nomad.driver | toJSON ]] + + lifecycle { + hook = "prestart" + sidecar = true + } + + config { + image = [[ $c.image | toJSON ]] + readonly_rootfs = true + pids_limit = 100 + [[ template "common/tmpfs" dict "size" "5000000" "target" "/tmp" ]] + } + + env { + ELEMENT_BIND_ADDR = "127.0.0.1" + ELEMENT_NGINX_BIND_PORT = "8710" + } + +[[ template "common/resources" $c.resources ]] + } + +[[ $c = merge .matrix.nginx . ]] + task "nginx" { + driver = [[ $c.nomad.driver | toJSON ]] + + lifecycle { + hook = "prestart" + sidecar = true + } + + config { + image = [[ $c.image | toJSON ]] + readonly_rootfs = true + pids_limit = 100 + volumes = [ + "local/nginx.conf:/etc/nginx/conf.d/default.conf" + ] + [[ template "common/tmpfs" dict "size" "5000000" "target" "/tmp" ]] + } + + template { + data =<<_EOT +[[ template "matrix/nginx.conf.tpl" ]] +_EOT + destination = "local/nginx.conf" + } +[[ template "common/resources" $c.resources ]] + } + } +} diff --git a/prep.d/10-mv-conf.sh b/prep.d/10-mv-conf.sh new file mode 100755 index 0000000..4452159 --- /dev/null +++ b/prep.d/10-mv-conf.sh @@ -0,0 +1 @@ +[[ template "common/mv_conf.sh.tpl" dict "ctx" . "services" (dict "matrix" .instance) ]] diff --git a/prep.d/20-rand-pwd.sh b/prep.d/20-rand-pwd.sh new file mode 100755 index 0000000..beb6f28 --- /dev/null +++ b/prep.d/20-rand-pwd.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -euo pipefail + +# Initialize random passwords if needed + +if ! vault kv list [[ .vault.prefix ]]kv/service 2>/dev/null | grep -q -E '^[[ .instance ]]$'; then + vault kv put [[ .vault.prefix ]]kv/service/[[ .instance ]] \ + macaroon_secret_key=$(pwgen -s -n 50 1) \ + form_secret=$(pwgen -s -n 50 1) +fi + +for PWD in macaroon_secret_key form_secret; do + if ! vault kv get -field ${PWD} [[ .vault.prefix ]]kv/service/[[ .instance ]] >/dev/null 2>&1; then + vault kv patch [[ .vault.prefix ]]kv/service/[[ .instance ]] \ + ${PWD}=$(pwgen -s -n 50 1) + fi +done diff --git a/templates/homeserver.yml.tpl b/templates/homeserver.yml.tpl new file mode 100644 index 0000000..3b32b56 --- /dev/null +++ b/templates/homeserver.yml.tpl @@ -0,0 +1,98 @@ +--- + +server_name: [[ .matrix.server_name ]] +public_baseurl: [[ .matrix.public_url ]] +serve_server_wellknown: true +report_stats: false + +web_client: false + +listeners: + - path: /alloc/tmp/synapse.sock + type: http + resources: + - names: + - client + - federation + compress: false + +database: + name: psycopg2 + args: + database: '[[ .matrix.synapse.db.name ]]' + host: '[[ .matrix.synapse.db.host ]]' + port: '[[ .matrix.synapse.db.port ]]' + user: '[[ .matrix.synapse.db.user ]]' + password: '[[ .matrix.synapse.db.password ]]' + +trusted_key_servers: + - server_name: "matrix.org" +suppress_key_server_warning: True + +email: + enable_notifs: true + smtp_host: 127.0.0.1 + smtp_port: 25 + require_transport_security: false + notif_from: "%(app)s " + notif_for_new_users: true + client_base_url: [[ .matrix.public_url ]] + +delete_stale_devices_after: 180d +max_avatar_size: 4M +allowed_avatar_mimetypes: + - image/png + - image/jpeg + - image/gif +forgotten_room_retention_period: 15d +request_token_inhibit_3pid_errors: true + +media_store_path: /data/media_store +media_retention: + local_media_lifetime: 730d + remote_media_lifetime: 14d + +url_preview_enabled: true +url_preview_ip_range_blacklist: + - '127.0.0.0/8' + - '10.0.0.0/8' + - '172.16.0.0/12' + - '192.168.0.0/16' + - '100.64.0.0/10' + - '192.0.0.0/24' + - '169.254.0.0/16' + - '192.88.99.0/24' + - '198.18.0.0/15' + - '192.0.2.0/24' + - '198.51.100.0/24' + - '203.0.113.0/24' + - '224.0.0.0/4' +url_preview_url_blacklist: + - username: '*' + - netloc: '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' + +default_identity_server: https://matrix.org + +macaroon_secret_key: '[[ .matrix.synapse.macaroon_secret_key ]]' +form_secret: '[[ .matrix.synapse.form_secret ]]' + +sso: + client_whitelist: + - [[ .matrix.public_url ]] + update_profile_information: true + +password_config: + enabled: false + +push: + include_content: event_id_only + +server_notices: + system_mxid_localpart: server + system_mxid_display_name: "Notification bot" + +alias_creation_rules: + - user_id: '*' + alias: '*' + action: allow + diff --git a/templates/nginx.conf.tpl b/templates/nginx.conf.tpl new file mode 100644 index 0000000..0b929dd --- /dev/null +++ b/templates/nginx.conf.tpl @@ -0,0 +1,43 @@ +upstream synapse { + server unix:/alloc/tmp/synapse.sock; +} + +upstream element { + server 127.0.0.1:8710; +} + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 127.0.0.1:8008 default_server; + server_tokens off; + root /usr/share/html; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_socket_keepalive on; + client_max_body_size 100m; + set_real_ip_from 127.0.0.1; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + location /_matrix { + proxy_pass http://synapse; + proxy_read_timeout 600; + } + location /_synapse { + proxy_pass http://synapse; + proxy_read_timeout 600; + } + location /health { + proxy_pass http://synapse; + } + location / { + proxy_pass http://element; + } +} diff --git a/variables.yml b/variables.yml new file mode 100644 index 0000000..a6e3037 --- /dev/null +++ b/variables.yml @@ -0,0 +1,60 @@ +--- + +instance: matrix + +matrix: + + server_name: matrix.[[ .consul.domain ]] + public_url: https://matrix.[[ .consul.domain ]] + + consul: + connect: + upstreams: + - destination_name: postgres[[ .consul.suffix ]] + local_bind_port: 5432 + + synapse: + + image: '[[ .docker.repo ]]matrix-synapse:latest' + + env: {} + + config: {} + + macaroon_secret_key: '{{ with secret "[[ .vault.prefix ]]kv/service/[[ .instance ]]" }}{{ .Data.data.macaroon_secret_key }}{{ end }}' + form_secret: '{{ with secret "[[ .vault.prefix ]]kv/service/[[ .instance ]]" }}{{ .Data.data.form_secret }}{{ end }}' + + db: + host: 127.0.0.1 + port: 5432 + name: '[[ .instance ]]-synapse' + user: '{{ with secret "[[ .vault.prefix ]]database/creds/[[ .instance ]]-synapse" }}{{ .Data.username }}{{ end }}' + password: '{{ with secret "[[ .vault.prefix ]]database/creds/[[ .instance ]]-synapse" }}{{ .Data.password }}{{ end }}' + + wait_for: + - service: postgres[[ .consul.suffix ]] + + resources: + cpu: 500 + memory: 384 + + element: + image: '[[ .docker.repo ]]matrix-element:latest' + + env: {} + + resources: + cpu: 20 + memory: 16 + + nginx: + image: nginxinc/nginx-unprivileged:alpine + + resources: + cpu: 20 + memory: 16 + + volumes: + data: + type: csi + source: "[[ .instance ]]-synapse-data" diff --git a/vault/policies/matrix-synapse.hcl b/vault/policies/matrix-synapse.hcl new file mode 100644 index 0000000..b77abb1 --- /dev/null +++ b/vault/policies/matrix-synapse.hcl @@ -0,0 +1,7 @@ +path "[[ .vault.prefix ]]kv/data/service/[[ .instance ]]" { + capabilities = ["read"] +} + +path "[[ .vault.prefix ]]database/creds/[[ .instance ]]-synapse" { + capabilities = ["read"] +}