comms: ensure synapse admin token
This commit is contained in:
parent
11ba37a4b2
commit
292d513e10
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user