# resources here are used to configure keycloak instance for SSO apiVersion: v1 kind: ServiceAccount metadata: name: keycloak-config namespace: keycloak --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: keycloak-config namespace: keycloak rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "create", "update", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: keycloak-config namespace: keycloak subjects: - kind: ServiceAccount name: keycloak-config namespace: keycloak roleRef: kind: Role name: keycloak-config apiGroup: rbac.authorization.k8s.io --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: keycloak-config namespace: argocd rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: keycloak-config namespace: argocd subjects: - kind: ServiceAccount name: keycloak-config namespace: keycloak roleRef: kind: Role name: keycloak-config apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: ConfigMap metadata: name: config-job namespace: keycloak data: client-scope-groups-payload.json: | { "name": "groups", "description": "groups a user belongs to", "attributes": { "consent.screen.text": "Access to groups a user belongs to.", "display.on.consent.screen": "true", "include.in.token.scope": "true", "gui.order": "" }, "type": "default", "protocol": "openid-connect" } group-admin-payload.json: | {"name":"admin"} group-base-user-payload.json: | {"name":"base-user"} group-mapper-payload.json: | { "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "name": "groups", "config": { "claim.name": "groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } } realm-payload.json: | {"realm":"cnoe","enabled":true} user-password.json: | { "temporary": false, "type": "password", "value": "${USER1_PASSWORD}" } user-user1.json: | { "username": "user1", "email": "", "firstName": "user", "lastName": "one", "requiredActions": [], "emailVerified": false, "groups": [ "/admin" ], "enabled": true } user-user2.json: | { "username": "user2", "email": "", "firstName": "user", "lastName": "two", "requiredActions": [], "emailVerified": false, "groups": [ "/base-user" ], "enabled": true } argo-client-payload.json: | { "protocol": "openid-connect", "clientId": "argo-workflows", "name": "Argo Workflows Client", "description": "Used for Argo Workflows SSO", "publicClient": false, "authorizationServicesEnabled": false, "serviceAccountsEnabled": false, "implicitFlowEnabled": false, "directAccessGrantsEnabled": true, "standardFlowEnabled": true, "frontchannelLogout": true, "attributes": { "saml_idp_initiated_sso_url_name": "", "oauth2.device.authorization.grant.enabled": false, "oidc.ciba.grant.enabled": false }, "alwaysDisplayInConsole": false, "rootUrl": "", "baseUrl": "", "redirectUris": [ "https://{{{ .Env.DOMAIN }}}:443/argo-workflows/oauth2/callback" ], "webOrigins": [ "/*" ] } backstage-client-payload.json: | { "protocol": "openid-connect", "clientId": "backstage", "name": "Backstage Client", "description": "Used for Backstage SSO", "publicClient": false, "authorizationServicesEnabled": false, "serviceAccountsEnabled": false, "implicitFlowEnabled": false, "directAccessGrantsEnabled": true, "standardFlowEnabled": true, "frontchannelLogout": true, "attributes": { "saml_idp_initiated_sso_url_name": "", "oauth2.device.authorization.grant.enabled": false, "oidc.ciba.grant.enabled": false }, "alwaysDisplayInConsole": false, "rootUrl": "", "baseUrl": "", "redirectUris": [ "https://{{{ .Env.DOMAIN }}}:443/api/auth/keycloak-oidc/handler/frame" ], "webOrigins": [ "/*" ] } --- apiVersion: batch/v1 kind: Job metadata: name: config namespace: keycloak annotations: argocd.argoproj.io/hook: PostSync spec: template: metadata: generateName: config spec: serviceAccountName: keycloak-config restartPolicy: Never volumes: - name: keycloak-config secret: secretName: keycloak-config - name: config-payloads configMap: name: config-job containers: - name: kubectl image: docker.io/library/ubuntu:22.04 volumeMounts: - name: keycloak-config readOnly: true mountPath: "/var/secrets/" - name: config-payloads readOnly: true mountPath: "/var/config/" command: ["/bin/bash", "-c"] args: - | #! /bin/bash set -ex -o pipefail apt -qq update && apt -qq install curl jq -y ADMIN_PASSWORD=$(cat /var/secrets/KEYCLOAK_ADMIN_PASSWORD) USER1_PASSWORD=$(cat /var/secrets/USER_PASSWORD) KEYCLOAK_URL=http://keycloak.keycloak.svc.cluster.local:8080/keycloak KEYCLOAK_TOKEN=$(curl -sS --fail-with-body -X POST -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "username=cnoe-admin" \ --data-urlencode "password=${ADMIN_PASSWORD}" \ --data-urlencode "grant_type=password" \ --data-urlencode "client_id=admin-cli" \ ${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token | jq -e -r '.access_token') set +e curl --fail-with-body -H "Authorization: bearer ${KEYCLOAK_TOKEN}" "${KEYCLOAK_URL}/admin/realms/cnoe" &> /dev/null if [ $? -eq 0 ]; then exit 0 fi set -e curl -sS -LO "https://dl.k8s.io/release/v1.28.3//bin/linux/amd64/kubectl" chmod +x kubectl echo "creating cnoe realm and groups" curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/realm-payload.json \ ${KEYCLOAK_URL}/admin/realms curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/client-scope-groups-payload.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/client-scopes curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/group-admin-payload.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/groups curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/group-base-user-payload.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/groups # Create scope mapper echo 'adding group claim to tokens' CLIENT_SCOPE_GROUPS_ID=$(curl -sS -H "Content-Type: application/json" -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -X GET ${KEYCLOAK_URL}/admin/realms/cnoe/client-scopes | jq -e -r '.[] | select(.name == "groups") | .id') curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/group-mapper-payload.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/client-scopes/${CLIENT_SCOPE_GROUPS_ID}/protocol-mappers/models echo "creating test users" curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/user-user1.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/users curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/user-user2.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/users USER1ID=$(curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" "${KEYCLOAK_URL}/admin/realms/cnoe/users?lastName=one" | jq -r '.[0].id') USER2ID=$(curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" "${KEYCLOAK_URL}/admin/realms/cnoe/users?lastName=two" | jq -r '.[0].id') echo "setting user passwords" jq -r --arg pass ${USER1_PASSWORD} '.value = $pass' /var/config/user-password.json > /tmp/user-password-to-be-applied.json curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X PUT --data @/tmp/user-password-to-be-applied.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/users/${USER1ID}/reset-password curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X PUT --data @/tmp/user-password-to-be-applied.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/users/${USER2ID}/reset-password echo "creating Argo Workflows client" curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/argo-client-payload.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/clients CLIENT_ID=$(curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X GET ${KEYCLOAK_URL}/admin/realms/cnoe/clients | jq -e -r '.[] | select(.clientId == "argo-workflows") | .id') CLIENT_SCOPE_GROUPS_ID=$(curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X GET ${KEYCLOAK_URL}/admin/realms/cnoe/client-scopes | jq -e -r '.[] | select(.name == "groups") | .id') curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X PUT ${KEYCLOAK_URL}/admin/realms/cnoe/clients/${CLIENT_ID}/default-client-scopes/${CLIENT_SCOPE_GROUPS_ID} ARGO_WORKFLOWS_CLIENT_SECRET=$(curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X GET ${KEYCLOAK_URL}/admin/realms/cnoe/clients/${CLIENT_ID} | jq -e -r '.secret') echo "creating Backstage client" curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X POST --data @/var/config/backstage-client-payload.json \ ${KEYCLOAK_URL}/admin/realms/cnoe/clients CLIENT_ID=$(curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X GET ${KEYCLOAK_URL}/admin/realms/cnoe/clients | jq -e -r '.[] | select(.clientId == "backstage") | .id') CLIENT_SCOPE_GROUPS_ID=$(curl -sS -H "Content-Type: application/json" -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -X GET ${KEYCLOAK_URL}/admin/realms/cnoe/client-scopes | jq -e -r '.[] | select(.name == "groups") | .id') curl -sS -H "Content-Type: application/json" -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -X PUT ${KEYCLOAK_URL}/admin/realms/cnoe/clients/${CLIENT_ID}/default-client-scopes/${CLIENT_SCOPE_GROUPS_ID} BACKSTAGE_CLIENT_SECRET=$(curl -sS -H "Content-Type: application/json" \ -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ -X GET ${KEYCLOAK_URL}/admin/realms/cnoe/clients/${CLIENT_ID} | jq -e -r '.secret') ARGOCD_PASSWORD=$(./kubectl -n argocd get secret argocd-initial-admin-secret -o go-template='{{.data.password | base64decode }}') ARGOCD_SESSION_TOKEN=$(curl -k -sS http://argocd-server.argocd.svc.cluster.local:443/api/v1/session -H 'Content-Type: application/json' -d "{\"username\":\"admin\",\"password\":\"${ARGOCD_PASSWORD}\"}" | jq -r .token) echo \ "apiVersion: v1 kind: Secret metadata: name: keycloak-clients namespace: keycloak type: Opaque stringData: ARGO_WORKFLOWS_CLIENT_SECRET: ${ARGO_WORKFLOWS_CLIENT_SECRET} ARGO_WORKFLOWS_CLIENT_ID: argo-workflows ARGOCD_SESSION_TOKEN: ${ARGOCD_SESSION_TOKEN} BACKSTAGE_CLIENT_SECRET: ${BACKSTAGE_CLIENT_SECRET} BACKSTAGE_CLIENT_ID: backstage " > /tmp/secret.yaml ./kubectl apply -f /tmp/secret.yaml