From a98e1bb7b228047d2ce48ccaa587d24f24b8766d Mon Sep 17 00:00:00 2001 From: jenkins Date: Sat, 20 Jun 2026 14:08:18 -0300 Subject: [PATCH] gitea: wire Veles OIDC login --- services/gitea/deployment.yaml | 157 +++++++++--- services/keycloak/kustomization.yaml | 4 + .../veles-gitea-oidc-secret-ensure-job.yaml | 53 ++++ .../scripts/veles_gitea_oidc_secret_ensure.sh | 239 ++++++++++++++++++ .../vault/scripts/vault_k8s_auth_configure.sh | 2 +- 5 files changed, 417 insertions(+), 38 deletions(-) create mode 100644 services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml create mode 100755 services/keycloak/scripts/veles_gitea_oidc_secret_ensure.sh diff --git a/services/gitea/deployment.yaml b/services/gitea/deployment.yaml index 75b27fb9..b26b29eb 100644 --- a/services/gitea/deployment.yaml +++ b/services/gitea/deployment.yaml @@ -56,6 +56,36 @@ spec: {{ with secret "kv/data/atlas/gitea/gitea-oidc" }} {{ .Data.data.openid_auto_discovery_url }} {{ end }} + vault.hashicorp.com/agent-inject-secret-gitea-veles__client_id: "kv/data/atlas/gitea/gitea-veles-oidc" + vault.hashicorp.com/agent-inject-template-gitea-veles__client_id: | + {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }} + {{ .Data.data.client_id }} + {{ end }} + vault.hashicorp.com/agent-inject-secret-gitea-veles__client_secret: "kv/data/atlas/gitea/gitea-veles-oidc" + vault.hashicorp.com/agent-inject-template-gitea-veles__client_secret: | + {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }} + {{ .Data.data.client_secret }} + {{ end }} + vault.hashicorp.com/agent-inject-secret-gitea-veles__discovery_url: "kv/data/atlas/gitea/gitea-veles-oidc" + vault.hashicorp.com/agent-inject-template-gitea-veles__discovery_url: | + {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }} + {{ .Data.data.openid_auto_discovery_url }} + {{ end }} + vault.hashicorp.com/agent-inject-secret-gitea-veles__claim_name: "kv/data/atlas/gitea/gitea-veles-oidc" + vault.hashicorp.com/agent-inject-template-gitea-veles__claim_name: | + {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }} + {{ .Data.data.required_claim_name }} + {{ end }} + vault.hashicorp.com/agent-inject-secret-gitea-veles__claim_value: "kv/data/atlas/gitea/gitea-veles-oidc" + vault.hashicorp.com/agent-inject-template-gitea-veles__claim_value: | + {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }} + {{ .Data.data.required_claim_value }} + {{ end }} + vault.hashicorp.com/agent-inject-secret-gitea-veles__team_map: "kv/data/atlas/gitea/gitea-veles-oidc" + vault.hashicorp.com/agent-inject-template-gitea-veles__team_map: | + {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }} + {{ .Data.data.group_team_map }} + {{ end }} spec: serviceAccountName: gitea-vault initContainers: @@ -68,49 +98,102 @@ spec: - /bin/sh - -c - | - set -euo pipefail - CLIENT_ID="$(tr -d '\r\n' &2 + return 1 + fi + tr -d '\r\n' <"$path" + } - if [ -n "$id" ]; then - echo "Updating existing auth source id=$id" - if ! $BIN -c "$APPINI" admin auth update-oauth \ - --id "$id" \ - --name keycloak \ - --provider openidConnect \ - --key "$CLIENT_ID" \ - --secret "$CLIENT_SECRET" \ - --auto-discover-url "$DISCOVERY_URL" \ - --scopes "openid profile email groups" \ - --required-claim-name "" \ - --required-claim-value "" \ - --group-claim-name groups \ - --admin-group admin \ - --skip-local-2fa; then - echo "OIDC update failed; continuing without blocking startup" >&2 + CLIENT_ID="$(read_secret /vault/secrets/gitea-oidc__client_id || true)" + CLIENT_SECRET="$(read_secret /vault/secrets/gitea-oidc__client_secret || true)" + DISCOVERY_URL="$(read_secret /vault/secrets/gitea-oidc__openid_auto_discovery_url || true)" + VELES_CLIENT_ID="$(read_secret /vault/secrets/gitea-veles__client_id || true)" + VELES_CLIENT_SECRET="$(read_secret /vault/secrets/gitea-veles__client_secret || true)" + VELES_DISCOVERY_URL="$(read_secret /vault/secrets/gitea-veles__discovery_url || true)" + VELES_REQUIRED_CLAIM_NAME="$(read_secret /vault/secrets/gitea-veles__claim_name || true)" + VELES_REQUIRED_CLAIM_VALUE="$(read_secret /vault/secrets/gitea-veles__claim_value || true)" + VELES_GROUP_TEAM_MAP="$(read_secret /vault/secrets/gitea-veles__team_map || true)" + + if [ ! -r "$APPINI" ]; then + echo "Gitea app.ini is not readable yet; skipping OIDC source maintenance" >&2 + exit 0 + fi + + if ! list="$($BIN -c "$APPINI" admin auth list)"; then + echo "Gitea auth source list failed; skipping OIDC source maintenance" >&2 + exit 0 + fi + + ensure_oidc_source() { + source_name="$1" + source_scopes="$2" + source_client_id="$3" + source_client_secret="$4" + source_discovery_url="$5" + source_required_claim_name="$6" + source_required_claim_value="$7" + shift 7 + id=$(echo "$list" | awk -v name="$source_name" '$2==name{print $1}') + if [ -n "$id" ]; then + echo "Updating auth source ${source_name} id=${id}" + if ! $BIN -c "$APPINI" admin auth update-oauth \ + --id "$id" \ + --name "$source_name" \ + --provider openidConnect \ + --key "$source_client_id" \ + --secret "$source_client_secret" \ + --auto-discover-url "$source_discovery_url" \ + --scopes "$source_scopes" \ + --required-claim-name "$source_required_claim_name" \ + --required-claim-value "$source_required_claim_value" \ + --group-claim-name groups \ + --admin-group admin \ + --skip-local-2fa "$@"; then + echo "OIDC update failed for ${source_name}; continuing without blocking startup" >&2 + fi + else + echo "Creating auth source ${source_name}" + if ! $BIN -c "$APPINI" admin auth add-oauth \ + --name "$source_name" \ + --provider openidConnect \ + --key "$source_client_id" \ + --secret "$source_client_secret" \ + --auto-discover-url "$source_discovery_url" \ + --scopes "$source_scopes" \ + --required-claim-name "$source_required_claim_name" \ + --required-claim-value "$source_required_claim_value" \ + --group-claim-name groups \ + --admin-group admin \ + --skip-local-2fa "$@"; then + echo "OIDC create failed for ${source_name}; continuing without blocking startup" >&2 + fi + fi + } + + if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ] && [ -n "$DISCOVERY_URL" ]; then + ensure_oidc_source keycloak "openid profile email groups" "$CLIENT_ID" "$CLIENT_SECRET" "$DISCOVERY_URL" "" "" + else + echo "Skipping keycloak auth source maintenance because atlas OIDC secret data is incomplete" >&2 + fi + + if [ -n "$VELES_CLIENT_ID" ] && [ -n "$VELES_CLIENT_SECRET" ] && [ -n "$VELES_DISCOVERY_URL" ] && [ -n "$VELES_REQUIRED_CLAIM_NAME" ] && [ -n "$VELES_REQUIRED_CLAIM_VALUE" ]; then + if [ -n "$VELES_GROUP_TEAM_MAP" ]; then + ensure_oidc_source veles "openid profile email" "$VELES_CLIENT_ID" "$VELES_CLIENT_SECRET" "$VELES_DISCOVERY_URL" "$VELES_REQUIRED_CLAIM_NAME" "$VELES_REQUIRED_CLAIM_VALUE" \ + --restricted-group "$VELES_REQUIRED_CLAIM_VALUE" \ + --group-team-map "$VELES_GROUP_TEAM_MAP" + else + ensure_oidc_source veles "openid profile email" "$VELES_CLIENT_ID" "$VELES_CLIENT_SECRET" "$VELES_DISCOVERY_URL" "$VELES_REQUIRED_CLAIM_NAME" "$VELES_REQUIRED_CLAIM_VALUE" \ + --restricted-group "$VELES_REQUIRED_CLAIM_VALUE" fi else - echo "Creating keycloak auth source" - if ! $BIN -c "$APPINI" admin auth add-oauth \ - --name keycloak \ - --provider openidConnect \ - --key "$CLIENT_ID" \ - --secret "$CLIENT_SECRET" \ - --auto-discover-url "$DISCOVERY_URL" \ - --scopes "openid profile email groups" \ - --required-claim-name "" \ - --required-claim-value "" \ - --group-claim-name groups \ - --admin-group admin \ - --skip-local-2fa; then - echo "OIDC create failed; continuing without blocking startup" >&2 - fi + echo "Skipping veles auth source maintenance because Veles OIDC secret data is incomplete" >&2 fi volumeMounts: - name: gitea-data diff --git a/services/keycloak/kustomization.yaml b/services/keycloak/kustomization.yaml index f635d2f8..90ca9596 100644 --- a/services/keycloak/kustomization.yaml +++ b/services/keycloak/kustomization.yaml @@ -28,6 +28,7 @@ resources: - oneoffs/quality-oidc-secret-ensure-job.yaml - oneoffs/agent-oidc-secret-ensure-job.yaml - oneoffs/veles-realm-ensure-job.yaml + - oneoffs/veles-gitea-oidc-secret-ensure-job.yaml - oneoffs/metis-ssh-keys-secret-ensure-job.yaml - oneoffs/metis-node-passwords-secret-ensure-job.yaml - oneoffs/harbor-oidc-secret-ensure-job.yaml @@ -54,3 +55,6 @@ configMapGenerator: - name: agent-oidc-secret-ensure-script files: - agent_oidc_secret_ensure.sh=scripts/agent_oidc_secret_ensure.sh + - name: veles-gitea-oidc-secret-ensure-script + files: + - veles_gitea_oidc_secret_ensure.sh=scripts/veles_gitea_oidc_secret_ensure.sh diff --git a/services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml b/services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml new file mode 100644 index 00000000..0187d540 --- /dev/null +++ b/services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml @@ -0,0 +1,53 @@ +# services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml +# One-off job for sso/veles-gitea-oidc-secret-ensure-1. +# Purpose: create/update the Veles realm Gitea OIDC client and write the +# matching Gitea auth-source secret to Vault. +# Keep suspended until the Vault policy change has reconciled, then unsuspend once. +apiVersion: batch/v1 +kind: Job +metadata: + name: veles-gitea-oidc-secret-ensure-1 + namespace: sso +spec: + suspend: true + backoffLimit: 0 + ttlSecondsAfterFinished: 3600 + template: + metadata: + annotations: + vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/agent-pre-populate-only: "true" + vault.hashicorp.com/role: "sso-secrets" + vault.hashicorp.com/agent-inject-secret-keycloak-admin-env.sh: "kv/data/atlas/shared/keycloak-admin" + vault.hashicorp.com/agent-inject-template-keycloak-admin-env.sh: | + {{ with secret "kv/data/atlas/shared/keycloak-admin" }} + export KEYCLOAK_ADMIN="{{ .Data.data.username }}" + export KEYCLOAK_ADMIN_USER="{{ .Data.data.username }}" + export KEYCLOAK_ADMIN_PASSWORD="{{ .Data.data.password }}" + {{ end }} + spec: + serviceAccountName: mas-secrets-ensure + restartPolicy: Never + volumes: + - name: veles-gitea-oidc-secret-ensure-script + configMap: + name: veles-gitea-oidc-secret-ensure-script + defaultMode: 0555 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: ["arm64"] + - key: node-role.kubernetes.io/worker + operator: Exists + containers: + - name: apply + image: bitnami/kubectl@sha256:554ab88b1858e8424c55de37ad417b16f2a0e65d1607aa0f3fe3ce9b9f10b131 + command: ["/scripts/veles_gitea_oidc_secret_ensure.sh"] + volumeMounts: + - name: veles-gitea-oidc-secret-ensure-script + mountPath: /scripts + readOnly: true diff --git a/services/keycloak/scripts/veles_gitea_oidc_secret_ensure.sh b/services/keycloak/scripts/veles_gitea_oidc_secret_ensure.sh new file mode 100755 index 00000000..322c19bb --- /dev/null +++ b/services/keycloak/scripts/veles_gitea_oidc_secret_ensure.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env sh +set -euo pipefail + +. /vault/secrets/keycloak-admin-env.sh + +KC_URL="${KEYCLOAK_SERVER:-http://keycloak.sso.svc.cluster.local}" +REALM="${KEYCLOAK_REALM:-veles}" +CLIENT_ID="${KEYCLOAK_CLIENT_ID:-gitea}" +PUBLIC_BASE_URL="${GITEA_PUBLIC_BASE_URL:-https://scm.bstein.dev}" +AUTH_SOURCE_NAME="${GITEA_AUTH_SOURCE_NAME:-veles}" +TESTER_GROUP="${VELES_GITEA_TESTER_GROUP:-veles-tester}" +VAULT_SECRET_PATH="${VAULT_SECRET_PATH:-gitea/gitea-veles-oidc}" +GROUP_TEAM_MAP="${GITEA_GROUP_TEAM_MAP:-}" + +ACCESS_TOKEN="" +for attempt in 1 2 3 4 5 6 7 8 9 10; do + if curl -fsS "${KC_URL}/realms/master" >/dev/null 2>&1; then + break + fi + echo "Waiting for Keycloak to be reachable (attempt ${attempt})" >&2 + sleep $((attempt * 2)) +done + +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 + +ensure_group() { + group_name="$1" + groups="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/groups?search=$(printf '%s' "${group_name}" | jq -sRr @uri)" || true)" + group_id="$(echo "$groups" | jq -r --arg name "$group_name" '.[]? | select(.name == $name) | .id' | head -n1 || true)" + if [ -n "$group_id" ] && [ "$group_id" != "null" ]; then + printf '%s' "$group_id" + return + fi + status="$(curl -sS -o /tmp/keycloak-group-create.json -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "$(jq -nc --arg name "$group_name" '{name:$name}')" \ + "${KC_URL}/admin/realms/${REALM}/groups")" + if [ "$status" != "201" ] && [ "$status" != "204" ] && [ "$status" != "409" ]; then + echo "Keycloak group create failed for ${group_name} (status ${status})" >&2 + cat /tmp/keycloak-group-create.json >&2 || true + exit 1 + fi + groups="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/groups?search=$(printf '%s' "${group_name}" | jq -sRr @uri)" || true)" + group_id="$(echo "$groups" | jq -r --arg name "$group_name" '.[]? | select(.name == $name) | .id' | head -n1 || true)" + if [ -z "$group_id" ] || [ "$group_id" = "null" ]; then + echo "Keycloak group ${group_name} not found after create" >&2 + exit 1 + fi + printf '%s' "$group_id" +} + +ensure_role() { + role_name="$1" + role="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/roles/$(printf '%s' "${role_name}" | jq -sRr @uri)" || true)" + if echo "$role" | jq -e --arg name "$role_name" '.name == $name' >/dev/null 2>&1; then + printf '%s' "$role" + return + fi + status="$(curl -sS -o /tmp/keycloak-role-create.json -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "$(jq -nc --arg name "$role_name" '{name:$name}')" \ + "${KC_URL}/admin/realms/${REALM}/roles")" + if [ "$status" != "201" ] && [ "$status" != "204" ] && [ "$status" != "409" ]; then + echo "Keycloak role create failed for ${role_name} (status ${status})" >&2 + cat /tmp/keycloak-role-create.json >&2 || true + exit 1 + fi + role="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/roles/$(printf '%s' "${role_name}" | jq -sRr @uri)" || true)" + if ! echo "$role" | jq -e --arg name "$role_name" '.name == $name' >/dev/null 2>&1; then + echo "Keycloak role ${role_name} not found after create" >&2 + exit 1 + fi + printf '%s' "$role" +} + +ensure_group_role() { + group_id="$1" + role_json="$2" + role_name="$(echo "$role_json" | jq -r '.name')" + mappings="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/groups/${group_id}/role-mappings/realm" || true)" + if echo "$mappings" | jq -e --arg name "$role_name" '.[]? | select(.name == $name)' >/dev/null 2>&1; then + return + fi + status="$(curl -sS -o /tmp/keycloak-group-role.json -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "[$role_json]" \ + "${KC_URL}/admin/realms/${REALM}/groups/${group_id}/role-mappings/realm")" + if [ "$status" != "200" ] && [ "$status" != "204" ]; then + echo "Keycloak group role mapping failed for ${role_name} (status ${status})" >&2 + cat /tmp/keycloak-group-role.json >&2 || true + exit 1 + fi +} + +ensure_mapper() { + client_uuid="$1" + mapper_name="$2" + mapper_payload="$3" + mappers="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${client_uuid}/protocol-mappers/models" || true)" + mapper_id="$(echo "$mappers" | jq -r --arg name "$mapper_name" '.[]? | select(.name == $name) | .id' | head -n1 || true)" + if [ -n "$mapper_id" ] && [ "$mapper_id" != "null" ]; then + mapper_payload="$(echo "$mapper_payload" | jq --arg id "$mapper_id" '. + {id:$id}')" + status="$(curl -sS -o /tmp/keycloak-mapper.json -w "%{http_code}" -X PUT \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "${mapper_payload}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${client_uuid}/protocol-mappers/models/${mapper_id}")" + else + status="$(curl -sS -o /tmp/keycloak-mapper.json -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "${mapper_payload}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${client_uuid}/protocol-mappers/models")" + fi + if [ "$status" != "200" ] && [ "$status" != "201" ] && [ "$status" != "204" ]; then + echo "Keycloak mapper ensure failed for ${mapper_name} (status ${status})" >&2 + cat /tmp/keycloak-mapper.json >&2 || true + exit 1 + fi +} + +TESTER_GROUP_ID="$(ensure_group "${TESTER_GROUP}")" +TESTER_ROLE="$(ensure_role "${TESTER_GROUP}")" +ensure_group_role "${TESTER_GROUP_ID}" "${TESTER_ROLE}" + +CLIENT_QUERY="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/clients?clientId=$(printf '%s' "${CLIENT_ID}" | jq -sRr @uri)" || true)" +CLIENT_UUID="$(echo "$CLIENT_QUERY" | jq -r '.[0].id' 2>/dev/null || true)" + +client_payload="$(jq -nc \ + --arg client_id "${CLIENT_ID}" \ + --arg root_url "${PUBLIC_BASE_URL}" \ + --arg callback "${PUBLIC_BASE_URL}/user/oauth2/${AUTH_SOURCE_NAME}/callback" \ + '{clientId:$client_id,enabled:true,protocol:"openid-connect",publicClient:false,standardFlowEnabled:true,implicitFlowEnabled:false,directAccessGrantsEnabled:false,serviceAccountsEnabled:false,redirectUris:[$callback],webOrigins:[$root_url],rootUrl:$root_url,baseUrl:"/",attributes:{"pkce.code.challenge.method":"S256","post.logout.redirect.uris":($root_url + "/*")}}')" + +if [ -z "$CLIENT_UUID" ] || [ "$CLIENT_UUID" = "null" ]; then + status="$(curl -sS -o /tmp/keycloak-client-create.json -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "${client_payload}" \ + "${KC_URL}/admin/realms/${REALM}/clients")" + if [ "$status" != "201" ] && [ "$status" != "204" ] && [ "$status" != "409" ]; then + echo "Keycloak client create failed for ${CLIENT_ID} (status ${status})" >&2 + cat /tmp/keycloak-client-create.json >&2 || true + exit 1 + fi + CLIENT_QUERY="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/clients?clientId=$(printf '%s' "${CLIENT_ID}" | jq -sRr @uri)" || true)" + CLIENT_UUID="$(echo "$CLIENT_QUERY" | jq -r '.[0].id' 2>/dev/null || true)" +fi + +if [ -z "$CLIENT_UUID" ] || [ "$CLIENT_UUID" = "null" ]; then + echo "Keycloak client ${CLIENT_ID} not found after create" >&2 + exit 1 +fi + +status="$(curl -sS -o /tmp/keycloak-client-update.json -w "%{http_code}" -X PUT \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "${client_payload}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}")" +if [ "$status" != "200" ] && [ "$status" != "204" ]; then + echo "Keycloak client update failed for ${CLIENT_ID} (status ${status})" >&2 + cat /tmp/keycloak-client-update.json >&2 || true + exit 1 +fi + +groups_mapper_payload="$(jq -nc \ + '{name:"groups",protocol:"openid-connect",protocolMapper:"oidc-group-membership-mapper",consentRequired:false,config:{"full.path":"false","id.token.claim":"true","access.token.claim":"true","userinfo.token.claim":"true","claim.name":"groups","jsonType.label":"String"}}')" +roles_mapper_payload="$(jq -nc \ + '{name:"roles",protocol:"openid-connect",protocolMapper:"oidc-usermodel-realm-role-mapper",consentRequired:false,config:{"multivalued":"true","id.token.claim":"true","access.token.claim":"true","userinfo.token.claim":"true","claim.name":"roles","jsonType.label":"String"}}')" +ensure_mapper "${CLIENT_UUID}" groups "${groups_mapper_payload}" +ensure_mapper "${CLIENT_UUID}" roles "${roles_mapper_payload}" + +CLIENT_SECRET="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" | jq -r '.value' 2>/dev/null || true)" +if [ -z "$CLIENT_SECRET" ] || [ "$CLIENT_SECRET" = "null" ]; then + echo "Keycloak client secret not found for ${CLIENT_ID}" >&2 + exit 1 +fi + +vault_addr="${VAULT_ADDR:-http://vault.vault.svc.cluster.local:8200}" +vault_role="${VAULT_ROLE:-sso-secrets}" +jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" +login_payload="$(jq -nc --arg jwt "${jwt}" --arg role "${vault_role}" '{jwt:$jwt, role:$role}')" +vault_token="$(curl -sS --request POST --data "${login_payload}" \ + "${vault_addr}/v1/auth/kubernetes/login" | jq -r '.auth.client_token')" +if [ -z "${vault_token}" ] || [ "${vault_token}" = "null" ]; then + echo "vault login failed" >&2 + exit 1 +fi + +payload="$(jq -nc \ + --arg client_id "${CLIENT_ID}" \ + --arg client_secret "${CLIENT_SECRET}" \ + --arg issuer "https://sso.bstein.dev/realms/${REALM}" \ + --arg auto_discovery_url "https://sso.bstein.dev/realms/${REALM}/.well-known/openid-configuration" \ + --arg auth_source_name "${AUTH_SOURCE_NAME}" \ + --arg tester_group "${TESTER_GROUP}" \ + --arg group_team_map "${GROUP_TEAM_MAP}" \ + '{data:{client_id:$client_id,client_secret:$client_secret,issuer:$issuer,openid_auto_discovery_url:$auto_discovery_url,auth_source_name:$auth_source_name,required_claim_name:"groups",required_claim_value:$tester_group,group_claim_name:"groups",restricted_group:$tester_group,group_team_map:$group_team_map}}')" + +write_status="$(curl -sS -o /tmp/veles-gitea-oidc-write.json -w "%{http_code}" -X POST \ + -H "X-Vault-Token: ${vault_token}" \ + -H 'Content-Type: application/json' \ + -d "${payload}" "${vault_addr}/v1/kv/data/atlas/${VAULT_SECRET_PATH}")" +if [ "${write_status}" != "200" ] && [ "${write_status}" != "204" ]; then + echo "Vault write failed for ${VAULT_SECRET_PATH} (status ${write_status})" >&2 + cat /tmp/veles-gitea-oidc-write.json >&2 || true + exit 1 +fi + +echo "Veles Gitea OIDC client ready in Keycloak and Vault" diff --git a/services/vault/scripts/vault_k8s_auth_configure.sh b/services/vault/scripts/vault_k8s_auth_configure.sh index 2f9ae177..43d4b55e 100644 --- a/services/vault/scripts/vault_k8s_auth_configure.sh +++ b/services/vault/scripts/vault_k8s_auth_configure.sh @@ -272,7 +272,7 @@ write_policy_and_role "vault" "vault" "vault" \ write_policy_and_role "sso-secrets" "sso" "mas-secrets-ensure" \ "shared/keycloak-admin shared/postmark-relay maintenance/metis-ssh-keys" \ - "harbor/harbor-oidc vault/vault-oidc-config comms/synapse-oidc logging/oauth2-proxy-logs-oidc finance/actual-oidc maintenance/metis-oidc maintenance/soteria-oidc maintenance/metis-ssh-keys openclaw/agent-oidc veles/veles-oidc" \ + "harbor/harbor-oidc vault/vault-oidc-config comms/synapse-oidc logging/oauth2-proxy-logs-oidc finance/actual-oidc maintenance/metis-oidc maintenance/soteria-oidc maintenance/metis-ssh-keys openclaw/agent-oidc veles/veles-oidc gitea/gitea-veles-oidc" \ ' path "kv/data/atlas/nodes/*" { capabilities = ["create", "update", "read"]