This is the Experimental version (Latest). It is under active development and may change. For the most reliable documentation, use the version selector in the top-right to switch to Stable, or click here to go to the Stable version's homepage.
Keycloak Realm Configuration
อัพเดทล่าสุด:
เนื้อหานี้ยังไม่มีในภาษาของคุณ
After installing Keycloak, you need to configure the Opstella realm, groups, users, and clients (ArgoCD, GitLab, etc.). This is performed using the keycloak-config-cli tool.
Configuration
Section titled “Configuration”-
Export Required Shell Variables
There are two sets of variables required for this configuration.
Previously Set Variables
Section titled “Previously Set Variables”Ensure you have defined and loaded your Global Shell Variables as described in Shell Variables.
The following variables should already be defined if you followed the Shell Variables guide:
(BASE_DOMAIN, KEYCLOAK_DOMAIN, ARGOCD_DOMAIN, GITLAB_DOMAIN, HARBOR_DOMAIN, KEYCLOAK_REALM)(Ensure your realm variables like
KEYCLOAK_DISPLAY_NAME,KEYCLOAK_REALM_OPSTELLA_GROUP,KEYCLOAK_REALM_OPSTELLA_GROUP_PATHandKEYCLOAK_REALM_ADMIN_EMAILare loaded fromdevsecops.vars.sh) -
Prepare Realm Configuration File
The following file defines the Opstella realm structure, including all OIDC clients for the platform.
Terminal window cat <<EOF > $BASE_WORKING_DIR/kc-opstella-realm.yaml# Forced update 2026-02-06realm: "${KEYCLOAK_REALM}"displayName: "${KEYCLOAK_DISPLAY_NAME}"displayNameHtml: <div class="kc-logo-text"><span>${KEYCLOAK_DISPLAY_NAME}</span></div>loginTheme: opstella-v5enabled: true# --- GROUPS (Fully Managed) ---# Supported: "import.managed.group"groups:- name: "${KEYCLOAK_REALM_OPSTELLA_GROUP}" # Lower Alphapath: "/${KEYCLOAK_REALM_OPSTELLA_GROUP_PATH}" # Lower Alpha# --- USERS (Update Only) ---# FACT: 'Users' are NOT supported for full management/deletion.# This section will Create/Update 'admin', but will NOT delete other users.users:- username: "$KEYCLOAK_REALM_ADMIN_USERNAME"email: "${KEYCLOAK_REALM_ADMIN_EMAIL}"enabled: trueemailVerified: truefirstName: OpstellalastName: Administrator# Assign to Groupgroups:- "/${KEYCLOAK_REALM_OPSTELLA_GROUP_PATH}"# Credentialscredentials:- type: passwordvalue: "${KEYCLOAK_REALM_ADMIN_PASSWORD}"temporary: false# Client Roles (Map Structure)clientRoles:realm-management:- realm-admin# --- CLIENTS (Fully Managed) ---# Supported: "import.managed.client"clients:# 1. ArgoCD Nonprod- clientId: argocd-nonprodname: ArgoCD Nonproddescription: "ArgoCD Nonprod Application"enabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: truerootUrl: "https://${ARGOCD_NONPROD_DOMAIN}"baseUrl: "https://${ARGOCD_NONPROD_DOMAIN}"adminUrl: "https://${ARGOCD_NONPROD_DOMAIN}"redirectUris:- "https://${ARGOCD_NONPROD_DOMAIN}/auth/callback"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "true""exclude.issuer.from.auth.response": "false"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"# 2. ArgoCD Prod- clientId: argocd-prodname: ArgoCD Proddescription: "ArgoCD Prod Application"enabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: truerootUrl: "https://${ARGOCD_PROD_DOMAIN}"baseUrl: "https://${ARGOCD_PROD_DOMAIN}"adminUrl: "https://${ARGOCD_PROD_DOMAIN}"redirectUris:- "https://${ARGOCD_PROD_DOMAIN}/auth/callback"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "true""exclude.issuer.from.auth.response": "false"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"# 2. DefectDojo- clientId: defectdojoname: DefectDojoenabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: falserootUrl: "https://${DEFECTDOJO_DOMAIN}"baseUrl: "https://${DEFECTDOJO_DOMAIN}"adminUrl: "https://${DEFECTDOJO_DOMAIN}"redirectUris:- "https://${DEFECTDOJO_DOMAIN}/*"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "false""exclude.issuer.from.auth.response": "false"protocolMappers:- name: audienceprotocol: openid-connectprotocolMapper: oidc-audience-mapperconfig:"included.client.audience": "defectdojo""id.token.claim": "true""access.token.claim": "true"# 3. SonarQube- clientId: sonarqubename: SonarQubeenabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: truerootUrl: "https://${SONARQUBE_DOMAIN}"baseUrl: "https://${SONARQUBE_DOMAIN}"adminUrl: "https://${SONARQUBE_DOMAIN}"redirectUris:- "https://${SONARQUBE_DOMAIN}/*"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "false""exclude.issuer.from.auth.response": "false"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"# 4. GitLab- clientId: gitlabname: GitLabenabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: truerootUrl: "https://${GITLAB_DOMAIN}"baseUrl: "https://${GITLAB_DOMAIN}"adminUrl: "https://${GITLAB_DOMAIN}"redirectUris:- "https://${GITLAB_DOMAIN}/*"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "false""exclude.issuer.from.auth.response": "false"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"# 5. Harbor- clientId: harborname: Harborenabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: truerootUrl: "https://${HARBOR_DOMAIN}"baseUrl: "https://${HARBOR_DOMAIN}"adminUrl: "https://${HARBOR_DOMAIN}"redirectUris:- "https://${HARBOR_DOMAIN}/*"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "true""exclude.issuer.from.auth.response": "false"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"# 6. Grafana- clientId: grafananame: Grafanaenabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: truerootUrl: "https://${GRAFANA_DASHBOARD_DOMAIN}"baseUrl: "https://${GRAFANA_DASHBOARD_DOMAIN}"adminUrl: "https://${GRAFANA_DASHBOARD_DOMAIN}"redirectUris:- "https://${GRAFANA_DASHBOARD_DOMAIN}/*"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "false""exclude.issuer.from.auth.response": "false"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"# 7. Kubernetes API- clientId: kubernetesname: Kubernetes APIenabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: trueredirectUris:- "http://localhost:8000"- "http://localhost:18000"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "false""exclude.issuer.from.auth.response": "false"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"# 8. Vault- clientId: vaultname: HashiCorp Vaultenabled: trueclientAuthenticatorType: client-secretpublicClient: falseserviceAccountsEnabled: trueauthorizationServicesEnabled: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: truerootUrl: "https://${VAULT_DOMAIN}"baseUrl: "https://${VAULT_DOMAIN}"adminUrl: "https://${VAULT_DOMAIN}"redirectUris:- "https://${VAULT_DOMAIN}/*"- "https://${VAULT_DOMAIN}/ui/vault/auth/oidc/oidc/callback"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "true""exclude.issuer.from.auth.response": "false"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"# 9. Opstella- clientId: opstellaname: Opstellaenabled: truepublicClient: truestandardFlowEnabled: truedirectAccessGrantsEnabled: truefullScopeAllowed: truerootUrl: "https://${OPSTELLA_UI_DOMAIN}"baseUrl: "https://${OPSTELLA_UI_DOMAIN}"adminUrl: "https://${OPSTELLA_UI_DOMAIN}"redirectUris:- "https://${OPSTELLA_UI_DOMAIN}/*"webOrigins:- "+"attributes:"backchannel.logout.session.required": "true""backchannel.logout.revoke.offline.tokens": "false""exclude.issuer.from.auth.response": "true"protocolMappers:- name: Opstella Groupsprotocol: openid-connectprotocolMapper: oidc-group-membership-mapperconfig:"full.path": "false""id.token.claim": "true""access.token.claim": "true""userinfo.token.claim": "true""claim.name": "groups"EOFBefore importing, make sure
envsubstleft no placeholder behind — an undefined variable is replaced with an empty string and the import will still succeed:Terminal window grep -c '[$][{]' $BASE_WORKING_DIR/kc-opstella-realm.yaml💡 Must print
0. -
Apply Configuration with keycloak-config-cli
The tool connects to Keycloak and synchronizes the Realm state. Pick whichever option fits the machine you are installing from.
Option A — Run with Docker
Section titled “Option A — Run with Docker”Requires a container runtime on the install host and a reachable, trusted
https://${KEYCLOAK_DOMAIN}.Terminal window docker run --rm \-v "$BASE_WORKING_DIR/kc-opstella-realm.yaml:/config/realm-config.yaml" \--env KEYCLOAK_URL="https://${KEYCLOAK_DOMAIN}" \--env KEYCLOAK_USER="admin" \--env KEYCLOAK_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD}" \--env KEYCLOAK_AVAILABILITYCHECK_ENABLED=true \--env KEYCLOAK_AVAILABILITYCHECK_TIMEOUT=120s \--env LOGGING_LEVEL_ROOT=DEBUG \--env IMPORT_FILES_LOCATIONS="/config/realm-config.yaml" \--env IMPORT_MANAGED_CLIENT=full \--env IMPORT_MANAGED_GROUP=full \--env IMPORT_MANAGED_ROLE=no-delete \--env IMPORT_MANAGED_COMPONENT=no-delete \--env IMPORT_MANAGED_AUTHENTICATION_FLOW=no-delete \--env IMPORT_MANAGED_REQUIRED_ACTION=no-delete \--env IMPORT_MANAGED_CLIENT_SCOPE=no-delete \--env IMPORT_MANAGED_SCOPE_MAPPING=no-delete \adorsys/keycloak-config-cli:6.4.1-24Option B — Run as a Kubernetes Job (no Docker required)
Section titled “Option B — Run as a Kubernetes Job (no Docker required)”Use this when the machine you install from has no container runtime, or when the Keycloak Ingress is not reachable yet. The Job talks to the in-cluster Service, so it needs neither public DNS nor a trusted certificate.
Terminal window kubectl create secret generic kc-realm-config \--namespace opstella-identity-system \--from-file=realm-config.yaml=$BASE_WORKING_DIR/kc-opstella-realm.yaml \--dry-run=client -o yaml | kubectl apply -f -apiVersion: batch/v1kind: Jobmetadata:name: kc-config-clispec:backoffLimit: 0ttlSecondsAfterFinished: 3600template:spec:restartPolicy: Nevervolumes:- name: configsecret:secretName: kc-realm-configcontainers:- name: config-cli# The image tag must match the MAJOR version of your Keycloak (24 here).image: adorsys/keycloak-config-cli:6.4.1-24volumeMounts:- name: configmountPath: /configreadOnly: trueenv:# In-cluster Service - no Ingress, public DNS or TLS trust required.- name: KEYCLOAK_URLvalue: "http://keycloak.opstella-identity-system.svc:8080"- name: KEYCLOAK_USERvalue: "admin"- name: KEYCLOAK_PASSWORDvalueFrom:secretKeyRef:name: keycloak-credentialskey: kc-admin-password- { name: KEYCLOAK_AVAILABILITYCHECK_ENABLED, value: "true" }- { name: KEYCLOAK_AVAILABILITYCHECK_TIMEOUT, value: "120s" }- { name: IMPORT_FILES_LOCATIONS, value: "/config/realm-config.yaml" }- { name: IMPORT_MANAGED_CLIENT, value: "full" }- { name: IMPORT_MANAGED_GROUP, value: "full" }- { name: IMPORT_MANAGED_ROLE, value: "no-delete" }- { name: IMPORT_MANAGED_COMPONENT, value: "no-delete" }- { name: IMPORT_MANAGED_AUTHENTICATION_FLOW, value: "no-delete" }- { name: IMPORT_MANAGED_REQUIRED_ACTION, value: "no-delete" }- { name: IMPORT_MANAGED_CLIENT_SCOPE, value: "no-delete" }- { name: IMPORT_MANAGED_SCOPE_MAPPING, value: "no-delete" }Terminal window kubectl apply -f $BASE_WORKING_DIR/kubernetes-manifests/kc-config-cli-job.yaml \--namespace opstella-identity-systemkubectl wait --for=condition=complete job/kc-config-cli \--namespace opstella-identity-system --timeout=300skubectl logs job/kc-config-cli --namespace opstella-identity-system | tail -20💡 The log ends with
Import took:and the Job reports1/1completions.Remove the Secret once the import is done — it holds the Realm admin password:
Terminal window kubectl delete secret kc-realm-config --namespace opstella-identity-system
Finished?
Use the below navigation to proceed