From 76deb9a160d31607a39b6d30b5ea31702730586c Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Thu, 8 Jan 2026 03:53:49 -0300 Subject: [PATCH] comms: ensure core secrets and synapse oidc --- services/comms/comms-secrets-ensure-job.yaml | 102 ++++++++++++++++++ services/comms/comms-secrets-ensure-rbac.yaml | 34 ++++++ services/comms/kustomization.yaml | 2 + services/keycloak/kustomization.yaml | 1 + .../synapse-oidc-secret-ensure-job.yaml | 75 +++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 services/comms/comms-secrets-ensure-job.yaml create mode 100644 services/comms/comms-secrets-ensure-rbac.yaml create mode 100644 services/keycloak/synapse-oidc-secret-ensure-job.yaml diff --git a/services/comms/comms-secrets-ensure-job.yaml b/services/comms/comms-secrets-ensure-job.yaml new file mode 100644 index 0000000..877649b --- /dev/null +++ b/services/comms/comms-secrets-ensure-job.yaml @@ -0,0 +1,102 @@ +# services/comms/comms-secrets-ensure-job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: comms-secrets-ensure-1 + namespace: comms +spec: + backoffLimit: 1 + ttlSecondsAfterFinished: 3600 + template: + spec: + serviceAccountName: comms-secrets-ensure + restartPolicy: Never + containers: + - name: ensure + image: bitnami/kubectl:latest + command: ["/bin/sh", "-c"] + args: + - | + set -eu + trap 'echo "comms-secrets-ensure failed"; sleep 300' ERR + umask 077 + + safe_pass() { + head -c 32 /dev/urandom | base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=' + } + + get_secret_value() { + ns="$1" + name="$2" + key="$3" + kubectl -n "${ns}" get secret "${name}" -o "jsonpath={.data.${key}}" 2>/dev/null | base64 -d 2>/dev/null || true + } + + ensure_secret_key() { + ns="$1" + name="$2" + key="$3" + value="$4" + if ! kubectl -n "${ns}" get secret "${name}" >/dev/null 2>&1; then + kubectl -n "${ns}" create secret generic "${name}" --from-literal="${key}=${value}" >/dev/null + return + fi + existing="$(kubectl -n "${ns}" get secret "${name}" -o "jsonpath={.data.${key}}" 2>/dev/null || true)" + if [ -z "${existing}" ]; then + b64="$(printf '%s' "${value}" | base64 | tr -d '\n')" + payload="$(printf '{"data":{"%s":"%s"}}' "${key}" "${b64}")" + kubectl -n "${ns}" patch secret "${name}" --type=merge -p "${payload}" >/dev/null + fi + } + + ensure_chat_secret() { + ns="$1" + if ! kubectl -n "${ns}" get secret chat-ai-keys-runtime >/dev/null 2>&1; then + kubectl -n "${ns}" create secret generic chat-ai-keys-runtime \ + --from-literal=matrix="${CHAT_KEY_MATRIX}" \ + --from-literal=homepage="${CHAT_KEY_HOMEPAGE}" >/dev/null + return + fi + ensure_secret_key "${ns}" chat-ai-keys-runtime matrix "${CHAT_KEY_MATRIX}" + ensure_secret_key "${ns}" chat-ai-keys-runtime homepage "${CHAT_KEY_HOMEPAGE}" + } + + CHAT_KEY_MATRIX="$(get_secret_value comms chat-ai-keys-runtime matrix)" + CHAT_KEY_HOMEPAGE="$(get_secret_value comms chat-ai-keys-runtime homepage)" + if [ -z "${CHAT_KEY_MATRIX}" ] || [ -z "${CHAT_KEY_HOMEPAGE}" ]; then + ALT_MATRIX="$(get_secret_value bstein-dev-home chat-ai-keys-runtime matrix)" + ALT_HOMEPAGE="$(get_secret_value bstein-dev-home chat-ai-keys-runtime homepage)" + [ -z "${CHAT_KEY_MATRIX}" ] && CHAT_KEY_MATRIX="${ALT_MATRIX}" + [ -z "${CHAT_KEY_HOMEPAGE}" ] && CHAT_KEY_HOMEPAGE="${ALT_HOMEPAGE}" + fi + [ -z "${CHAT_KEY_MATRIX}" ] && CHAT_KEY_MATRIX="$(safe_pass)" + [ -z "${CHAT_KEY_HOMEPAGE}" ] && CHAT_KEY_HOMEPAGE="$(safe_pass)" + + ensure_chat_secret comms + ensure_chat_secret bstein-dev-home + + ensure_secret_key comms turn-shared-secret TURN_STATIC_AUTH_SECRET "$(safe_pass)" + ensure_secret_key comms livekit-api primary "$(safe_pass)" + ensure_secret_key comms synapse-redis redis-password "$(safe_pass)" + ensure_secret_key comms synapse-macaroon macaroon_secret_key "$(safe_pass)" + ensure_secret_key comms atlasbot-credentials-runtime bot-password "$(safe_pass)" + ensure_secret_key comms atlasbot-credentials-runtime seeder-password "$(safe_pass)" + + SYN_PASS="$(get_secret_value comms synapse-db POSTGRES_PASSWORD)" + if [ -z "${SYN_PASS}" ]; then + SYN_PASS="$(safe_pass)" + kubectl -n comms create secret generic synapse-db --from-literal=POSTGRES_PASSWORD="${SYN_PASS}" >/dev/null + fi + + POD_NAME="$(kubectl -n postgres get pods -l app=postgres -o jsonpath='{.items[0].metadata.name}')" + if [ -z "${POD_NAME}" ]; then + echo "postgres pod not found" >&2 + exit 1 + fi + SYN_PASS_SQL="$(printf '%s' "${SYN_PASS}" | sed "s/'/''/g")" + kubectl -n postgres exec -i "${POD_NAME}" -- psql -U postgres -d postgres \ + -c "CREATE ROLE synapse LOGIN PASSWORD '${SYN_PASS_SQL}';" || true + kubectl -n postgres exec -i "${POD_NAME}" -- psql -U postgres -d postgres \ + -c "ALTER ROLE synapse WITH PASSWORD '${SYN_PASS_SQL}';" + kubectl -n postgres exec -i "${POD_NAME}" -- psql -U postgres -d postgres \ + -c "CREATE DATABASE synapse OWNER synapse;" || true diff --git a/services/comms/comms-secrets-ensure-rbac.yaml b/services/comms/comms-secrets-ensure-rbac.yaml new file mode 100644 index 0000000..dfb4f21 --- /dev/null +++ b/services/comms/comms-secrets-ensure-rbac.yaml @@ -0,0 +1,34 @@ +# services/comms/comms-secrets-ensure-rbac.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: comms-secrets-ensure + namespace: comms +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: comms-secrets-ensure +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "create", "patch", "update"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["pods/exec"] + verbs: ["create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: comms-secrets-ensure +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: comms-secrets-ensure +subjects: + - kind: ServiceAccount + name: comms-secrets-ensure + namespace: comms diff --git a/services/comms/kustomization.yaml b/services/comms/kustomization.yaml index b08f6db..f1e0c80 100644 --- a/services/comms/kustomization.yaml +++ b/services/comms/kustomization.yaml @@ -11,8 +11,10 @@ resources: - mas-configmap.yaml - mas-admin-client-secret-ensure-job.yaml - mas-secrets-ensure-rbac.yaml + - comms-secrets-ensure-rbac.yaml - mas-db-ensure-rbac.yaml - mas-db-ensure-job.yaml + - comms-secrets-ensure-job.yaml - mas-deployment.yaml - element-rendered.yaml - livekit-config.yaml diff --git a/services/keycloak/kustomization.yaml b/services/keycloak/kustomization.yaml index d6dd32e..e3d6513 100644 --- a/services/keycloak/kustomization.yaml +++ b/services/keycloak/kustomization.yaml @@ -17,6 +17,7 @@ resources: - ldap-federation-job.yaml - user-overrides-job.yaml - mas-secrets-ensure-job.yaml + - synapse-oidc-secret-ensure-job.yaml - service.yaml - ingress.yaml generatorOptions: diff --git a/services/keycloak/synapse-oidc-secret-ensure-job.yaml b/services/keycloak/synapse-oidc-secret-ensure-job.yaml new file mode 100644 index 0000000..6468fd8 --- /dev/null +++ b/services/keycloak/synapse-oidc-secret-ensure-job.yaml @@ -0,0 +1,75 @@ +# services/keycloak/synapse-oidc-secret-ensure-job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: synapse-oidc-secret-ensure-1 + namespace: sso +spec: + backoffLimit: 0 + ttlSecondsAfterFinished: 3600 + template: + spec: + serviceAccountName: mas-secrets-ensure + restartPolicy: Never + containers: + - name: apply + image: alpine:3.20 + command: ["/bin/sh", "-c"] + args: + - | + set -euo pipefail + apk add --no-cache curl jq >/dev/null + + KC_URL="http://keycloak.sso.svc.cluster.local" + ACCESS_TOKEN="" + for attempt in 1 2 3 4 5; do + TOKEN_JSON="$(curl -sS -X POST "$KC_URL/realms/master/protocol/openid-connect/token" \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -d "grant_type=password" \ + -d "client_id=admin-cli" \ + -d "username=${KEYCLOAK_ADMIN}" \ + -d "password=${KEYCLOAK_ADMIN_PASSWORD}" || true)" + ACCESS_TOKEN="$(echo "$TOKEN_JSON" | jq -r '.access_token' 2>/dev/null || true)" + if [ -n "$ACCESS_TOKEN" ] && [ "$ACCESS_TOKEN" != "null" ]; then + break + fi + echo "Keycloak token request failed (attempt ${attempt})" >&2 + sleep $((attempt * 2)) + done + if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then + echo "Failed to fetch Keycloak admin token" >&2 + exit 1 + fi + + CLIENT_ID="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "$KC_URL/admin/realms/atlas/clients?clientId=synapse" | jq -r '.[0].id' 2>/dev/null || true)" + if [ -z "$CLIENT_ID" ] || [ "$CLIENT_ID" = "null" ]; then + echo "Keycloak client synapse not found" >&2 + exit 1 + fi + CLIENT_SECRET="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "$KC_URL/admin/realms/atlas/clients/${CLIENT_ID}/client-secret" | jq -r '.value' 2>/dev/null || true)" + if [ -z "$CLIENT_SECRET" ] || [ "$CLIENT_SECRET" = "null" ]; then + echo "Keycloak client secret not found" >&2 + exit 1 + fi + + existing="$(kubectl -n comms get secret synapse-oidc -o jsonpath='{.data.client-secret}' 2>/dev/null || true)" + if [ -n "${existing}" ]; then + exit 0 + fi + + kubectl -n comms create secret generic synapse-oidc \ + --from-literal=client-secret="${CLIENT_SECRET}" \ + --dry-run=client -o yaml | kubectl -n comms apply -f - >/dev/null + env: + - name: KEYCLOAK_ADMIN + valueFrom: + secretKeyRef: + name: keycloak-admin + key: username + - name: KEYCLOAK_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-admin + key: password