Harness Keycloak Auth Skill
Integrate Keycloak OIDC with Harness pipelines and EKS deployments.
Use For
-
Keycloak client management in pipelines, OIDC for EKS authentication
-
Realm-as-code patterns, service account provisioning
Keycloak Helm Values for EKS
charts/keycloak/values.yaml
keycloak: replicas: 2
image: repository: quay.io/keycloak/keycloak tag: "24.0.1"
command: - "/opt/keycloak/bin/kc.sh" - "start" - "--optimized"
extraEnv: | - name: KC_HOSTNAME value: "keycloak.{{ .Values.global.domain }}" - name: KC_PROXY value: "edge" - name: KC_DB value: "postgres" - name: KC_DB_URL valueFrom: secretKeyRef: name: keycloak-db key: url - name: KC_DB_USERNAME valueFrom: secretKeyRef: name: keycloak-db key: username - name: KC_DB_PASSWORD valueFrom: secretKeyRef: name: keycloak-db key: password - name: KEYCLOAK_ADMIN valueFrom: secretKeyRef: name: keycloak-admin key: username - name: KEYCLOAK_ADMIN_PASSWORD valueFrom: secretKeyRef: name: keycloak-admin key: password
ingress: enabled: true ingressClassName: nginx annotations: cert-manager.io/cluster-issuer: letsencrypt-prod rules: - host: keycloak.{{ .Values.global.domain }} paths: - path: / pathType: Prefix tls: - secretName: keycloak-tls hosts: - keycloak.{{ .Values.global.domain }}
serviceAccount: create: true annotations: eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.aws.accountId }}:role/keycloak-role
Realm-as-Code Configuration
Realm Export Template
{ "realm": "{{ .Values.keycloak.realm }}", "enabled": true, "sslRequired": "external", "registrationAllowed": false, "loginWithEmailAllowed": true, "duplicateEmailsAllowed": false, "resetPasswordAllowed": true, "editUsernameAllowed": false, "bruteForceProtected": true, "permanentLockout": false, "maxFailureWaitSeconds": 900, "minimumQuickLoginWaitSeconds": 60, "waitIncrementSeconds": 60, "quickLoginCheckMilliSeconds": 1000, "maxDeltaTimeSeconds": 43200, "failureFactor": 5, "roles": { "realm": [ { "name": "admin", "description": "Administrator" }, { "name": "user", "description": "Standard user" } ] }, "clients": [], "users": [], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", "xContentTypeOptions": "nosniff", "xRobotsTag": "none", "xFrameOptions": "SAMEORIGIN", "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", "xXSSProtection": "1; mode=block", "strictTransportSecurity": "max-age=31536000; includeSubDomains" } }
Client Configuration Template
{ "clientId": "{{ .Values.service.name }}-client", "name": "{{ .Values.service.name }} Service", "enabled": true, "clientAuthenticatorType": "client-secret", "secret": "{{ .Values.keycloak.clientSecret }}", "redirectUris": [ "https://{{ .Values.service.name }}.{{ .Values.global.domain }}/*" ], "webOrigins": [ "https://{{ .Values.service.name }}.{{ .Values.global.domain }}" ], "standardFlowEnabled": true, "implicitFlowEnabled": false, "directAccessGrantsEnabled": true, "serviceAccountsEnabled": true, "publicClient": false, "protocol": "openid-connect", "attributes": { "pkce.code.challenge.method": "S256", "access.token.lifespan": "300", "client.session.idle.timeout": "1800" }, "defaultClientScopes": [ "web-origins", "profile", "roles", "email" ], "optionalClientScopes": [ "address", "phone", "offline_access" ] }
Harness Pipeline Steps for Keycloak
Create/Update Keycloak Client
-
step: type: Run name: Configure Keycloak Client identifier: configure_keycloak spec: shell: Bash command: | # Get admin token TOKEN=$(curl -s -X POST
"${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "username=${KEYCLOAK_ADMIN}"
-d "password=${KEYCLOAK_ADMIN_PASSWORD}"
-d "grant_type=password"
-d "client_id=admin-cli" | jq -r '.access_token')# Check if client exists CLIENT_ID="<+service.name>-client" EXISTING=$(curl -s -X GET \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=${CLIENT_ID}" \ -H "Authorization: Bearer ${TOKEN}" | jq -r '.[0].id // empty') # Prepare client config cat > /tmp/client.json << 'EOF' { "clientId": "<+service.name>-client", "name": "<+service.name>", "enabled": true, "clientAuthenticatorType": "client-secret", "redirectUris": ["https://<+service.name>.<+pipeline.variables.domain>/*"], "webOrigins": ["https://<+service.name>.<+pipeline.variables.domain>"], "standardFlowEnabled": true, "serviceAccountsEnabled": true, "publicClient": false, "protocol": "openid-connect" } EOF if [ -n "$EXISTING" ]; then # Update existing client curl -s -X PUT \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${EXISTING}" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d @/tmp/client.json echo "Updated client: ${CLIENT_ID}" else # Create new client curl -s -X POST \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d @/tmp/client.json echo "Created client: ${CLIENT_ID}" fi # Get client secret CLIENT_UUID=$(curl -s -X GET \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=${CLIENT_ID}" \ -H "Authorization: Bearer ${TOKEN}" | jq -r '.[0].id') SECRET=$(curl -s -X GET \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${CLIENT_UUID}/client-secret" \ -H "Authorization: Bearer ${TOKEN}" | jq -r '.value') # Store in AWS Secrets Manager aws secretsmanager put-secret-value \ --secret-id "<+service.name>/keycloak-client-secret" \ --secret-string "${SECRET}" envVariables: KEYCLOAK_URL: <+pipeline.variables.keycloak_url> KEYCLOAK_REALM: <+pipeline.variables.keycloak_realm> KEYCLOAK_ADMIN: <+secrets.getValue("keycloak_admin_user")> KEYCLOAK_ADMIN_PASSWORD: <+secrets.getValue("keycloak_admin_password")>
Realm Import Step
-
step: type: Run name: Import Keycloak Realm identifier: import_realm spec: shell: Bash command: | # Get admin token TOKEN=$(curl -s -X POST
"${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "username=${KEYCLOAK_ADMIN}"
-d "password=${KEYCLOAK_ADMIN_PASSWORD}"
-d "grant_type=password"
-d "client_id=admin-cli" | jq -r '.access_token')# Import realm from repo curl -s -X POST \ "${KEYCLOAK_URL}/admin/realms" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d @keycloak/realm-export.json echo "Realm imported successfully"
EKS OIDC Integration with Keycloak
IRSA for Keycloak-Authenticated Pods
ServiceAccount with Keycloak token injection
apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Values.service.name }} annotations: eks.amazonaws.com/role-arn: arn:aws:iam::{{ .Values.aws.accountId }}:role/{{ .Values.service.name }}-role
apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.service.name }} spec: template: spec: serviceAccountName: {{ .Values.service.name }} containers: - name: app env: - name: KEYCLOAK_URL value: "https://keycloak.{{ .Values.global.domain }}" - name: KEYCLOAK_REALM value: "{{ .Values.keycloak.realm }}" - name: KEYCLOAK_CLIENT_ID value: "{{ .Values.service.name }}-client" - name: KEYCLOAK_CLIENT_SECRET valueFrom: secretKeyRef: name: {{ .Values.service.name }}-keycloak key: client-secret
External Secrets for Keycloak Credentials
apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: {{ .Values.service.name }}-keycloak spec: refreshInterval: 1h secretStoreRef: name: aws-secrets-manager kind: ClusterSecretStore target: name: {{ .Values.service.name }}-keycloak creationPolicy: Owner data: - secretKey: client-secret remoteRef: key: {{ .Values.service.name }}/keycloak-client-secret
Helm Values for Keycloak-Enabled Service
values.yaml for a service with Keycloak auth
service: name: api-gateway
keycloak: enabled: true realm: production clientId: api-gateway-client
Client secret fetched from AWS Secrets Manager
clientSecretRef: name: api-gateway-keycloak key: client-secret
OIDC configuration
oidc: issuerUri: https://keycloak.example.com/realms/production jwksUri: https://keycloak.example.com/realms/production/protocol/openid-connect/certs tokenEndpoint: https://keycloak.example.com/realms/production/protocol/openid-connect/token authorizationEndpoint: https://keycloak.example.com/realms/production/protocol/openid-connect/auth userInfoEndpoint: https://keycloak.example.com/realms/production/protocol/openid-connect/userinfo
Role mappings
roles: admin: ROLE_ADMIN user: ROLE_USER
Pipeline Variables for Keycloak
pipeline: variables: - name: keycloak_url type: String default: "https://keycloak.example.com" description: "Keycloak server URL" - name: keycloak_realm type: String default: "production" description: "Keycloak realm name" - name: domain type: String default: "example.com" description: "Base domain for services"
Verification Steps
-
step: type: Run name: Verify Keycloak Integration identifier: verify_keycloak spec: shell: Bash command: | # Test token endpoint RESPONSE=$(curl -s -o /dev/null -w "%{http_code}"
"${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/.well-known/openid-configuration")if [ "$RESPONSE" != "200" ]; then echo "Keycloak realm not accessible" exit 1 fi # Test client authentication TOKEN_RESPONSE=$(curl -s -X POST \ "${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=${CLIENT_ID}" \ -d "client_secret=${CLIENT_SECRET}" \ -d "grant_type=client_credentials") if echo "$TOKEN_RESPONSE" | jq -e '.access_token' > /dev/null; then echo "Keycloak client authentication successful" else echo "Keycloak client authentication failed" exit 1 fi
Troubleshooting
Issue Solution
Token endpoint unreachable Check Keycloak ingress, verify realm exists
Invalid client credentials Regenerate client secret, update secrets
CORS errors Configure web origins in client settings
Token validation failed Check JWKS endpoint, verify issuer URI
Realm import failed Validate JSON syntax, check for conflicts
References
-
Keycloak Admin REST API
-
EKS OIDC Provider