Installazione base

Creazione directory e ingresso

$ sudo mkdir /opt/csirt.regione.toscana.it/vector
$ cd  /opt/csirt.regione.toscana.it/vector

Creazione di una password per i DB

$ openssl rand -hex 20
7907640782ec73516069d574a158b56757ee8404

Creazione della rete:

$ docker network create csirt 

Contenuto del file docker-compose.yaml

name: matrix-csirt
services:
    synapse:
        image: "ghcr.io/element-hq/synapse:develop"
        networks:
          - default
          - csirt
        restart: always
        container_name: "matrix-csirt"
        volumes:
            - "./data:/data"
        environment:
            VIRTUAL_HOST: "csirt.regione.toscana.it"
            VIRTUAL_PORT: 8008
            SYNAPSE_SERVER_NAME: "csirt.regione.toscana.it"
            SYNAPSE_REPORT_STATS: "yes"
        ports:
            - "8008:8008/tcp"  
            - "8448:8448/tcp" 
        depends_on:
        - postgresql
    postgresql:
        image: postgres:15.5-bullseye
        restart: always
        networks:
          - default
        environment:
            POSTGRES_PASSWORD: 7907640782ec73516069d574a158b56757ee8404
            POSTGRES_USER: synapse
            POSTGRES_DB: synapse
            POSTGRES_INITDB_ARGS: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'"
        volumes:
            - "./data/postgresdata:/var/lib/postgresql/data"
    redis:
      restart: always
      image: redis:7-alpine
      healthcheck:
        test: ['CMD', 'redis-cli', 'ping']
      volumes:
        - ./redis:/data
networks:
  default:
  csirt:
    external: true

Creazione dell'ambiente iniziale:

docker run -it --rm -v ./data:/data -e SYNAPSE_SERVER_NAME=csirt.regione.toscana.it -e SYNAPSE_REPORT_STATS=yes ghcr.io/element-hq/synapse:develop generate

Modifica del file data/homeserver.yaml per supportare postgres ed eliminare SQLite:

server_name: "csirt.regione.toscana.it"
public_baseurl: "https://matrix.csirt.regione.toscana.it"
pid_file: /data/homeserver.pid
listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    resources:
      - names: [client, federation]
        compress: false
redis:
  enabled: true
  host: redis
database:
    name: psycopg2
    args:
        user: synapse
        password: 7907640782ec73516069d574a158b56757ee8404
        host: postgresql
        database: synapse
        cp_min: 5
        cp_max: 10
log_config: "/data/csirt.regione.toscana.it.log.config"
media_store_path: /data/media_store
registration_shared_secret: "jP7e*OUno*lt3DgKH1drLEZk&kwhtAOAWnSCL6~xaG*+w=Mf,t"
report_stats: true
macaroon_secret_key: "odv7NVlpXJK,iMieCHyrHv6X&ciAG5d1AGCQ;qjzDR9Vh7ftpV"
form_secret: "^gGqv;vuhFKQvF-H^&G;3HBb1QH#rUtb_+psY^Kk;LYxQYd&uZ"
signing_key_path: "/data/csirt.regione.toscana.it.signing.key"
trusted_key_servers:
  - server_name: "matrix.org"

Inizialmente vogliamo permettere la registrazione senza verifiche, dato che vogliamo creare l'utente amministratore. Aggiungiamo quindi le righe

enable_registration: true 
enable_registration_without_verification: true

al precedente file (non ha importanza dove, basta seguire lo schema yaml). Creiamo l'utente amministratore (password nuovamente ottenuta con openssl rand -hex 16):

$ docker compose up -d 
$ docker compose exec synapse register_new_matrix_user --config data/homeserver.yaml --user admin --password f57f4ad2a5f1f526141fbad0f9e91fe5  --admin

A questo punto dovremmo avere 3 container attivi:

$ docker compose ps -a 
NAME                        IMAGE                                COMMAND                  SERVICE      CREATED         STATUS                   PORTS
matrix-csirt                ghcr.io/element-hq/synapse:develop   "/start.py"              synapse      4 minutes ago   Up 4 minutes (healthy)   0.0.0.0:8008->8008/tcp, :::8008->8008/tcp, 0.0.0.0:8448->8448/tcp, :::8448->8448/tcp, 8009/tcp
matrix-csirt-postgresql-1   postgres:15.5-bullseye               "docker-entrypoint.s…"   postgresql   4 minutes ago   Up 4 minutes             5432/tcp
matrix-csirt-redis-1  

Iniziamo quindi con la creazione del reverse proxy con nginx: /etc/nginx/sites-available/matrix.csirt.regione.toscana.it.conf

server {
  listen 80;
  server_name matrix.cisrt.regione.toscana.it;
  return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    listen 8448 ssl http2 default_server;
    listen [::]:8448 ssl http2 default_server;

    server_name matrix.csirt.regione.toscana.it;
    ssl_certificate /etc/letsencrypt/live/matrix.csirt.regione.toscana.it/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/matrix.csirt.regione.toscana.it/privkey.pem;
    ssl_session_timeout 1d;
    access_log /var/log/nginx/matrix.csirt.regione.toscana.it.access.log;
    error_log /var/log/nginx/matrix.csirt.regione.toscana.it.error.log;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_early_data on;
   ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
   ssl_prefer_server_ciphers on;
   ssl_session_cache shared:SSL:50m;
   add_header Strict-Transport-Security max-age=15768000;
   ssl_stapling on;
   ssl_stapling_verify on;

    location ~ ^(/_matrix|/_synapse/client) {

        proxy_pass http://localhost:8008;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;

        client_max_body_size 50M;
        proxy_http_version 1.1;
    }
}

Prima di abilitare il servizio richiediamo un certificato a letsencrypt:

$ sudo certbot certonly --preferred-challenges http -d  matrix.csrit.regione.toscana.it -v

A questo punto possiamo abilitare il sito dopo aver controllato che non ci siano errori di sintassi o simili:

$ sudo ln -sf /etc/nginx/sites-available/matrix.csirt.regione.toscana.it.conf /etc/nginx/sites-enabled 
$ sudo nginx -t 
$ sudo systemctl restart nginx

Come si vede dal file di configurazione, nginx è in ascolto su due porte: 443 e 8448. La prima è utilizzata per le connessioni, la seconda per le federazioni tra server. La cosa è abbastanza orribile, di conseguenza vogliamo che tutto avvenga tramite la 443. Inoltre il nostro mxid ovvero l'identificativo che viene dopo il nome deve essere csirt.regione.toscana.it e non matrix.csirt.regione.toscana.it. Devono essere fatti un paio di aggiustamenti sul server nginx che serve il dominio base. In particolare devono essere aggiunte le seguenti direttive sul file host di nginx:

  location ^~ /.well-known {
          root /var/www/html/matrix;
  }

  location /.well-known/matrix/client {
          access_log off;
          add_header Access-Control-Allow-Origin *;
          default_type application/json;
          return 200 '{"m.homeserver": {"base_url": "https://csirt.regione.toscana.it"}, "org.matrix.msc3575.proxy": {"url": "https://matrix.csirt.regione.toscana.it"}}';
  }

  location /.well-known/matrix/server {
          access_log off;
          add_header Access-Control-Allow-Origin *;
          default_type application/json;
          return 200 '{"m.server": "matrix.csirt.regione.toscana.it:443"}';
  }

Adesso è possibile commentare le righe

    listen 8448 ssl http2 default_server;
    listen [::]:8448 ssl http2 default_server;

Controlliamo quindi che tutto funzioni con il federation test: https://federationtester.matrix.org/

Sliding sync

In questo modo il server è funzionante: le registrazioni sono aperte, possono essere create stanze e la federazione funziona. Tuttavia

The current /sync endpoint scales badly as the number of rooms on an account increases. It scales badly because all rooms are returned to the client, and clients cannot opt-out of a large amount of extraneous data such as receipts. On large accounts with thousands of rooms, the initial sync operation can take minutes to perform. This significantly delays the initial login to Matrix clients, and also makes incremental sync very heavy when resuming after any significant pause in usage.

Dobbiamo abilitare lo sliding-sync. Modifichiamo quindi il file docker-compose.yaml come segue, aggiungendo questi due servizi (proxy per lo sliding-sync e relativo DB). Anche in questo caso le password del db postgres sono create con openssl rand -hex 20, mentre il secret openssl rand -hex 32:

    ssync-proxy:
      image: ghcr.io/matrix-org/sliding-sync:main
      restart: unless-stopped
      ports:
        - "8888:8888/tcp"
      environment:
        - "SYNCV3_SECRET=e822aaf8db24b435ad2f8667230669481ad854906cc55b542bf6069f96f0aec2"
        - "SYNCV3_SERVER=https://matrix.csirt.regione.toscana.it"
        - "SYNCV3_DB=user=syncv3 dbname=syncv3 sslmode=disable host=ssync-db password=24a3c171c4d71ad22dbede055ba32ae53d9ea076"
        - "SYNCV3_BINDADDR=0.0.0.0:8888"
      depends_on:
        - ssync-db
    ssync-db:
      image: docker.io/postgres:15-alpine
      restart: unless-stopped
      environment:
        - POSTGRES_USER=syncv3
        - POSTGRES_PASSWORD=24a3c171c4d71ad22dbede055ba32ae53d9ea076
        - POSTGRES_DB=syncv3
      volumes:
        - "./ssync_db_data:/var/lib/postgresql/data"

E anche il file di configurazione di nginx matrix.csirt.regione.toscana.it deve essere modificato:

 location ~ ^/(client/|_matrix/client/unstable/org.matrix.msc3575/sync) {
    proxy_pass http://localhost:8888;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $host;
} 

Accesso mediante web

All'interno del file docker-compose.yaml si aggiunge il servizio element-web per potersi connettere via browser:

    element-web:
      image: vectorim/element-web:latest
      restart: unless-stopped
      volumes:
        - ./config.json:/app/config.json
      ports:
        - 18080:80

e il file config.json contiene:

{
    "default_server_config": {
        "m.homeserver": {
            "base_url": "https://matrix.csirt.regione.toscana.it",
            "server_name": "matrix.csirt.regione.toscana.it"
        },
        "m.identity_server": {
            "base_url": "https://vector.im"
        }
    },
    "disable_custom_urls": false,
    "disable_guests": false,
    "disable_login_language_selector": false,
    "disable_3pid_login": false,
    "brand": "Element",
    "integrations_ui_url": "https://scalar.vector.im/",
    "integrations_rest_url": "https://scalar.vector.im/api",
    "integrations_widgets_urls": [
        "https://scalar.vector.im/_matrix/integrations/v1",
        "https://scalar.vector.im/api",
        "https://scalar-staging.vector.im/_matrix/integrations/v1",
        "https://scalar-staging.vector.im/api",
        "https://scalar-staging.riot.im/scalar/api"
    ],
    "bug_report_endpoint_url": "https://element.io/bugreports/submit",
    "uisi_autorageshake_app": "element-auto-uisi",
    "default_country_code": "IT",
    "show_labs_settings": false,
    "features": {},
    "default_federate": true,
    "default_theme": "light",
    "room_directory": {
        "servers": ["matrix.org"]
    },
    "enable_presence_by_hs_url": {
        "https://matrix.org": false,
        "https://matrix-client.matrix.org": false
    },
    "setting_defaults": {
        "breadcrumbs": true,
        "UIFeature.deactivate": false,
        "UIFeature.registration": true,
        "UIFeature.passwordReset": false
    },
    "jitsi": {
        "preferred_domain": "meet.element.io"
    },
    "element_call": {
        "url": "https://call.element.io",
        "participant_limit": 8,
        "brand": "Element Call"
    },
    "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
}

Sync Worker

Per bilanciare il carico, aggiungiamo due ulteriori servizi:

    sync-worker:
        image: "ghcr.io/element-hq/synapse:develop"
        container_name: sync-worker
        restart: unless-stopped
        entrypoint: ["/start.py", "run", "--config-path=/data/homeserver.yaml", "--config-path=/data/workers/sync-worker.yaml"]
        volumes:
          - ./data:/data
        environment:
            SYNAPSE_WORKER: synapse.app.generic_worker
        depends_on:
          - postgresql
          - synapse
        ports:
          - "18083:8083/tcp"
    sync-worker-client:
        image: "ghcr.io/element-hq/synapse:develop"
        container_name: sync-worker-client
        restart: unless-stopped
        entrypoint: ["/start.py", "run", "--config-path=/data/homeserver.yaml", "--config-path=/data/workers/sync-worker-client.yaml"]
        volumes:
          - ./data:/data
        environment:
            SYNAPSE_WORKER: synapse.app.generic_worker
        depends_on:
          - postgresql
          - synapse
        ports:
          - "18082:8082/tcp

Dovremo quindi creare i file /data/workers/sync-worker.yaml e /data/workers/sync-worker-client.yaml, oltre a modificare homeserver.yaml

$ sudo mkdir data/workers

Partiamo quindi dal sync-worker-yaml:

worker_app: synapse.app.generic_worker
worker_name: sync-worker

# The replication listener on the main synapse process.

worker_listeners:
 - type: http
   port: 8083
   x_forwarded: true
   resources:
     - names:
       - client
       - federation
 - type: http
   port: 8084
   resources:
     - names: [replication]
   compress: false

e proseguiamo con sync-worker-client.yaml

worker_app: synapse.app.generic_worker
worker_name: sync-worker-client

# The replication listener on the main synapse process.
worker_listeners:
 - type: http
   port: 8082
   x_forwarded: true
   resources:
     - names:
       - client
       - federation
 - type: http
   port: 8085
   resources:
     - names: [replication]
   compress: false

Mappiamo i nuoi servizi in homeserver.yaml: