This commit is contained in:
2026-04-24 08:40:57 +02:00
parent 1a4d8092a7
commit 8ced919e00
4 changed files with 64 additions and 7 deletions

View File

@@ -14,6 +14,10 @@ stringData:
{
"clientId": "backstage",
"name": "Backstage Developer Portal",
"serviceAccountsEnabled": true,
"serviceAccountRoles": {
"realm-management": ["view-users", "query-users", "view-groups", "query-groups"]
},
"redirectUris": ["https://backstage.forteapps.net/api/auth/oidc/handler/frame"],
"webOrigins": ["https://backstage.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],

View File

@@ -997,6 +997,7 @@ ignore:
- **Important**: `dangerouslyAllowSignInWithoutUserInCatalog` must be nested inside the resolver object, not at the provider level
**Keycloak User/Group Sync**:
- The `backstage` Keycloak client has `serviceAccountsEnabled: true` with `realm-management` roles (`view-users`, `query-users`, `view-groups`, `query-groups`) — assigned automatically by the registrar
- The `keycloakOrg` catalog provider auto-imports users and groups from the `forte` realm
- Requires the Keycloak dynamic plugin to be enabled (pre-installed but disabled by default in RHDH)
- Syncs every 30 minutes with 15-second initial delay
@@ -1082,9 +1083,10 @@ upstream:
2. For each config Secret, parses `client.json` and computes a config hash
3. Skips if hash matches annotation and credential Secret already exists
4. Creates or updates the Keycloak client via Admin API
5. Fetches the generated client secret
6. Upserts credential Secret in target namespace + central `secrets` namespace
7. Annotates config Secret with sync status, config hash, and timestamp
5. If `serviceAccountsEnabled: true` and `serviceAccountRoles` defined, assigns service account roles (e.g., `realm-management``view-users`)
6. Fetches the generated client secret
7. Upserts credential Secret in target namespace + central `secrets` namespace
8. Annotates config Secret with sync status, config hash, and timestamp
**Resources**:
- `ServiceAccount`: `keycloak-client-registrar` (namespace: `keycloak`)

View File

@@ -22,9 +22,9 @@ route:
upstream:
backstage:
image:
registry: quay.io
repository: rhdh-community/rhdh
tag: next
registry: ghcr.io
repository: vfarcic/idp-full-backstage
tag: 0.0.3
podSecurityContext:
runAsUser: 1001

View File

@@ -371,7 +371,7 @@ extraDeploy:
continue
fi
# Build Keycloak client representation (strip our secret delivery config)
# Build Keycloak client representation (strip our secret/role delivery config)
KC_CLIENT=$(echo "$CLIENT_JSON" | jq '{
clientId: .clientId,
name: .name,
@@ -379,6 +379,7 @@ extraDeploy:
protocol: "openid-connect",
clientAuthenticatorType: "client-secret",
standardFlowEnabled: true,
serviceAccountsEnabled: (.serviceAccountsEnabled // false),
directAccessGrantsEnabled: false,
publicClient: false,
redirectUris: .redirectUris,
@@ -426,6 +427,56 @@ extraDeploy:
# Sync credentials to target namespace
sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$CRED_NS" "$CRED_NAME" "$CRED_ID_KEY" "$CRED_SECRET_KEY"
# Assign service account roles if serviceAccountsEnabled
SA_ENABLED=$(echo "$CLIENT_JSON" | jq -r '.serviceAccountsEnabled // false')
SA_ROLES_JSON=$(echo "$CLIENT_JSON" | jq -c '.serviceAccountRoles // empty')
if [ "$SA_ENABLED" = "true" ] && [ -n "$SA_ROLES_JSON" ]; then
echo " Assigning service account roles for '${CLIENT_ID}'"
# Get the service account user for this client
SA_USER_ID=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/service-account-user" \
| jq -r '.id // empty')
if [ -z "$SA_USER_ID" ]; then
echo " WARNING: Could not get service account user for '${CLIENT_ID}'"
else
# Iterate over each target client and its roles
echo "$SA_ROLES_JSON" | jq -r 'keys[]' | while read -r TARGET_CLIENT_ID; do
# Get the target client's UUID
TARGET_UUID=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${TARGET_CLIENT_ID}" \
| jq -r '.[0].id // empty')
if [ -z "$TARGET_UUID" ]; then
echo " WARNING: Target client '${TARGET_CLIENT_ID}' not found"
continue
fi
# Get available roles from the target client
AVAILABLE_ROLES=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${TARGET_UUID}/roles")
# Build the role payload from requested role names
ROLE_PAYLOAD=$(echo "$SA_ROLES_JSON" | jq -c --arg tc "$TARGET_CLIENT_ID" --argjson avail "$AVAILABLE_ROLES" '
.[$tc] as $wanted |
[$avail[] | select(.name as $n | $wanted | index($n))]
')
ROLE_COUNT=$(echo "$ROLE_PAYLOAD" | jq 'length')
if [ "$ROLE_COUNT" = "0" ]; then
echo " WARNING: No matching roles found for '${TARGET_CLIENT_ID}'"
continue
fi
# Assign the roles to the service account
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-X POST -d "$ROLE_PAYLOAD" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/users/${SA_USER_ID}/role-mappings/clients/${TARGET_UUID}")
if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "200" ]; then
echo " Assigned ${ROLE_COUNT} roles from '${TARGET_CLIENT_ID}'"
else
echo " WARNING: Failed to assign roles from '${TARGET_CLIENT_ID}' (HTTP ${HTTP_CODE})"
fi
done
fi
fi
# Annotate config Secret with hash and sync status
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/config-hash" "$CONFIG_HASH"
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "synced"