# services/comms/mas-local-users-ensure-job.yaml apiVersion: batch/v1 kind: Job metadata: name: mas-local-users-ensure-18 namespace: comms spec: backoffLimit: 1 ttlSecondsAfterFinished: 3600 template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/agent-pre-populate-only: "true" vault.hashicorp.com/role: "comms" vault.hashicorp.com/agent-inject-secret-turn-secret: "kv/data/atlas/comms/turn-shared-secret" vault.hashicorp.com/agent-inject-template-turn-secret: | {{- with secret "kv/data/atlas/comms/turn-shared-secret" -}}{{ .Data.data.TURN_STATIC_AUTH_SECRET }}{{- end -}} vault.hashicorp.com/agent-inject-secret-livekit-primary: "kv/data/atlas/comms/livekit-api" vault.hashicorp.com/agent-inject-template-livekit-primary: | {{- with secret "kv/data/atlas/comms/livekit-api" -}}{{ .Data.data.primary }}{{- end -}} vault.hashicorp.com/agent-inject-secret-bot-pass: "kv/data/atlas/comms/atlasbot-credentials-runtime" vault.hashicorp.com/agent-inject-template-bot-pass: | {{- with secret "kv/data/atlas/comms/atlasbot-credentials-runtime" -}}{{ index .Data.data "bot-password" }}{{- end -}} vault.hashicorp.com/agent-inject-secret-seeder-pass: "kv/data/atlas/comms/atlasbot-credentials-runtime" vault.hashicorp.com/agent-inject-template-seeder-pass: | {{- with secret "kv/data/atlas/comms/atlasbot-credentials-runtime" -}}{{ index .Data.data "seeder-password" }}{{- end -}} vault.hashicorp.com/agent-inject-secret-chat-matrix: "kv/data/atlas/shared/chat-ai-keys-runtime" vault.hashicorp.com/agent-inject-template-chat-matrix: | {{- with secret "kv/data/atlas/shared/chat-ai-keys-runtime" -}}{{ .Data.data.matrix }}{{- end -}} vault.hashicorp.com/agent-inject-secret-chat-homepage: "kv/data/atlas/shared/chat-ai-keys-runtime" vault.hashicorp.com/agent-inject-template-chat-homepage: | {{- with secret "kv/data/atlas/shared/chat-ai-keys-runtime" -}}{{ .Data.data.homepage }}{{- end -}} vault.hashicorp.com/agent-inject-secret-mas-admin-secret: "kv/data/atlas/comms/mas-admin-client-runtime" vault.hashicorp.com/agent-inject-template-mas-admin-secret: | {{- with secret "kv/data/atlas/comms/mas-admin-client-runtime" -}}{{ .Data.data.client_secret }}{{- end -}} vault.hashicorp.com/agent-inject-secret-synapse-db-pass: "kv/data/atlas/comms/synapse-db" vault.hashicorp.com/agent-inject-template-synapse-db-pass: | {{- with secret "kv/data/atlas/comms/synapse-db" -}}{{ .Data.data.POSTGRES_PASSWORD }}{{- end -}} vault.hashicorp.com/agent-inject-secret-mas-db-pass: "kv/data/atlas/comms/mas-db" vault.hashicorp.com/agent-inject-template-mas-db-pass: | {{- with secret "kv/data/atlas/comms/mas-db" -}}{{ .Data.data.password }}{{- end -}} vault.hashicorp.com/agent-inject-secret-mas-matrix-shared: "kv/data/atlas/comms/mas-secrets-runtime" vault.hashicorp.com/agent-inject-template-mas-matrix-shared: | {{- with secret "kv/data/atlas/comms/mas-secrets-runtime" -}}{{ .Data.data.matrix_shared_secret }}{{- end -}} vault.hashicorp.com/agent-inject-secret-mas-kc-secret: "kv/data/atlas/comms/mas-secrets-runtime" vault.hashicorp.com/agent-inject-template-mas-kc-secret: | {{- with secret "kv/data/atlas/comms/mas-secrets-runtime" -}}{{ .Data.data.keycloak_client_secret }}{{- end -}} spec: restartPolicy: Never affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-role.kubernetes.io/worker operator: Exists preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: kubernetes.io/arch operator: In values: ["arm64"] serviceAccountName: comms-vault volumes: - name: vault-scripts configMap: name: comms-vault-env defaultMode: 0555 containers: - name: ensure image: python:3.11-slim volumeMounts: - name: vault-scripts mountPath: /vault/scripts readOnly: true env: - name: MAS_ADMIN_CLIENT_ID value: 01KDXMVQBQ5JNY6SEJPZW6Z8BM - name: MAS_ADMIN_CLIENT_SECRET_FILE value: /vault/secrets/mas-admin-secret - name: MAS_TOKEN_URL value: http://matrix-authentication-service:8080/oauth2/token - name: MAS_ADMIN_API_BASE value: http://matrix-authentication-service:8081/api/admin/v1 - name: SEEDER_USER value: othrys-seeder - name: BOT_USER value: atlasbot command: - /bin/sh - -c - | set -euo pipefail . /vault/scripts/comms_vault_env.sh pip install --no-cache-dir requests >/dev/null python - <<'PY' import base64 import os import time import requests import urllib.parse MAS_ADMIN_CLIENT_ID = os.environ["MAS_ADMIN_CLIENT_ID"] MAS_ADMIN_CLIENT_SECRET_FILE = os.environ["MAS_ADMIN_CLIENT_SECRET_FILE"] MAS_TOKEN_URL = os.environ["MAS_TOKEN_URL"] MAS_ADMIN_API_BASE = os.environ["MAS_ADMIN_API_BASE"].rstrip("/") AUTH_BASE = "http://matrix-authentication-service:8080" SERVER_NAME = "live.bstein.dev" def wait_for_service(url): last = None for attempt in range(1, 11): try: requests.get(url, timeout=10) return except Exception as exc: # noqa: BLE001 last = exc time.sleep(attempt * 2) raise RuntimeError(f"MAS service not reachable: {last}") def request_with_retry(method, url, attempts=6, **kwargs): last = None for attempt in range(1, attempts + 1): try: return requests.request(method, url, **kwargs) except requests.RequestException as exc: last = exc time.sleep(attempt * 2) raise RuntimeError(f"request failed for {url}: {last}") def admin_token(): with open(MAS_ADMIN_CLIENT_SECRET_FILE, "r", encoding="utf-8") as f: secret = f.read().strip() basic = base64.b64encode(f"{MAS_ADMIN_CLIENT_ID}:{secret}".encode()).decode() last = None for attempt in range(1, 6): try: r = requests.post( MAS_TOKEN_URL, headers={"Authorization": f"Basic {basic}"}, data={"grant_type": "client_credentials", "scope": "urn:mas:admin"}, timeout=30, ) if r.status_code == 200: return r.json()["access_token"] except Exception as exc: # noqa: BLE001 last = exc time.sleep(attempt * 2) raise RuntimeError(f"MAS admin token request failed: {last}") def get_user(token, username): r = request_with_retry( "GET", f"{MAS_ADMIN_API_BASE}/users/by-username/{urllib.parse.quote(username)}", headers={"Authorization": f"Bearer {token}"}, timeout=30, ) if r.status_code == 404: return None r.raise_for_status() return r.json()["data"] def create_user(token, username, password): payloads = [ { "data": { "type": "user", "attributes": { "username": username, "password": password, }, } }, {"username": username, "password": password}, ] for payload in payloads: r = request_with_retry( "POST", f"{MAS_ADMIN_API_BASE}/users", headers={"Authorization": f"Bearer {token}"}, json=payload, timeout=30, ) if r.status_code in (200, 201): return r.json().get("data") or {} if r.status_code == 409: return None return None def update_password(token, user_id, password): r = request_with_retry( "POST", f"{MAS_ADMIN_API_BASE}/users/{urllib.parse.quote(user_id)}/set-password", headers={"Authorization": f"Bearer {token}"}, json={"password": password}, timeout=30, ) return r.status_code in (200, 204) def ensure_user(token, username, password): user = get_user(token, username) if user is None: user = create_user(token, username, password) user = get_user(token, username) if user is None: raise RuntimeError(f"failed to ensure user {username}") update_password(token, user["id"], password) login_name = username if not login_name.startswith("@"): login_name = f"@{login_name}:{SERVER_NAME}" r = request_with_retry( "POST", f"{AUTH_BASE}/_matrix/client/v3/login", json={ "type": "m.login.password", "identifier": {"type": "m.id.user", "user": login_name}, "password": password, }, timeout=30, ) if r.status_code != 200: raise RuntimeError(f"login failed for {username}: {r.status_code} {r.text}") wait_for_service(MAS_ADMIN_API_BASE) token = admin_token() ensure_user(token, os.environ["SEEDER_USER"], os.environ["SEEDER_PASS"]) ensure_user(token, os.environ["BOT_USER"], os.environ["BOT_PASS"]) PY