comms: ensure synapse admin token

This commit is contained in:
Brad Stein 2026-01-27 04:58:13 -03:00
parent 11ba37a4b2
commit 292d513e10

View File

@ -2,7 +2,7 @@
apiVersion: batch/v1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: synapse-admin-ensure-1 name: synapse-admin-ensure-2
namespace: comms namespace: comms
spec: spec:
backoffLimit: 1 backoffLimit: 1
@ -40,24 +40,26 @@ spec:
- -c - -c
- | - |
set -euo pipefail set -euo pipefail
pip install --no-cache-dir psycopg2-binary bcrypt >/dev/null
python - <<'PY' python - <<'PY'
import base64
import hashlib
import hmac
import json import json
import os import os
import secrets import secrets
import string import string
import time
import urllib.error import urllib.error
import urllib.request import urllib.request
import bcrypt
import psycopg2
VAULT_ADDR = os.environ.get("VAULT_ADDR", "http://vault.vault.svc.cluster.local:8200").rstrip("/") VAULT_ADDR = os.environ.get("VAULT_ADDR", "http://vault.vault.svc.cluster.local:8200").rstrip("/")
VAULT_ROLE = os.environ.get("VAULT_ROLE", "comms-secrets") 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" 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: def log(msg: str) -> None:
print(msg, flush=True) print(msg, flush=True)
@ -110,16 +112,6 @@ spec:
alphabet = string.ascii_letters + string.digits alphabet = string.ascii_letters + string.digits
return "".join(secrets.choice(alphabet) for _ in range(length)) 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: def ensure_admin_creds(token: str) -> dict:
data = vault_get(token, "comms/synapse-admin") data = vault_get(token, "comms/synapse-admin")
username = (data.get("username") or "").strip() or "synapse-admin" username = (data.get("username") or "").strip() or "synapse-admin"
@ -131,47 +123,92 @@ spec:
vault_put(token, "comms/synapse-admin", data) vault_put(token, "comms/synapse-admin", data)
return data return data
def register_admin(secret: str, username: str, password: str) -> str: def ensure_user(cur, cols, user_id, password, admin):
nonce_payload = request_json(f"{SYNAPSE_ADMIN_URL}/_synapse/admin/v1/register") now_ms = int(time.time() * 1000)
nonce = nonce_payload.get("nonce") values = {
if not nonce: "name": user_id,
raise RuntimeError("synapse register nonce missing") "password_hash": bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode(),
admin_flag = "admin" "creation_ts": now_ms,
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,
} }
req = urllib.request.Request(
f"{SYNAPSE_ADMIN_URL}/_synapse/admin/v1/register", def add_flag(name, flag):
data=json.dumps(payload).encode("utf-8"), if name not in cols:
headers={"Content-Type": "application/json"}, return
method="POST", 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() vault_token = vault_login()
reg_secret = ensure_registration_secret(vault_token)
admin_data = ensure_admin_creds(vault_token) admin_data = ensure_admin_creds(vault_token)
if admin_data.get("access_token"): if admin_data.get("access_token"):
log("synapse admin token already present") log("synapse admin token already present")
raise SystemExit(0) 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) vault_put(vault_token, "comms/synapse-admin", admin_data)
log("synapse admin user ensured") log("synapse admin token stored")
PY PY