This is the Stable version of the documentation. The latest version is experimental and under active development. You can use the version selector in the top-right menu to switch versions for this specific page, or click here to go to the latest 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:
Terminal window # Global & Tenant Domains — shown for reference only.# Do NOT re-export them here: `idp.` is only the default hostname and re-exporting would# overwrite whatever your site actually uses (for example `opstella-idp.example.com`).# BASE_DOMAIN, KEYCLOAK_DOMAIN, GITLAB_DOMAIN, HARBOR_DOMAIN, SONARQUBE_DOMAIN,# DEFECTDOJO_DOMAIN, VAULT_DOMAIN, GRAFANA_DASHBOARD_DOMAIN, OPSTELLA_UI_DOMAIN,# ARGOCD_NONPROD_DOMAIN, ARGOCD_PROD_DOMAIN# Core Keycloak Variables# KEYCLOAK_REALM, KEYCLOAK_REALM_ADMIN_EMAILThen make sure you have loaded global variables:
Terminal window source $HOME/opstella-installation/shell-values/global.vars.shNew Configuration Variables
Section titled “New Configuration Variables”Export these additional variables specific to the realm and admin setup:
Terminal window # Realm Display Informationexport KEYCLOAK_DISPLAY_NAME="Opstella Platform"# Realm Opstella Groupexport KEYCLOAK_REALM_OPSTELLA_GROUP="opstella-group"export KEYCLOAK_REALM_OPSTELLA_GROUP_PATH="opstella-group"export KEYCLOAK_REALM_ADMIN_PASSWORD="CHANGEME"# Core Keycloak Admin (Set during installation)export KEYCLOAK_ADMIN_PASSWORD="CHANGEME" -
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 '[$][{]' $HOME/opstella-installation/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 "$HOME/opstella-installation/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=$HOME/opstella-installation/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 $HOME/opstella-installation/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