synapse: 1er compte admin cree, repare (bcrypt+PUT v2) et verifie (login OK, API admin OK) + 27-family-users outille
This commit is contained in:
@@ -112,31 +112,51 @@ Tant que l'IdP n'est pas posé, **chaque brique garde son compte local** : c'est
|
||||
|
||||
## 7. État **mesuré** des comptes (12/10, lecture seule — `scripts/15-identity-sweep.sh`, `16-identity-sweep-sqlite.sh`)
|
||||
|
||||
Ce qui suit sort de commandes, pas d'hypothèses. Aucun compte créé, aucun privilège changé,
|
||||
aucune valeur secrète affichée.
|
||||
Ce qui suit sort de commandes, pas d'hypothèses. Balayage **100 % lecture seule** (aucun compte
|
||||
créé, aucun privilège changé, aucune valeur secrète affichée), **sauf** l'exception tracée en §7.1
|
||||
(compte admin Synapse, GO utilisateur explicite du 12/10).
|
||||
|
||||
| Brique | Compteurs mesurés | Drapeaux mesurés | Constat |
|
||||
|---|---|---|---|
|
||||
| **Gitea** | 4 comptes : `Master`, `Folzo`, `Antoine`, `g9ops` | `IsAdmin` = **2** (`Master`, `g9ops`) · **2FA = false sur les 4** | **un bot (`g9ops`) est admin** et **personne n'a de 2FA** → C-ID7 non satisfait, mesuré |
|
||||
| **Nextcloud** | 8 comptes | groupes : `admin` = {Effi, Master} ; `Famille Folzo` = {Audrey, Folzo, carla, ewan, lola, noah} ; `guest_app` | 2FA **available** (`twofactor_totp`, `twofactor_backupcodes`, `twofactor_nextcloud_notification` installés) — activation par utilisateur non mesurée (nécessite la session de l'utilisateur) |
|
||||
| **Synapse** | **2 comptes** : `@ops`, `@ops-bot` | `admin` = **0**, `locked` = 0, `deactivated` = 0, 14 devices | **Trou critique : aucun compte admin.** C-MX1 (6 comptes) et C-MX3 (2FA admin) sont bloqués **à la racine** : il n'y a pas d'admin à protéger, et pas de porte de sortie si le compte `ops-bot` est perdu |
|
||||
| **Synapse** | mesuré 18:40 : **2 comptes** (`@ops`, `@ops-bot`), `admin` = **0** → **re-mesuré 19:00 : 4 comptes, 1 admin utilisable** (`@folzo`), 1 désactivé | `@folzo` : `admin` = 1, `locked` = 0, `deactivated` = 0, hash bcrypt présent, **login = OK**, **API admin = OK** (`GET /_synapse/admin/v2/users` = 3 utilisateurs) | Trou **résolu** (GO utilisateur 12/10, `scripts/26-synapse-admin-repair.sh`) : la brique a désormais un admin → C-MX1 outillé (`scripts/27-synapse-family-users.sh`), C-MX3 débloqué (2FA à activer sur `@folzo`) |
|
||||
| **n8n** | **1 compte** : `effi@openfox.local` (`disabled = 0`) | basic auth déclarée (`N8N_BASIC_AUTH_USER/PASSWORD`) dans `compose/g9-queue` | owner = toi ✓ ; 2FA et clé API = à poser (C-ID9) |
|
||||
| **PhotoPrism** | 3 comptes (`auth_users`) | — | les 3 sont à nommer dans la moitié B |
|
||||
| **Vaultwarden** | — | `ADMIN_TOKEN_FILE` (jeton **dans un fichier**, pas en variable d'env en clair) | bonne pratique déjà en place |
|
||||
| **CouchDB** | — | `GET /_all_databases` = **401** sans identifiants | **auth active** (le « sans auth » du 12/10 était un faux positif de sonde sur `/`, public par conception) |
|
||||
|
||||
### 7.1 Le trou Synapse, et la porte de sortie (à utiliser **toi**)
|
||||
### 7.1 Le trou Synapse : **résolu**, avec la voie qui a marché et celle qui a échoué
|
||||
|
||||
`database.name: sqlite3` → `docker exec synapse python3 -c "…select name,admin from users…"`
|
||||
donne 2 comptes, **aucun admin**. Conséquence mesurée : **aucun moyen officiel de créer un compte
|
||||
famille, de verrouiller un compte, ni de réinitialiser un mot de passe.**
|
||||
Mesure initiale : `database.name: sqlite3` → 2 comptes, **aucun admin** → aucun moyen officiel de
|
||||
créer un compte famille, de verrouiller, ni de réinitialiser un mot de passe.
|
||||
|
||||
Porte de sortie documentée (le levier existe déjà dans la brique, je ne l'utilise pas) :
|
||||
`homeserver.yaml` contient `registration_shared_secret` → l'endpoint
|
||||
`POST /_synapse/admin/v1/register` accepte une inscription signée avec ce secret, ce qui permet de
|
||||
créer **le premier compte admin** (puis `admin: true` en base). C'est un geste **humain**
|
||||
(création de compte = interdit permanent G11) ; la procédure complète est à écrire dans
|
||||
`RUNBOOKS/matrix.md` avant, et le secret ne doit **pas** être recopié dans le kit.
|
||||
**Voie tentée et échouée** (`scripts/17-synapse-admin.sh`, inscription signée par
|
||||
`registration_shared_secret` sur `POST /_synapse/admin/v1/register`) : **`HMAC incorrect`**, testé
|
||||
avec `username` en localpart **et** en mxid complet, secret relevé identique entre `sed` et python
|
||||
(sha256). Le secret n'a **jamais** été recopié dans le kit ni affiché.
|
||||
|
||||
Rollback : `docker exec synapse python3 -c "update users set admin=0 where name='…'"` (à faire
|
||||
avec `mode=ro` d'abord pour vérifier, puis écriture assumée).
|
||||
**Voie qui a marché** (`scripts/26-synapse-admin-repair.sh`, GO utilisateur) :
|
||||
1. `POST /_matrix/client/v3/register` (flows `m.login.dummy`, `enable_registration: true`) → compte
|
||||
créé, mais Synapse **ignore le localpart demandé** → mxid aléatoire, et un bug d'arguments du
|
||||
script l'a laissé avec `password_hash` **NULL** → admin=1 **inutilisable**.
|
||||
2. Réparation : le module **`bcrypt` est présent dans l'image synapse** (5.0.0) → hash bcrypt 12 du
|
||||
mot de passe du credential écrit sur le compte admin, **`docker restart`** (les caches sqlite de
|
||||
Synapse ne sont pas invalidés par une écriture directe), login → token.
|
||||
3. **`PUT /_synapse/admin/v2/users/@folzo:192.168.1.24`** avec `{password, admin:true}` — l'API
|
||||
admin v2 **crée par PUT** (POST = `Unrecognized request`) → compte propre, admin, login **OK**.
|
||||
4. Compte aléatoire : `admin=0`, `deactivated=1`, `password_hash=NULL` (conservé, jamais purgé).
|
||||
|
||||
Preuves mesurées : `--verify folzo` = **login OK** ; `--admin-check folzo` = **API admin OK**
|
||||
(3 utilisateurs listés, 1 admin). Snapshot sqlite avant toute écriture :
|
||||
`/data/homeserver.db.bak-20261012-1900` (rollback = restaurer ce fichier, conteneur arrêté).
|
||||
|
||||
**Ce qui reste humain** : changer le mot de passe de `@folzo` à la 1ʳᵉ connexion (Element), activer
|
||||
le **2FA/TOTP + codes de récupération** (C-MX3), créer les 6 comptes famille
|
||||
(`scripts/27-synapse-family-users.sh`, `admin:false`, mots de passe en 600 → coffre), noter la
|
||||
**phrase de récupération E2E sur papier** par appareil.
|
||||
|
||||
**Trou ouvert consigné** : `enable_registration: True` (et `registration_without_verification` dans
|
||||
`homeserver.yaml`) = **n'importe quel client du LAN peut créer un compte**. À fermer (GO) une fois
|
||||
l'admin en place : `enable_registration: false` + restart — la création passe alors par l'API admin.
|
||||
Rollback : remettre `true`.
|
||||
|
||||
@@ -18,8 +18,8 @@ geste physique, usage humain (G11 : interdits permanents + L3).
|
||||
|
||||
| Critère | Geste | Qui | Vérification |
|
||||
|---|---|---|---|
|
||||
| **C-MX1** 6 comptes | API admin, `admin:false`, 2 adultes + 4 enfants ; phrase de récupération E2E **sur papier** ; mots de passe dans le coffre | toi | API liste les 6, `admin:false`, 0 secret en git |
|
||||
| **C-MX3** 2FA admin Synapse | TOTP sur les comptes admin + codes de récupération consignés | toi | login admin exige l'OTP (mesuré) |
|
||||
| **C-MX1** 6 comptes | **admin en place et mesuré (12/10 19:00)** → `bash scripts/27-synapse-family-users.sh --dry` puis `CONFIRM=CREER bash scripts/27-synapse-family-users.sh` (2 adultes + 4 enfants, `admin:false`, mots de passe en 600 → **recopier dans le coffre puis `rm`**) ; phrase de récupération E2E **sur papier** par appareil | toi | API liste les 6, `admin:false`, 0 secret en git |
|
||||
| **C-MX3** 2FA admin Synapse | sur `@folzo` (login **mesuré OK**) : changer le mot de passe à la 1ʳᵉ connexion, activer **TOTP** + codes de récupération dans Element, les consigner `infra/matrix/break-glass` | toi | login admin exige l'OTP (mesuré) |
|
||||
| **C-MX2** Element | installer Element sur **2 appareils adultes** seulement ; salon famille + salon ops | toi | session active sur les 2 + messages échangés |
|
||||
| **C-FM1** coffre unique + formation | Vaultwarden : `Famille` + `Enfants`, coffre propre 16/13 avec emergency access, 10/9 sans coffre, import Takeout, passkeys Android ; atelier 1 h (femme) + 30 min/ado | toi | **chacune le fait seule 7 jours après** (journalisé) |
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
| Symptôme | Diagnostic (lecture seule) | Action | Rollback |
|
||||
|---|---|---|---|
|
||||
| Pas de compte admin | login admin → 403 | ajouter `registration_shared_secret` (sudo, `homeserver.yaml`) → restart → `docker exec <synapse> register_new_matrix_user -a -u <admin>` → **retirer** le secret → restart | retirer la ligne + restart |
|
||||
| Création de compte refusée | `PUT /_synapse/admin/v2/users/@<uid>:192.168.1.24` (token admin) | créer avec `admin:false` ; inscription publique reste **coupée** (voulu) | `POST /_synapse/admin/v1/deactivate/<uid>` (conserve la trace) |
|
||||
| Pas de compte admin | `select name,admin,deactivated,case when password_hash is null then 0 else 1 end from users` (sqlite `mode=ro`) + login → « Invalid username or password » | **`scripts/26-synapse-admin-repair.sh`** (voie mesurée 12/10) : hash **bcrypt** du mot de passe écrit en base sur un compte admin → **`docker restart`** (caches) → login → **`PUT /_synapse/admin/v2/users/@<uid>`** `{password, admin:true}` → retirer l'admin temporaire. Voie officielle à réessayer : `docker exec synapse register_new_matrix_user -a -u <admin> -c /data/homeserver.yaml` (le secret est **déjà** dans `homeserver.yaml` ; `POST /_synapse/admin/v1/register` signé a répondu **`HMAC incorrect`**) | snapshot sqlite **avant** (`sqlite3 .backup` → `/data/homeserver.db.bak-*`) + `update users set admin=0, deactivated=1, password_hash=NULL where name='<temporaire>'` |
|
||||
| Création de compte refusée | `PUT /_synapse/admin/v2/users/@<uid>:192.168.1.24` (token admin) | **PUT** (pas POST) ; `scripts/27-synapse-family-users.sh` (`--dry`, `--revoke`) ; inscription publique à **couper** (`enable_registration: false`) une fois l'admin en place | `PUT … {"deactivated": true}` (conserve la trace) |
|
||||
| Alerte non reçue dans le salon | `sudo python3 /usr/local/bin/g9-verify-alert.py 20` ; `sudo bash /usr/local/bin/g9-probes.sh --selftest` | token/room dans `/etc/restic/matrix.env` (600) → régénérer le token `ops-bot` si révoqué | restaurer `/etc/restic/matrix.env` (backup daté) |
|
||||
| E2E illisible sur un appareil | session/device inconnu | phrase de récupération E2E **notée sur papier** (procédure B) ; vérifier les devices via API admin | aucun (clé perdue = session perdue, d'où le papier) |
|
||||
| Fused de logs / disque | `du -sh /data/*` + logs Synapse | purge via `synapse_stitch_schemas`/`purge` documenté **après** backup | restauration restic |
|
||||
|
||||
@@ -154,3 +154,4 @@
|
||||
| 2026-10-12 18:45 | 4.5 | trou **Synapse sans admin** : porte de sortie outillée, **non exécutée** | C-MX1, C-MX3, C-ID4 : la brique n'a pas de compte admin → ni création de comptes famille, ni réinitialisation possible | `scripts/17-synapse-admin.sh` : inscription signée via `registration_shared_secret` + `POST /_synapse/admin/v1/register` (secret lu à l'exécution dans le conteneur, **jamais affiché ni recopié**, mot de passe saisi masqué, longueur ≥ 12, confirmation `CREER` pour un admin). **Vérifié jusqu'à la dernière étape sans rien créer** : `GET /_synapse/admin/v1/register` → nonce reçu, `registration_shared_secret` présent dans `homeserver.yaml` (`FOUND`). Procédure consignée dans `RUNBOOKS/matrix.md` + §7.1 du registre. **Exécution = toi** (création de compte = interdit permanent G11) | `docker exec synapse python3 -c "… update users set admin=0 where name='<compte>'"` (désactivation propre, jamais de purge) |
|
||||
| 2026-10-12 18:30 | 4.5 | **dumps logiques cohérents des 3 bases** (`18-db-dumps.sh`, GO utilisateur) | C-P5 : corriger le trou mesuré « copie chaude = complète en octets, pas garantie rejouable » | `pg_dump` Gitea (auth **socket `trust`** mesurée dans `pg_hba.conf` → **aucun mot de passe sur une ligne de commande**) = **80 543 o** gz, **116 `CREATE TABLE`** ; `mysqldump --single-transaction --routines` Nextcloud = **5,9 Mo** gz, **317** objets, se termine par `-- Dump completed` ; idem PhotoPrism = **89,6 Mo** gz, **39** objets, `-- Dump completed`. **Aucune brique stoppée** (`--single-transaction` = instantané InnoDB cohérent). Dumps écrits sous `/data/docker/<brique>/dumps/` → **déjà dans le périmètre restic** (visibles au prochain backup de 03:30). **Rejouabilité prouvée pour Gitea** : base de test `gtest_restore` créée, dump chargé, **116 tables restaurées** (== 116 du dump), base **supprimée** (`pg_database` = gitea_db/postgres/template0/template1, vérifié) | dumps : `rm -f /data/docker/{gitea,nextcloud,photoprism}/dumps/*-20261012-1825Z.sql.gz`. **Test MariaDB complet non fait** : le dump Nextcloud contient `USE \`nextcloud\`` → le charger tel quel écraserait la production ; il faut un serveur jeton (GO séparé) |
|
||||
| 2026-10-12 18:35 | 4.5 | **hub de monitoring posé hors du G9** (J5040) | C-AP5 : le trou mesuré était « les sondes tournent depuis le G9 → elles meurent avec lui » ; l'ancien plan (05-beszel.sh) mettait le hub sur **.25, la machine à mourir en Phase 6** | `compose/beszel-hub/docker-compose.yml` (kit) → `henrygd/beszel@sha256:6b39a8b64f9b15d00a25e7683e31a93c46161182a54f62713525107064e10599` (épinglé digest, label watchtower=false, base dans `/home/folzo/beszel-hub/data`) ; `docker compose up -d` ; **règle ufw LAN-only ajoutée** : `sudo ufw insert 1 allow proto tcp from 192.168.1.0/24 to any port 8090 comment "beszel hub LAN-only"` → **jamais joignable depuis le WAN** ; mesure : `http://192.168.1.27:8090/` = **200** ; superuser créé par CLI (`/beszel superuser create`, **mot de passe généré sur la machine, écrit en 600 dans `/home/folzo/beszel-admin.cred`, jamais affiché, jamais commité**). **Reste** : les agents (.24/.27) + le token universel, qui se crée dans l'UI (`/settings/tokens`) — la voie API PocketBase n'est pas trouvée (`/api/collections/_superusers/auth-with-password` = 404, `…/authenticators/superusers/email` = « resource not found », `/api/collections/_superusers/records` = 403 → API bien présente, chemin d'auth non identifié) | `cd /home/folzo/g9-migration/compose/beszel-hub && docker compose down` ; `sudo ufw delete <n° 8090>` ; `rm -f /home/folzo/beszel-admin.cred` ; `rm -rf /home/folzo/beszel-hub` |
|
||||
| 2026-10-12 19:00 | 4.5 | **compte admin Synapse créé et réparé** (`26-synapse-admin-repair.sh`, GO utilisateur) | C-MX1/C-MX3/C-ID4 : la brique n'avait **aucun admin** (`admin` = 0 sur `@ops` et `@ops-bot`) → ni création de comptes famille, ni réinitialisation possible | `POST /_matrix/client/v3/register` (`m.login.dummy`) → compte créé mais Synapse **ignore le localpart** (mxid aléatoire) et le bug d'arguments de `21-…` l'a laissé **`password_hash` = NULL** → admin=1 **inutilisable** (login « Invalid username or password », et `/_synapse/admin/v1/register` signé = **`HMAC incorrect`**, secret jamais recopié ni affiché). Réparation mesurée : **`bcrypt` 5.0.0 est dans l'image synapse** → hash bcrypt 12 du mot de passe du credential écrit en base (mot de passe **jamais en argv** : stdin + `curl -K` fichier 600 + tmpdir 700) → **`docker restart`** (Synapse ne vide pas ses caches sqlite sur écriture directe) → login OK → **`PUT /_synapse/admin/v2/users/@folzo:192.168.1.24`** `{password, admin:true}` (l'API v2 **crée par PUT**, POST = `Unrecognized request`) → compte temporaire `admin=0`, `deactivated=1`, `password_hash=NULL`. **Preuves** : `--verify folzo` = **login OK** ; `--admin-check folzo` = **API admin OK** (3 utilisateurs, 1 admin) ; lecture seule finale : 4 comptes, 1 admin actif, 1 désactivé. `27-synapse-family-users.sh` ajouté (`--dry` mesuré = 6 PUT planifiés, `--revoke` = désactivation) | snapshot sqlite **avant** toute écriture : `/data/homeserver.db.bak-20261012-1900` (rollback = arrêter `synapse`, restaurer le fichier, redémarrer) ; compte aléatoire conservé désactivé (0 purge) ; `rm -f /tmp/2[567]-*.sh` sur le G9 |
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# 20-synapse-first-admin.sh — crée le PREMIER compte admin Synapse (GO utilisateur 12/10).
|
||||
# Constat mesuré : 2 comptes, admin = 0 -> aucune porte de sortie.
|
||||
# Le mot de passe est GENERE SUR LA MACHINE, écrit en 600 dans un fichier root/propriétaire,
|
||||
# jamais affiché, jamais commité. L'utilisateur doit le changer à la première connexion.
|
||||
# Usage : bash 20-synapse-first-admin.sh <localpart>
|
||||
set -euo pipefail
|
||||
|
||||
USER_LOCAL="${1:?usage: $0 <localpart>}"
|
||||
SYNAPSE_URL="${SYNAPSE_URL:-http://192.168.1.24:8008}"
|
||||
CTN="${CTN:-synapse}"
|
||||
CFG="${CFG:-/data/homeserver.yaml}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-$USER_LOCAL.cred}"
|
||||
|
||||
umask 077
|
||||
PW="$(openssl rand -base64 24)"
|
||||
printf 'user=@%s:192.168.1.24\npassword=%s\n' "$USER_LOCAL" "$PW" > "$CRED"
|
||||
chmod 600 "$CRED"
|
||||
|
||||
SECRET="$(docker exec "$CTN" sh -c "sed -n 's/^registration_shared_secret: \"\(.*\)\"/\1/p' '$CFG'")"
|
||||
[[ -n "$SECRET" ]] || { echo "secret introuvable — abandon"; exit 1; }
|
||||
NONCE="$(curl -s -m 10 "$SYNAPSE_URL/_synapse/admin/v1/register" | python3 -c 'import json,sys; print(json.load(sys.stdin)["nonce"])')"
|
||||
[[ -n "$NONCE" ]] || { echo "nonce vide — abandon"; exit 1; }
|
||||
|
||||
MAC="$(python3 - "$SECRET" "$NONCE" "$USER_LOCAL" "$PW" "true" <<'PY'
|
||||
import hmac, hashlib, sys
|
||||
print(hmac.new(sys.argv[1].encode(), "\n".join(sys.argv[2:]).encode(), hashlib.sha256).hexdigest())
|
||||
PY
|
||||
)"
|
||||
BODY="$(python3 - "$NONCE" "$USER_LOCAL" "$PW" "$MAC" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"nonce": sys.argv[1], "username": sys.argv[2], "password": sys.argv[3],
|
||||
"mac": sys.argv[4], "admin": "true"}))
|
||||
PY
|
||||
)"
|
||||
unset PW SECRET MAC
|
||||
|
||||
RESP="$(curl -s -m 20 -X POST "$SYNAPSE_URL/_synapse/admin/v1/register" -H 'Content-Type: application/json' -d "$BODY")"
|
||||
unset BODY
|
||||
printf '%s' "$RESP" | python3 -c 'import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
print("réponse:", d.get("user_id", d.get("error", d)))' 2>/dev/null || printf 'réponse: %s\n' "$(printf '%s' "$RESP" | cut -c1-160)"
|
||||
|
||||
echo "== vérification (lecture seule) =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
c = sqlite3.connect('file:/data/homeserver.db?mode=ro', uri=True).cursor()
|
||||
print('comptes total/admin:', c.execute('select count(*), sum(admin) from users').fetchone())
|
||||
for r in c.execute('select name, admin, locked, deactivated from users order by admin desc'):
|
||||
print(' ', r)
|
||||
"
|
||||
echo "identifiants : $CRED (600) — à changer à la première connexion, puis 2FA (C-MX3)"
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# 21-synapse-admin-via-register.sh — 1er compte admin Synapse, voie réellement mesurée.
|
||||
#
|
||||
# Mesures 12/10 :
|
||||
# - inscription signée `/_synapse/admin/v1/register` -> « HMAC incorrect » (testé en localpart
|
||||
# ET en mxid complet ; secret vérifié identique, sha256 de la lecture sed == lecture python)
|
||||
# - `POST /_matrix/client/v3/register` -> flows = ["m.login.dummy"] : **inscription ouverte à
|
||||
# tout client du LAN** (constat de sécurité : `enable_registration_without_verification: true`)
|
||||
# - base sqlite `/data/homeserver.db` : `admin` = 0 sur les 2 comptes existants
|
||||
#
|
||||
# GO utilisateur 12/10 pour créer le compte admin. Mot de passe généré sur la machine, écrit en
|
||||
# 600, jamais affiché, jamais commité. L'élévation de privilège est un UPDATE ciblé `admin=1`.
|
||||
# Usage : bash 21-synapse-admin-via-register.sh <localpart>
|
||||
set -euo pipefail
|
||||
|
||||
LOCAL="${1:?usage: $0 <localpart>}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-$LOCAL.cred}"
|
||||
URL="${URL:-http://192.168.1.24:8008}"
|
||||
CTN="${CTN:-synapse}"
|
||||
DB="${DB:-/data/homeserver.db}"
|
||||
SERVER_NAME="${SERVER_NAME:-192.168.1.24}"
|
||||
|
||||
if [[ -f "$CRED" ]]; then
|
||||
PW="$(python3 -c "print(open('$CRED').read().split('password=')[1].strip())")"
|
||||
else
|
||||
umask 077
|
||||
PW="$(openssl rand -base64 24)"
|
||||
printf 'user=@%s:%s\npassword=%s\n' "$LOCAL" "$SERVER_NAME" "$PW" > "$CRED"
|
||||
chmod 600 "$CRED"
|
||||
fi
|
||||
|
||||
SESSION="$(curl -s -m 10 -X POST "$URL/_matrix/client/v3/register" -H 'Content-Type: application/json' -d '{}' \
|
||||
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("session",""))')"
|
||||
if [[ -z "$SESSION" ]]; then echo "KO session vide"; exit 1; fi
|
||||
|
||||
BODY="$(python3 - "$SESSION" "$LOCAL" "$PW" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({
|
||||
"username": sys.argv[1],
|
||||
"password": sys.argv[2],
|
||||
"auth": {"type": "m.login.dummy"},
|
||||
"session": sys.argv[3],
|
||||
}))
|
||||
PY
|
||||
)"
|
||||
RESP="$(curl -s -m 20 -X POST "$URL/_matrix/client/v3/register" -H 'Content-Type: application/json' -d "$BODY")"
|
||||
unset BODY PW
|
||||
printf '%s' "$RESP" | python3 -c 'import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
print("inscription:", d.get("user_id") or d.get("error") or d)' 2>/dev/null || printf 'réponse: %s\n' "$(printf '%s' "$RESP" | cut -c1-140)"
|
||||
|
||||
MXID="@${LOCAL}:${SERVER_NAME}"
|
||||
echo "== élévation de privilège (UPDATE ciblé admin=1) =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
con = sqlite3.connect('$DB')
|
||||
cur = con.cursor()
|
||||
cur.execute('update users set admin=1 where name=?', ('$MXID',))
|
||||
con.commit()
|
||||
print('lignes concernées:', cur.rowcount)
|
||||
"
|
||||
echo "== vérification (lecture seule) =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
c = sqlite3.connect('file:$DB?mode=ro', uri=True).cursor()
|
||||
print('comptes total/admin:', c.execute('select count(*), sum(admin) from users').fetchone())
|
||||
for r in c.execute('select name, admin, locked, deactivated from users order by admin desc'):
|
||||
print(' ', r)
|
||||
"
|
||||
echo "identifiants : $CRED (600) — à changer à la première connexion, puis 2FA (C-MX3)"
|
||||
echo "rollback : docker exec $CTN python3 -c \"import sqlite3; sqlite3.connect('$DB').execute('update users set admin=0 where name=?', ('$MXID',))\""
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# 22-synapse-admin-finish.sh — rendre l'admin UTILISABLE et propre.
|
||||
#
|
||||
# Mesuré : `POST /_matrix/client/v3/register` avec `username` fourni -> Synapse **ignore** le
|
||||
# localpart demandé et attribue un mxid aléatoire (`@mpijdqsyryjrrfmjdoraqcxf:…` au 12/10).
|
||||
# Ce script : (1) donne admin au compte créé, (2) prouve que l'API admin répond (login mot de
|
||||
# passe -> token, jamais affiché), (3) crée le compte propre demandé via l'API admin v2,
|
||||
# (4) désactive le compte aléatoire.
|
||||
# Usage : bash 22-synapse-admin-finish.sh <mxid_aleatoire_cree> <localpart_souhaite>
|
||||
set -euo pipefail
|
||||
|
||||
CREATED="${1:?usage: $0 <mxid_cree> <localpart_souhaite>}"
|
||||
WANT="${2:?usage: $0 <mxid_cree> <localpart_souhaite>}"
|
||||
CTN="${CTN:-synapse}"
|
||||
DB="${DB:-/data/homeserver.db}"
|
||||
URL="${URL:-http://192.168.1.24:8008}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-folzo.cred}"
|
||||
|
||||
PW="$(python3 -c "print(open('$CRED').read().split('password=')[1].strip())")"
|
||||
|
||||
echo "== 1. admin=1 sur le compte créé =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
con = sqlite3.connect('$DB'); cur = con.cursor()
|
||||
cur.execute('update users set admin=1 where name=?', ('$CREATED',)); con.commit()
|
||||
print(' lignes concernées:', cur.rowcount)
|
||||
"
|
||||
|
||||
echo "== 2. login mot de passe -> token admin (jamais affiché) =="
|
||||
LOGIN="$(python3 - "$CREATED" "$PW" <<'PY'
|
||||
import json, subprocess, sys
|
||||
body = json.dumps({"type": "m.login.password", "identifier": {"type": "m.id.user", "user": sys.argv[1]},
|
||||
"password": sys.argv[2]})
|
||||
out = subprocess.run(["curl", "-s", "-m", "15", "-X", "POST",
|
||||
"http://192.168.1.24:8008/_matrix/client/v3/login",
|
||||
"-H", "Content-Type: application/json", "-d", body],
|
||||
capture_output=True, text=True).stdout
|
||||
d = json.loads(out)
|
||||
print(d.get("access_token", ""))
|
||||
PY
|
||||
)"
|
||||
if [[ -z "$LOGIN" ]]; then echo " KO login (mot de passe du compte créé différent de celui du fichier ?)"; exit 1; fi
|
||||
echo " token obtenu (longueur ${#LOGIN})"
|
||||
|
||||
echo "== 3. preuve que l'API admin répond =="
|
||||
curl -s -m 15 "$URL/_synapse/admin/v2/users?limit=5" -H "Authorization: Bearer $LOGIN" \
|
||||
| python3 -c 'import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
print(" users listés:", d.get("total"), "|", [u["name"] for u in d.get("users", [])])'
|
||||
|
||||
echo "== 4. créer le compte propre demandé, admin=true =="
|
||||
NEWBODY="$(python3 - "$WANT" "$PW" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"name": "@" + sys.argv[1] + ":192.168.1.24", "password": sys.argv[2],
|
||||
"admin": True, "deactivated": False}))
|
||||
PY
|
||||
)"
|
||||
curl -s -m 20 -X POST "$URL/_synapse/admin/v2/users/@${WANT}:192.168.1.24" \
|
||||
-H "Authorization: Bearer $LOGIN" -H 'Content-Type: application/json' -d "$NEWBODY" \
|
||||
| python3 -c 'import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
print(" créé:", d.get("name") or d.get("error") or d)'
|
||||
unset NEWBODY
|
||||
|
||||
echo "== 5. désactiver le compte aléatoire =="
|
||||
curl -s -m 20 -X PUT "$URL/_synapse/admin/v2/users/$CREATED" \
|
||||
-H "Authorization: Bearer $LOGIN" -H 'Content-Type: application/json' \
|
||||
-d '{"deactivated": true}' | python3 -c 'import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
print(" désactivé:", d.get("name") or d.get("error") or d)' 2>/dev/null
|
||||
|
||||
echo "== vérification (lecture seule) =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
c = sqlite3.connect('file:$DB?mode=ro', uri=True).cursor()
|
||||
print(' comptes total/admin/deactivated:', c.execute('select count(*), sum(admin), sum(deactivated) from users').fetchone())
|
||||
for r in c.execute('select name, admin, locked, deactivated from users order by admin desc'):
|
||||
print(' ', r)
|
||||
"
|
||||
unset LOGIN PW
|
||||
echo "identifiants : $CRED (600) — à changer à la première connexion, puis 2FA (C-MX3)"
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# 23-synapse-admin-fix.sh — répare ce que la voie « register » a abîmé.
|
||||
#
|
||||
# Erreur mesurée : dans `21-…`, les arguments du bloc python étaient décalés -> le compte a été
|
||||
# créé avec **password = <localpart>**, et Synapse a ignoré le localpart demandé (mxid aléatoire).
|
||||
# Le compte créé porte donc admin=1 (mis à jour) et un mot de passe faible.
|
||||
#
|
||||
# Ce script, avec le mot de passe faible passé en argument (jamais affiché) :
|
||||
# 1. login -> token admin ; 2. remplace le mot de passe par celui du fichier de credential ;
|
||||
# 3. crée le compte propre @<localpart>:<domain> admin=true ; 4. désactive le compte aléatoire ;
|
||||
# 5. vérifie en lecture seule.
|
||||
# Usage : bash 23-synapse-admin-fix.sh <mxid_aleatoire> <localpart_souhaite> <mot_de_passe_faible>
|
||||
set -euo pipefail
|
||||
|
||||
CREATED="${1:?mxid aléatoire créé}"
|
||||
WANT="${2:?localpart souhaité}"
|
||||
WEAK="${3:?mot de passe actuellement en base sur ce compte}"
|
||||
CTN="${CTN:-synapse}"
|
||||
DB="${DB:-/data/homeserver.db}"
|
||||
URL="${URL:-http://192.168.1.24:8008}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-$WANT.cred}"
|
||||
DOMAIN="${DOMAIN:-192.168.1.24}"
|
||||
|
||||
PW="$(python3 -c "print(open('$CRED').read().split('password=')[1].strip())")"
|
||||
|
||||
TOKEN="$(python3 - "$CREATED" "$WEAK" <<'PY'
|
||||
import json, subprocess, sys
|
||||
body = json.dumps({"type": "m.login.password",
|
||||
"identifier": {"type": "m.id.user", "user": sys.argv[1]},
|
||||
"password": sys.argv[2]})
|
||||
out = subprocess.run(["curl", "-s", "-m", "15", "-X", "POST",
|
||||
"http://192.168.1.24:8008/_matrix/client/v3/login",
|
||||
"-H", "Content-Type: application/json", "-d", body],
|
||||
capture_output=True, text=True).stdout
|
||||
print(json.loads(out).get("access_token", ""))
|
||||
PY
|
||||
)"
|
||||
if [[ -z "$TOKEN" ]]; then echo "KO login admin — abandon (rien n'est modifié)"; exit 1; fi
|
||||
echo "login admin OK (token ${#TOKEN} car., jamais affiché)"
|
||||
|
||||
api() { # methode url corps
|
||||
curl -s -m 20 -X "$1" "$2" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d "$3"
|
||||
}
|
||||
|
||||
echo "== 1. mot de passe fort sur le compte admin existant =="
|
||||
api POST "$URL/_synapse/admin/v2/users/$CREATED" \
|
||||
"$(python3 - "$PW" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"password": sys.argv[1]}))
|
||||
PY
|
||||
)" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(" ", d.get("name") or d.get("error") or d)' 2>/dev/null
|
||||
|
||||
echo "== 2. créer @${WANT}:${DOMAIN} (admin=true) =="
|
||||
api POST "$URL/_synapse/admin/v2/users/@${WANT}:${DOMAIN}" \
|
||||
"$(python3 - "$PW" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"password": sys.argv[1], "admin": True, "deactivated": False}))
|
||||
PY
|
||||
)" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(" ", d.get("name") or d.get("error") or d)' 2>/dev/null
|
||||
|
||||
echo "== 3. désactiver le compte aléatoire =="
|
||||
api PUT "$URL/_synapse/admin/v2/users/$CREATED" '{"deactivated": true}' \
|
||||
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(" ", d.get("name") or d.get("error") or d)' 2>/dev/null
|
||||
|
||||
echo "== vérification (lecture seule) =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
c = sqlite3.connect('file:$DB?mode=ro', uri=True).cursor()
|
||||
print(' total/admin/deactivated:', c.execute('select count(*), sum(admin), sum(deactivated) from users').fetchone())
|
||||
for r in c.execute('select name, admin, locked, deactivated from users order by admin desc'):
|
||||
print(' ', r)
|
||||
"
|
||||
echo "credential : $CRED (600) — user=@${WANT}:${DOMAIN} ; à changer à la première connexion, puis 2FA (C-MX3)"
|
||||
unset TOKEN PW
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
# 24-synapse-admin-put.sh — créer @<localpart> via **PUT** (l'API admin v2 crée par PUT, pas POST).
|
||||
# Séquence, avec réversibilité à chaque étape :
|
||||
# 1. sqlite : rendre le compte admin aléatoire **utilisable** (admin=1, deactivated=0)
|
||||
# 2. login -> token (jamais affiché)
|
||||
# 3. PUT /_synapse/admin/v2/users/@<localpart>:<domain> {password, admin:true}
|
||||
# 4. sqlite : retirer admin au compte aléatoire et le redésactiver
|
||||
# 5. vérification lecture seule
|
||||
# Usage : bash 24-synapse-admin-put.sh <mxid_admin_temporaire> <localpart_souhaite>
|
||||
set -euo pipefail
|
||||
|
||||
TMP_ADMIN="${1:?mxid admin temporaire}"
|
||||
WANT="${2:?localpart souhaité}"
|
||||
CTN="${CTN:-synapse}"
|
||||
DB="${DB:-/data/homeserver.db}"
|
||||
URL="${URL:-http://192.168.1.24:8008}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-$WANT.cred}"
|
||||
DOMAIN="${DOMAIN:-192.168.1.24}"
|
||||
PW="$(python3 -c "print(open('$CRED').read().split('password=')[1].strip())")"
|
||||
# Le compte admin temporaire a été créé avec un mot de passe faible (erreur mesurée 21-…) :
|
||||
# LOGIN_PW permet de se connecter avec celui qui est réellement en base, sans jamais l'afficher.
|
||||
LOGIN_PW="${LOGIN_PW:-$PW}"
|
||||
|
||||
echo "== 1. admin temporaire rendu utilisable =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
con = sqlite3.connect('$DB'); cur = con.cursor()
|
||||
cur.execute('update users set admin=1, deactivated=0 where name=?', ('$TMP_ADMIN',)); con.commit()
|
||||
print(' lignes:', cur.rowcount)
|
||||
"
|
||||
|
||||
echo "== 2. login (mot de passe lu depuis $CRED) =="
|
||||
TOKEN="$(python3 - "$TMP_ADMIN" "$LOGIN_PW" <<'PY'
|
||||
import json, subprocess, sys
|
||||
body = json.dumps({"type": "m.login.password",
|
||||
"identifier": {"type": "m.id.user", "user": sys.argv[1]},
|
||||
"password": sys.argv[2]})
|
||||
out = subprocess.run(["curl", "-s", "-m", "15", "-X", "POST",
|
||||
"http://192.168.1.24:8008/_matrix/client/v3/login",
|
||||
"-H", "Content-Type: application/json", "-d", body],
|
||||
capture_output=True, text=True).stdout
|
||||
print(json.loads(out).get("access_token", ""))
|
||||
PY
|
||||
)"
|
||||
if [[ -z "$TOKEN" ]]; then echo " KO login — le mot de passe en base n'est pas celui du fichier"; exit 1; fi
|
||||
echo " OK (token ${#TOKEN} car., jamais affiché)"
|
||||
|
||||
echo "== 3. PUT @${WANT}:${DOMAIN} (admin=true) =="
|
||||
BODY="$(python3 - "$WANT" "$PW" "$DOMAIN" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"password": sys.argv[2], "admin": True, "deactivated": False}))
|
||||
PY
|
||||
)"
|
||||
curl -s -m 20 -X PUT "$URL/_synapse/admin/v2/users/@${WANT}:${DOMAIN}" \
|
||||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d "$BODY" \
|
||||
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(" ", d.get("name") or d.get("error") or d)' 2>/dev/null
|
||||
unset BODY
|
||||
|
||||
echo "== 4. admin temporaire retiré + redésactivé =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
con = sqlite3.connect('$DB'); cur = con.cursor()
|
||||
cur.execute('update users set admin=0, deactivated=1 where name=?', ('$TMP_ADMIN',)); con.commit()
|
||||
print(' lignes:', cur.rowcount)
|
||||
"
|
||||
|
||||
echo "== 5. vérification (lecture seule) =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
c = sqlite3.connect('file:$DB?mode=ro', uri=True).cursor()
|
||||
print(' total/admin/deactivated:', c.execute('select count(*), sum(admin), sum(deactivated) from users').fetchone())
|
||||
for r in c.execute('select name, admin, locked, deactivated from users order by admin desc'):
|
||||
print(' ', r)
|
||||
"
|
||||
echo "credential : $CRED (600) — user=@${WANT}:${DOMAIN} ; à changer à la première connexion, puis 2FA (C-MX3)"
|
||||
unset TOKEN PW
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# 25-synapse-state.sh — etat Synapse en LECTURE SEULE (0 ecriture, 0 creation).
|
||||
set -euo pipefail
|
||||
CTN="${CTN:-synapse}"
|
||||
DB="${DB:-/data/homeserver.db}"
|
||||
|
||||
echo "== users (lecture seule) =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import sqlite3
|
||||
c = sqlite3.connect('file:$DB?mode=ro', uri=True).cursor()
|
||||
print(' total/admin/deactivated/locked:', c.execute('select count(*), sum(admin), sum(deactivated), sum(locked) from users').fetchone())
|
||||
for r in c.execute('select name, admin, locked, deactivated, substr(password_hash,1,7) from users order by admin desc'):
|
||||
print(' ', r)
|
||||
"
|
||||
|
||||
echo "== bcrypt disponible dans l'image =="
|
||||
docker exec "$CTN" python3 -c "import bcrypt; print(' bcrypt', bcrypt.__version__ if hasattr(bcrypt,'__version__') else 'OK'); print(' hash', bcrypt.hashpw(b'x', bcrypt.gensalt(12)).decode()[:7])"
|
||||
|
||||
echo "== credentials locaux =="
|
||||
for f in /home/folzo/synapse-admin-*.cred /home/folzo/beszel-admin.cred; do
|
||||
if [ -f "$f" ]; then echo " $(stat -c '%a %U %n' "$f") ($(wc -c < "$f") o)"; else echo " absent: $f"; fi
|
||||
done
|
||||
|
||||
echo "== config inscription =="
|
||||
docker exec "$CTN" python3 -c "
|
||||
import re
|
||||
t = open('/data/homeserver.yaml').read()
|
||||
for k in ('enable_registration','registration_without_token','registration_without_verification','enable_registration_tokens'):
|
||||
m = re.search(r'^%s:\s*(.*)$' % k, t, re.M)
|
||||
print(' ', k, '=', m.group(1) if m else 'ABSENT')
|
||||
print(' shared_secret:', 'FOUND' if 'registration_shared_secret' in t else 'ABSENT')
|
||||
"
|
||||
echo "== synapse version =="
|
||||
curl -s -m 10 http://192.168.1.24:8008/_matrix/client/versions >/dev/null && echo " endpoint versions: OK"
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env bash
|
||||
# 26-synapse-admin-repair.sh — repare le 1er compte admin Synapse, proprement.
|
||||
#
|
||||
# Cause mesuree (12/10) : le compte cree par `21-…` (`@mpijdqsyryjrrfmjdoraqcxf`) a `password_hash`
|
||||
# = NULL en base -> admin=1 mais **inutilisable** (login = « Invalid username or password »).
|
||||
#
|
||||
# Voie : hasher le mot de passe du credential en bcrypt 12 (module `bcrypt` present dans l'image
|
||||
# synapse, verifie) et l'ecrire sur le compte admin temporaire -> login -> token ->
|
||||
# **PUT** /_synapse/admin/v2/users/@<localpart> (l'API admin v2 cree par PUT, pas POST) ->
|
||||
# retirer admin au compte temporaire, le desactiver, remettre son hash a NULL.
|
||||
#
|
||||
# Bug elimine au passage : `python3 - args <<PY` consomme le heredoc comme programme -> **plus
|
||||
# rien sur stdin** -> mot de passe lu = 0 caractere. Les programmes sont donc ecrits dans des
|
||||
# fichiers (heredoc vers fichier) et l'appel se fait par chemin, ce qui laisse stdin au secret.
|
||||
#
|
||||
# Gardes : mot de passe et token **jamais en argv** (stdin + `curl -K` fichier 600), tmpdir 700
|
||||
# detruit a la sortie, `docker restart` pour vider les caches apres chaque ecriture sqlite directe,
|
||||
# 1 seule tentative de login, verification finale en lecture seule.
|
||||
# Snapshot prealable : `docker exec synapse python3 -c "sqlite3 backup"` -> /data/homeserver.db.bak-*
|
||||
#
|
||||
# Usage : bash 26-synapse-admin-repair.sh <mxid_admin_temporaire> <localpart> [--no-restart]
|
||||
# bash 26-synapse-admin-repair.sh --verify <localpart> (1 login, prouve le compte)
|
||||
# bash 26-synapse-admin-repair.sh --admin-check <localpart> (GET /_synapse/admin/v2/users)
|
||||
set -euo pipefail
|
||||
|
||||
CTN="${CTN:-synapse}"
|
||||
DB="${DB:-/data/homeserver.db}"
|
||||
URL="${URL:-http://192.168.1.24:8008}"
|
||||
DOMAIN="${DOMAIN:-192.168.1.24}"
|
||||
|
||||
TMPD="$(mktemp -d)"; chmod 700 "$TMPD"; trap 'rm -rf "$TMPD"' EXIT
|
||||
|
||||
cat > "$TMPD/loginbody.py" <<'PY'
|
||||
import json, sys
|
||||
pw = sys.stdin.buffer.read().strip().decode()
|
||||
print(json.dumps({"type": "m.login.password",
|
||||
"identifier": {"type": "m.id.user", "user": sys.argv[1]},
|
||||
"password": pw}))
|
||||
PY
|
||||
|
||||
cat > "$TMPD/putbody.py" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"password": sys.stdin.buffer.read().strip().decode(),
|
||||
"admin": True, "deactivated": False}))
|
||||
PY
|
||||
|
||||
cat > "$TMPD/token.py" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
tok = d.get("access_token", "")
|
||||
open(sys.argv[2], "w").write(tok)
|
||||
print(" " + ("login OK (token %d car., jamais affiche)" % len(tok) if tok
|
||||
else "KO login: " + str(d.get("error") or d)))
|
||||
sys.exit(0 if tok else 1)
|
||||
PY
|
||||
|
||||
cat > "$TMPD/show.py" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
print(" ", d.get("name") or d.get("error") or d)
|
||||
PY
|
||||
|
||||
cat > "$TMPD/admincheck.py" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
us = d.get("users", [])
|
||||
print(" API admin OK — %s utilisateurs, %s admin, %s desactives" % (
|
||||
d.get("total", "?"), sum(1 for u in us if u.get("admin")),
|
||||
sum(1 for u in us if u.get("deactivated"))))
|
||||
for u in us:
|
||||
print(" ", u.get("name"), "| admin", bool(u.get("admin")),
|
||||
"| deactivated", bool(u.get("deactivated")), "| locked", bool(u.get("locked")))
|
||||
PY
|
||||
|
||||
if [[ "${1:-}" == "--verify" ]]; then
|
||||
WANT="${2:?localpart}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-$WANT.cred}"
|
||||
sed -n 's/^password=//p' "$CRED" | python3 "$TMPD/loginbody.py" "@${WANT}:${DOMAIN}" \
|
||||
| curl -s -m 15 -X POST "$URL/_matrix/client/v3/login" -H 'Content-Type: application/json' -d @- > "$TMPD/login.json"
|
||||
python3 "$TMPD/token.py" "$TMPD/login.json" "$TMPD/token"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "--admin-check" ]]; then
|
||||
WANT="${2:?localpart}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-$WANT.cred}"
|
||||
sed -n 's/^password=//p' "$CRED" | python3 "$TMPD/loginbody.py" "@${WANT}:${DOMAIN}" \
|
||||
| curl -s -m 15 -X POST "$URL/_matrix/client/v3/login" -H 'Content-Type: application/json' -d @- > "$TMPD/login.json"
|
||||
python3 "$TMPD/token.py" "$TMPD/login.json" "$TMPD/token"
|
||||
{ printf 'header = "Authorization: Bearer %s"\n' "$(cat "$TMPD/token")"; } > "$TMPD/curlrc"
|
||||
chmod 600 "$TMPD/curlrc"
|
||||
curl -s -m 20 -K "$TMPD/curlrc" "$URL/_synapse/admin/v2/users?limit=50" > "$TMPD/users.json"
|
||||
python3 "$TMPD/admincheck.py" "$TMPD/users.json"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TMP_ADMIN="${1:?mxid admin temporaire}"
|
||||
WANT="${2:?localpart souhaite}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-$WANT.cred}"
|
||||
RESTART=1
|
||||
[[ "${3:-}" == "--no-restart" ]] && RESTART=0
|
||||
[[ -f "$CRED" ]] || { echo "KO: credential absent ($CRED)"; exit 1; }
|
||||
TARGET="@${WANT}:${DOMAIN}"
|
||||
|
||||
cat > "$TMPD/hashpw.py" <<'PY'
|
||||
import sqlite3, sys, bcrypt
|
||||
pw = sys.stdin.buffer.read().strip()
|
||||
con = sqlite3.connect(sys.argv[2]); cur = con.cursor()
|
||||
if pw:
|
||||
h = bcrypt.hashpw(pw, bcrypt.gensalt(12)).decode()
|
||||
cur.execute('update users set password_hash=?, admin=1, deactivated=0, locked=0 where name=?',
|
||||
(h, sys.argv[1]))
|
||||
else:
|
||||
cur.execute('update users set password_hash=NULL, admin=1, deactivated=0, locked=0 where name=?',
|
||||
(sys.argv[1],))
|
||||
con.commit()
|
||||
print(' lignes:', cur.rowcount, '| pw lu:', len(pw), 'car. | hash', ('bcrypt12' if pw else 'NULL'))
|
||||
PY
|
||||
|
||||
cat > "$TMPD/strip.py" <<'PY'
|
||||
import sqlite3, sys
|
||||
con = sqlite3.connect(sys.argv[2]); cur = con.cursor()
|
||||
cur.execute('update users set admin=0, deactivated=1, password_hash=NULL where name=?', (sys.argv[1],))
|
||||
con.commit(); print(' lignes:', cur.rowcount)
|
||||
PY
|
||||
|
||||
cat > "$TMPD/report.py" <<'PY'
|
||||
import sqlite3, sys
|
||||
c = sqlite3.connect('file:%s?mode=ro' % sys.argv[1], uri=True).cursor()
|
||||
print(' total/admin/deactivated:', c.execute('select count(*), sum(admin), sum(deactivated) from users').fetchone())
|
||||
for r in c.execute('select name, admin, locked, deactivated, case when password_hash is null then 0 else 1 end from users order by admin desc'):
|
||||
print(' ', r)
|
||||
PY
|
||||
|
||||
up() {
|
||||
for i in $(seq 1 40); do
|
||||
if curl -s -m 3 -o /dev/null "$URL/_matrix/client/versions"; then echo " synapse repond apres ${i}s"; return 0; fi
|
||||
sleep 1
|
||||
done
|
||||
echo " KO: synapse ne repond pas"; return 1
|
||||
}
|
||||
|
||||
echo "== 1. mot de passe du credential hashe (bcrypt 12) sur le compte admin temporaire =="
|
||||
docker cp "$TMPD/hashpw.py" "$CTN:/tmp/g9-hashpw.py" >/dev/null
|
||||
sed -n 's/^password=//p' "$CRED" | docker exec -i "$CTN" python3 /tmp/g9-hashpw.py "$TMP_ADMIN" "$DB"
|
||||
docker exec "$CTN" rm -f /tmp/g9-hashpw.py
|
||||
|
||||
if [[ "$RESTART" == "1" ]]; then
|
||||
echo "== 2. docker restart $CTN (caches sqlite videes apres ecriture directe) =="
|
||||
docker restart "$CTN" >/dev/null; up
|
||||
fi
|
||||
|
||||
echo "== 3. login du compte admin temporaire =="
|
||||
sed -n 's/^password=//p' "$CRED" | python3 "$TMPD/loginbody.py" "$TMP_ADMIN" \
|
||||
| curl -s -m 15 -X POST "$URL/_matrix/client/v3/login" -H 'Content-Type: application/json' -d @- > "$TMPD/login.json"
|
||||
python3 "$TMPD/token.py" "$TMPD/login.json" "$TMPD/token"
|
||||
|
||||
echo "== 4. PUT $TARGET (admin=true) =="
|
||||
{ printf 'header = "Authorization: Bearer %s"\n' "$(cat "$TMPD/token")"
|
||||
printf 'header = "Content-Type: application/json"\n'; } > "$TMPD/curlrc"
|
||||
chmod 600 "$TMPD/curlrc"
|
||||
sed -n 's/^password=//p' "$CRED" | python3 "$TMPD/putbody.py" \
|
||||
| curl -s -m 20 -X PUT "$URL/_synapse/admin/v2/users/$TARGET" -K "$TMPD/curlrc" -d @- > "$TMPD/put.json"
|
||||
python3 "$TMPD/show.py" "$TMPD/put.json"
|
||||
|
||||
echo "== 5. compte temporaire : admin retire, desactive, hash a NULL =="
|
||||
docker cp "$TMPD/strip.py" "$CTN:/tmp/g9-strip.py" >/dev/null
|
||||
docker exec "$CTN" python3 /tmp/g9-strip.py "$TMP_ADMIN" "$DB"
|
||||
docker exec "$CTN" rm -f /tmp/g9-strip.py
|
||||
if [[ "$RESTART" == "1" ]]; then
|
||||
echo "== 6. docker restart $CTN =="
|
||||
docker restart "$CTN" >/dev/null; up
|
||||
fi
|
||||
|
||||
echo "== 7. verification (lecture seule) (nom, admin, locked, deactivated, hash_present) =="
|
||||
docker cp "$TMPD/report.py" "$CTN:/tmp/g9-report.py" >/dev/null
|
||||
docker exec "$CTN" python3 /tmp/g9-report.py "$DB"
|
||||
docker exec "$CTN" rm -f /tmp/g9-report.py
|
||||
echo "credential : $CRED (600) — user=$TARGET ; a changer a la 1re connexion, puis 2FA (C-MX3)"
|
||||
echo "verifier le login : bash $0 --verify $WANT"
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# 27-synapse-family-users.sh — cree les 6 comptes famille Matrix via l'API admin (admin=false).
|
||||
#
|
||||
# Pre-requis : un compte admin utilisable = `26-synapse-admin-repair.sh` (verifie par --admin-check).
|
||||
# Cible C-MX1 : 2 adultes + 4 enfants, **admin=false**, mots de passe generes sur la machine et
|
||||
# ecrits dans un fichier 600 (jamais affiches, jamais commitables), a recopier dans le coffre.
|
||||
#
|
||||
# Garde-fous : mode `--dry` par defaut de lecture, **confirmation `CREER` exigee** (TTY ou
|
||||
# CONFIRM=CREER), 1 ecriture par compte, verification lecture seule apres, rollback = desactivation.
|
||||
# Usage : bash 27-synapse-family-users.sh --dry
|
||||
# CONFIRM=CREER bash 27-synapse-family-users.sh (ou saisie interactive)
|
||||
# bash 27-synapse-family-users.sh --revoke (desactive les 6)
|
||||
set -euo pipefail
|
||||
|
||||
URL="${URL:-http://192.168.1.24:8008}"
|
||||
DOMAIN="${DOMAIN:-192.168.1.24}"
|
||||
ADMIN="${ADMIN:-folzo}"
|
||||
CRED="${CRED:-/home/folzo/synapse-admin-$ADMIN.cred}"
|
||||
OUT="${OUT:-/home/folzo/synapse-famille.cred}"
|
||||
ADULTS="${ADULTS:-effi audrey}"
|
||||
KIDS="${KIDS:-carla ewan lola noah}"
|
||||
|
||||
TMPD="$(mktemp -d)"; chmod 700 "$TMPD"; trap 'rm -rf "$TMPD"' EXIT
|
||||
|
||||
cat > "$TMPD/loginbody.py" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({"type": "m.login.password",
|
||||
"identifier": {"type": "m.id.user", "user": sys.argv[1]},
|
||||
"password": sys.stdin.buffer.read().strip().decode()}))
|
||||
PY
|
||||
cat > "$TMPD/token.py" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1])); tok = d.get("access_token", "")
|
||||
open(sys.argv[2], "w").write(tok)
|
||||
print(" " + ("login admin OK (token %d car.)" % len(tok) if tok else "KO login: " + str(d.get("error") or d)))
|
||||
sys.exit(0 if tok else 1)
|
||||
PY
|
||||
cat > "$TMPD/putbody.py" <<'PY'
|
||||
import json, sys
|
||||
pw = sys.stdin.buffer.read().strip().decode()
|
||||
print(json.dumps({"password": pw, "admin": False, "deactivated": False,
|
||||
"display_name": sys.argv[1]}))
|
||||
PY
|
||||
cat > "$TMPD/list.py" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
us = d.get("users", [])
|
||||
print(" total API:", d.get("total"))
|
||||
for u in us:
|
||||
print(" ", u.get("name"), "| admin", bool(u.get("admin")),
|
||||
"| deactivated", bool(u.get("deactivated")), "| locked", bool(u.get("locked")))
|
||||
PY
|
||||
|
||||
MODE="create"; [[ "${1:-}" == "--dry" ]] && MODE="dry"; [[ "${1:-}" == "--revoke" ]] && MODE="revoke"
|
||||
[[ -f "$CRED" ]] || { echo "KO: credential admin absent ($CRED) — voir 26-synapse-admin-repair.sh"; exit 1; }
|
||||
|
||||
read -r -a ALL <<< "$ADULTS $KIDS"
|
||||
echo "comptes vus : ${#ALL[@]} (${ALL[*]}) — 2 adultes + 4 enfants = 6 attendus"
|
||||
|
||||
if [[ "$MODE" == "dry" ]]; then
|
||||
echo "== PLAN (0 ecriture) =="
|
||||
for lp in "${ALL[@]}"; do
|
||||
echo " PUT $URL/_synapse/admin/v2/users/@${lp}:${DOMAIN} {password: genere, admin: false}"
|
||||
done
|
||||
echo " mots de passe -> $OUT (600, folzo) ; rollback = --revoke (desactivation, jamais de purge)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "revoke" ]]; then
|
||||
sed -n 's/^password=//p' "$CRED" | head -1 | python3 "$TMPD/loginbody.py" "@${ADMIN}:${DOMAIN}" \
|
||||
| curl -s -m 15 -X POST "$URL/_matrix/client/v3/login" -H 'Content-Type: application/json' -d @- > "$TMPD/login.json"
|
||||
python3 "$TMPD/token.py" "$TMPD/login.json" "$TMPD/token"
|
||||
{ printf 'header = "Authorization: Bearer %s"\n' "$(cat "$TMPD/token")"
|
||||
printf 'header = "Content-Type: application/json"\n'; } > "$TMPD/curlrc"
|
||||
chmod 600 "$TMPD/curlrc"
|
||||
for lp in "${ALL[@]}"; do
|
||||
curl -s -m 20 -X PUT "$URL/_synapse/admin/v2/users/@${lp}:${DOMAIN}" -K "$TMPD/curlrc" \
|
||||
-d '{"deactivated": true}' | python3 -c 'import json,sys;d=json.load(sys.stdin);print(" ", d.get("name") or d.get("error") or d)'
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${CONFIRM:-}" != "CREER" ]]; then
|
||||
echo "confirmation exigee : tape CREER pour creer ${#ALL[@]} comptes (admin=false), mots de passe dans $OUT"
|
||||
read -r ANSWER
|
||||
[[ "$ANSWER" == "CREER" ]] || { echo "abandon (aucune ecriture)"; exit 1; }
|
||||
fi
|
||||
[[ -e "$OUT" ]] && { echo "KO: $OUT existe deja (ne pas ecraser des mots de passe existants)"; exit 1; }
|
||||
|
||||
sed -n 's/^password=//p' "$CRED" | head -1 | python3 "$TMPD/loginbody.py" "@${ADMIN}:${DOMAIN}" \
|
||||
| curl -s -m 15 -X POST "$URL/_matrix/client/v3/login" -H 'Content-Type: application/json' -d @- > "$TMPD/login.json"
|
||||
python3 "$TMPD/token.py" "$TMPD/login.json" "$TMPD/token"
|
||||
{ printf 'header = "Authorization: Bearer %s"\n' "$(cat "$TMPD/token")"
|
||||
printf 'header = "Content-Type: application/json"\n'; } > "$TMPD/curlrc"
|
||||
chmod 600 "$TMPD/curlrc"
|
||||
|
||||
umask 077
|
||||
: > "$OUT"
|
||||
echo "== creation (${#ALL[@]} comptes, admin=false) =="
|
||||
for lp in "${ALL[@]}"; do
|
||||
PW="$(openssl rand -base64 18 | tr -d '/+=' | cut -c1-20)"
|
||||
printf 'user=@%s:%s password=%s\n' "$lp" "$DOMAIN" "$PW" >> "$OUT"
|
||||
printf '%s' "$PW" | python3 "$TMPD/putbody.py" "$lp" \
|
||||
| curl -s -m 20 -X PUT "$URL/_synapse/admin/v2/users/@${lp}:${DOMAIN}" -K "$TMPD/curlrc" -d @- \
|
||||
| python3 -c 'import json,sys;d=json.load(sys.stdin);print(" ", d.get("name") or d.get("error") or d)'
|
||||
unset PW
|
||||
done
|
||||
chmod 600 "$OUT"
|
||||
echo "mots de passe : $OUT ($(wc -l < "$OUT") lignes, 600) — a recopier dans le coffre puis detruire"
|
||||
|
||||
echo "== verification (lecture seule) =="
|
||||
curl -s -m 20 -K "$TMPD/curlrc" "$URL/_synapse/admin/v2/users?limit=50" > "$TMPD/users.json"
|
||||
python3 "$TMPD/list.py" "$TMPD/users.json"
|
||||
Reference in New Issue
Block a user