ข้ามไปยังเนื้อหา

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.

  1. Export Required Shell Variables

    There are two sets of variables required for this configuration.

    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_EMAIL

    Then make sure you have loaded global variables:

    Terminal window
    source $HOME/opstella-installation/shell-values/global.vars.sh

    Export these additional variables specific to the realm and admin setup:

    Terminal window
    # Realm Display Information
    export KEYCLOAK_DISPLAY_NAME="Opstella Platform"
    # Realm Opstella Group
    export 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"
  2. 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-06
    realm: "${KEYCLOAK_REALM}"
    displayName: "${KEYCLOAK_DISPLAY_NAME}"
    displayNameHtml: <div class="kc-logo-text"><span>${KEYCLOAK_DISPLAY_NAME}</span></div>
    loginTheme: opstella-v5
    enabled: true
    # --- GROUPS (Fully Managed) ---
    # Supported: "import.managed.group"
    groups:
    - name: "${KEYCLOAK_REALM_OPSTELLA_GROUP}" # Lower Alpha
    path: "/${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: true
    emailVerified: true
    firstName: Opstella
    lastName: Administrator
    # Assign to Group
    groups:
    - "/${KEYCLOAK_REALM_OPSTELLA_GROUP_PATH}"
    # Credentials
    credentials:
    - type: password
    value: "${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-nonprod
    name: ArgoCD Nonprod
    description: "ArgoCD Nonprod Application"
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    rootUrl: "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    # 2. ArgoCD Prod
    - clientId: argocd-prod
    name: ArgoCD Prod
    description: "ArgoCD Prod Application"
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    rootUrl: "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    # 2. DefectDojo
    - clientId: defectdojo
    name: DefectDojo
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: false
    rootUrl: "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: audience
    protocol: openid-connect
    protocolMapper: oidc-audience-mapper
    config:
    "included.client.audience": "defectdojo"
    "id.token.claim": "true"
    "access.token.claim": "true"
    # 3. SonarQube
    - clientId: sonarqube
    name: SonarQube
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    rootUrl: "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    # 4. GitLab
    - clientId: gitlab
    name: GitLab
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    rootUrl: "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    # 5. Harbor
    - clientId: harbor
    name: Harbor
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    rootUrl: "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    # 6. Grafana
    - clientId: grafana
    name: Grafana
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    rootUrl: "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    # 7. Kubernetes API
    - clientId: kubernetes
    name: Kubernetes API
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    redirectUris:
    - "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    # 8. Vault
    - clientId: vault
    name: HashiCorp Vault
    enabled: true
    clientAuthenticatorType: client-secret
    publicClient: false
    serviceAccountsEnabled: true
    authorizationServicesEnabled: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    rootUrl: "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    # 9. Opstella
    - clientId: opstella
    name: Opstella
    enabled: true
    publicClient: true
    standardFlowEnabled: true
    directAccessGrantsEnabled: true
    fullScopeAllowed: true
    rootUrl: "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 Groups
    protocol: openid-connect
    protocolMapper: oidc-group-membership-mapper
    config:
    "full.path": "false"
    "id.token.claim": "true"
    "access.token.claim": "true"
    "userinfo.token.claim": "true"
    "claim.name": "groups"
    EOF

    Before importing, make sure envsubst left 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.

  3. 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.

    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-24

    Option 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/v1
    kind: Job
    metadata:
    name: kc-config-cli
    spec:
    backoffLimit: 0
    ttlSecondsAfterFinished: 3600
    template:
    spec:
    restartPolicy: Never
    volumes:
    - name: config
    secret:
    secretName: kc-realm-config
    containers:
    - name: config-cli
    # The image tag must match the MAJOR version of your Keycloak (24 here).
    image: adorsys/keycloak-config-cli:6.4.1-24
    volumeMounts:
    - name: config
    mountPath: /config
    readOnly: true
    env:
    # In-cluster Service - no Ingress, public DNS or TLS trust required.
    - name: KEYCLOAK_URL
    value: "http://keycloak.opstella-identity-system.svc:8080"
    - name: KEYCLOAK_USER
    value: "admin"
    - name: KEYCLOAK_PASSWORD
    valueFrom:
    secretKeyRef:
    name: keycloak-credentials
    key: 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-system
    kubectl wait --for=condition=complete job/kc-config-cli \
    --namespace opstella-identity-system --timeout=300s
    kubectl logs job/kc-config-cli --namespace opstella-identity-system | tail -20

    💡 The log ends with Import took: and the Job reports 1/1 completions.

    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