From 292d513e103b8570e254b3cffefdad709d20c85e Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 27 Jan 2026 04:58:13 -0300 Subject: [PATCH] comms: ensure synapse admin token --- services/comms/synapse-admin-ensure-job.yaml | 141 ++++++++++++------- 1 file changed, 89 insertions(+), 52 deletions(-) diff --git a/services/comms/synapse-admin-ensure-job.yaml b/services/comms/synapse-admin-ensure-job.yaml index be9e0fd..6ddea83 100644 --- a/services/comms/synapse-admin-ensure-job.yaml +++ b/services/comms/synapse-admin-ensure-job.yaml @@ -2,7 +2,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: synapse-admin-ensure-1 + name: synapse-admin-ensure-2 namespace: comms spec: backoffLimit: 1 @@ -40,24 +40,26 @@ spec: - -c - | set -euo pipefail + pip install --no-cache-dir psycopg2-binary bcrypt >/dev/null python - <<'PY' - import base64 - import hashlib - import hmac import json import os import secrets import string + import time import urllib.error import urllib.request + import bcrypt + import psycopg2 + VAULT_ADDR = os.environ.get("VAULT_ADDR", "http://vault.vault.svc.cluster.local:8200").rstrip("/") VAULT_ROLE = os.environ.get("VAULT_ROLE", "comms-secrets") - SYNAPSE_ADMIN_URL = os.environ.get( - "SYNAPSE_ADMIN_URL", - "http://othrys-synapse-matrix-synapse.comms.svc.cluster.local:8008", - ).rstrip("/") SA_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token" + PGHOST = "postgres-service.postgres.svc.cluster.local" + PGPORT = 5432 + PGDATABASE = "synapse" + PGUSER = "synapse" def log(msg: str) -> None: print(msg, flush=True) @@ -110,16 +112,6 @@ spec: alphabet = string.ascii_letters + string.digits return "".join(secrets.choice(alphabet) for _ in range(length)) - def ensure_registration_secret(token: str) -> str: - data = vault_get(token, "comms/synapse-registration") - secret = (data.get("registration_shared_secret") or "").strip() - if not secret: - secret = secrets.token_urlsafe(32) - data["registration_shared_secret"] = secret - vault_put(token, "comms/synapse-registration", data) - log("registration secret created") - return secret - def ensure_admin_creds(token: str) -> dict: data = vault_get(token, "comms/synapse-admin") username = (data.get("username") or "").strip() or "synapse-admin" @@ -131,47 +123,92 @@ spec: vault_put(token, "comms/synapse-admin", data) return data - def register_admin(secret: str, username: str, password: str) -> str: - nonce_payload = request_json(f"{SYNAPSE_ADMIN_URL}/_synapse/admin/v1/register") - nonce = nonce_payload.get("nonce") - if not nonce: - raise RuntimeError("synapse register nonce missing") - admin_flag = "admin" - user_type = "" - mac_payload = "\x00".join([nonce, username, password, admin_flag, user_type]) - mac = hmac.new(secret.encode("utf-8"), mac_payload.encode("utf-8"), hashlib.sha1).hexdigest() - payload = { - "nonce": nonce, - "username": username, - "password": password, - "admin": True, - "mac": mac, + def ensure_user(cur, cols, user_id, password, admin): + now_ms = int(time.time() * 1000) + values = { + "name": user_id, + "password_hash": bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode(), + "creation_ts": now_ms, } - req = urllib.request.Request( - f"{SYNAPSE_ADMIN_URL}/_synapse/admin/v1/register", - data=json.dumps(payload).encode("utf-8"), - headers={"Content-Type": "application/json"}, - method="POST", + + def add_flag(name, flag): + if name not in cols: + return + if cols[name]["type"] in ("smallint", "integer"): + values[name] = int(flag) + else: + values[name] = bool(flag) + + add_flag("admin", admin) + add_flag("deactivated", False) + add_flag("shadow_banned", False) + add_flag("is_guest", False) + + columns = list(values.keys()) + placeholders = ", ".join(["%s"] * len(columns)) + updates = ", ".join([f"{col}=EXCLUDED.{col}" for col in columns if col != "name"]) + query = f"INSERT INTO users ({', '.join(columns)}) VALUES ({placeholders}) ON CONFLICT (name) DO UPDATE SET {updates};" + cur.execute(query, [values[c] for c in columns]) + + def get_cols(cur): + cur.execute( + """ + SELECT column_name, is_nullable, column_default, data_type + FROM information_schema.columns + WHERE table_schema = 'public' AND table_name = 'users' + """ + ) + cols = {} + for name, is_nullable, default, data_type in cur.fetchall(): + cols[name] = { + "nullable": is_nullable == "YES", + "default": default, + "type": data_type, + } + return cols + + def ensure_access_token(cur, user_id, token_value): + cur.execute("SELECT COALESCE(MAX(id), 0) + 1 FROM access_tokens") + token_id = cur.fetchone()[0] + cur.execute( + """ + INSERT INTO access_tokens (id, user_id, token, device_id, valid_until_ms) + VALUES (%s, %s, %s, %s, NULL) + ON CONFLICT (token) DO NOTHING + """, + (token_id, user_id, token_value, "ariadne-admin"), ) - try: - with urllib.request.urlopen(req, timeout=30) as resp: - payload = json.loads(resp.read().decode("utf-8")) - except urllib.error.HTTPError as exc: - body = exc.read().decode("utf-8") - raise RuntimeError(f"synapse admin register failed: {exc.code} {body}") from exc - access_token = payload.get("access_token") - if not access_token: - raise RuntimeError("synapse admin token missing") - return access_token vault_token = vault_login() - reg_secret = ensure_registration_secret(vault_token) admin_data = ensure_admin_creds(vault_token) if admin_data.get("access_token"): log("synapse admin token already present") raise SystemExit(0) - access_token = register_admin(reg_secret, admin_data["username"], admin_data["password"]) - admin_data["access_token"] = access_token + + synapse_db = vault_get(vault_token, "comms/synapse-db") + pg_password = synapse_db.get("POSTGRES_PASSWORD") + if not pg_password: + raise RuntimeError("synapse db password missing") + + user_id = f"@{admin_data['username']}:live.bstein.dev" + conn = psycopg2.connect( + host=PGHOST, + port=PGPORT, + dbname=PGDATABASE, + user=PGUSER, + password=pg_password, + ) + token_value = secrets.token_urlsafe(32) + try: + with conn: + with conn.cursor() as cur: + cols = get_cols(cur) + ensure_user(cur, cols, user_id, admin_data["password"], True) + ensure_access_token(cur, user_id, token_value) + finally: + conn.close() + + admin_data["access_token"] = token_value vault_put(vault_token, "comms/synapse-admin", admin_data) - log("synapse admin user ensured") + log("synapse admin token stored") PY