comms: source admin token from seeder access tokens

This commit is contained in:
Brad Stein 2026-01-28 18:29:49 -03:00
parent b2c7ca8cf1
commit caa6b73336

View File

@ -1,12 +1,12 @@
# services/comms/oneoffs/synapse-admin-ensure-job.yaml # services/comms/oneoffs/synapse-admin-ensure-job.yaml
# One-off job for comms/synapse-admin-ensure-11. # One-off job for comms/synapse-admin-ensure-12.
# Purpose: synapse admin ensure 11 (see container args/env in this file). # Purpose: synapse admin ensure 12 (see container args/env in this file).
# Run by setting spec.suspend to false, reconcile, then set it back to true. # Run by setting spec.suspend to false, reconcile, then set it back to true.
# Safe to delete the finished Job/pod; it should not run continuously. # Safe to delete the finished Job/pod; it should not run continuously.
apiVersion: batch/v1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: synapse-admin-ensure-11 name: synapse-admin-ensure-12
namespace: comms namespace: comms
spec: spec:
suspend: false suspend: false
@ -49,14 +49,10 @@ spec:
python - <<'PY' python - <<'PY'
import json import json
import os import os
import secrets
import string
import time
import urllib.error import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import bcrypt
import psycopg2 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("/")
@ -112,51 +108,16 @@ spec:
headers={"X-Vault-Token": token, "Content-Type": "application/json"}, headers={"X-Vault-Token": token, "Content-Type": "application/json"},
method="POST", method="POST",
) )
with urllib.request.urlopen(req, timeout=30) as resp: with urllib.request.urlopen(req, timeout=30) as resp:
resp.read() resp.read()
def random_password(length: int = 32) -> str:
alphabet = string.ascii_letters + string.digits
return "".join(secrets.choice(alphabet) for _ in range(length))
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 "othrys-seeder"
password = (data.get("password") or "").strip()
if not password:
password = random_password()
data["username"] = username data["username"] = username
data["password"] = password
vault_put(token, "comms/synapse-admin", data) vault_put(token, "comms/synapse-admin", data)
return data return data
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,
}
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): def get_cols(cur):
cur.execute( cur.execute(
""" """
@ -174,33 +135,6 @@ spec:
} }
return cols 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"),
)
def ensure_device(cur, user_id, device_id):
cur.execute(
"SELECT 1 FROM devices WHERE user_id = %s AND device_id = %s",
(user_id, device_id),
)
if cur.fetchone():
return
cur.execute(
"""
INSERT INTO devices (user_id, device_id, display_name, last_seen, ip, user_agent, hidden)
VALUES (%s, %s, %s, %s, NULL, NULL, FALSE)
""",
(user_id, device_id, "ariadne-admin", int(time.time() * 1000)),
)
def admin_token_valid(token: str, user_id: str) -> bool: def admin_token_valid(token: str, user_id: str) -> bool:
if not token or not SYNAPSE_ADMIN_URL: if not token or not SYNAPSE_ADMIN_URL:
return False return False
@ -242,15 +176,24 @@ spec:
user=PGUSER, user=PGUSER,
password=pg_password, password=pg_password,
) )
token_value = f"syt_{secrets.token_urlsafe(32)}"
device_id = "ariadne-admin"
try: try:
with conn: with conn:
with conn.cursor() as cur: with conn.cursor() as cur:
cols = get_cols(cur) cols = get_cols(cur)
ensure_user(cur, cols, user_id, admin_data["password"], True) if "admin" not in cols:
ensure_device(cur, user_id, device_id) raise RuntimeError("users.admin column missing")
ensure_access_token(cur, user_id, token_value) cur.execute(
"""
SELECT token FROM access_tokens
WHERE user_id = %s AND valid_until_ms IS NULL
ORDER BY id DESC LIMIT 1
""",
(user_id,),
)
row = cur.fetchone()
if not row:
raise RuntimeError(f"no access token found for {user_id}")
token_value = row[0]
finally: finally:
conn.close() conn.close()