set fallback := true # Keycloak Operator info: # https://www.keycloak.org/operator/installation export KEYCLOAK_NAMESPACE := env("KEYCLOAK_NAMESPACE", "keycloak") export KEYCLOAK_OPERATOR_VERSION := env("KEYCLOAK_OPERATOR_VERSION", "26.4.5") export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "") export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "") export K8S_OIDC_CLIENT_ID := env('K8S_OIDC_CLIENT_ID', "k8s") export KEYCLOAK_ADMIN_USER := env("KEYCLOAK_ADMIN_USER", "") export KEYCLOAK_ADMIN_PASSWORD := env("KEYCLOAK_ADMIN_PASSWORD", "") export K8S_VAULT_NAMESPACE := env("K8S_VAULT_NAMESPACE", "vault") export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets") [private] default: @just --list --unsorted --list-submodules # Create Keycloak namespace create-namespace: @kubectl get namespace ${KEYCLOAK_NAMESPACE} &>/dev/null || \ kubectl create namespace ${KEYCLOAK_NAMESPACE} # Delete Keycloak namespace delete-namespace: @kubectl delete namespace ${KEYCLOAK_NAMESPACE} --ignore-not-found # Create Keycloak secret create-credentials: #!/bin/bash set -euo pipefail admin_user=$(gum input --prompt="Initial Keycloak admin username: " --width=100 --value="admin") password=$( gum input --prompt="Initial Keycloak admin password: " --password --width=100 \ --placeholder="Empty to generate a random password" ) if [ -z "${password}" ]; then password=$(just utils::random-password) fi just create-namespace if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then echo "External Secrets Operator detected. Creating ExternalSecret..." just put-admin-credentials-to-vault "${admin_user}" "${password}" kubectl delete secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found kubectl delete externalsecret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found gomplate -f keycloak-credentials-external-secret.gomplate.yaml | kubectl apply -f - echo "Waiting for ExternalSecret to sync..." kubectl wait --for=condition=Ready externalsecret/keycloak-credentials \ -n ${KEYCLOAK_NAMESPACE} --timeout=60s # Create bootstrap admin secret for Keycloak Operator (username/password format) kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found kubectl create secret generic keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} \ --from-literal=username="${admin_user}" \ --from-literal=password="${password}" else echo "External Secrets Operator not found. Creating secrets directly..." # Delete existing secrets kubectl delete secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found # Create keycloak-credentials secret (admin-user/password format for legacy scripts) kubectl create secret generic keycloak-credentials -n ${KEYCLOAK_NAMESPACE} \ --from-literal=admin-user="${admin_user}" \ --from-literal=password="${password}" # Create bootstrap admin secret for Keycloak Operator (username/password format) kubectl create secret generic keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} \ --from-literal=username="${admin_user}" \ --from-literal=password="${password}" if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then just put-admin-credentials-to-vault "${admin_user}" "${password}" fi fi # Delete Keycloak secrets delete-credentials: @kubectl delete secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found @kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found @kubectl delete externalsecret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found # Create Keycloak database secret create-database-secret: #!/bin/bash set -euo pipefail if kubectl get secret database-config -n ${KEYCLOAK_NAMESPACE} &>/dev/null; then kubectl delete secret database-config -n ${KEYCLOAK_NAMESPACE} fi kubectl create secret generic database-config -n ${KEYCLOAK_NAMESPACE} \ --from-literal=host=postgres-cluster-rw.postgres \ --from-literal=port=5432 \ --from-literal=user=$(just postgres::admin-username) \ --from-literal=password=$(just postgres::admin-password) \ --from-literal=database=keycloak # Delete Keycloak database secret delete-database-secret: @kubectl delete secret database-config -n ${KEYCLOAK_NAMESPACE} --ignore-not-found # Install Keycloak Operator install-operator: #!/bin/bash set -euo pipefail just create-namespace # Using 'baseline' instead of 'restricted' because Keycloak Operator does not meet # restricted requirements kubectl label namespace ${KEYCLOAK_NAMESPACE} \ pod-security.kubernetes.io/enforce=baseline --overwrite echo "Installing Keycloak Operator CRDs..." kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloaks.k8s.keycloak.org-v1.yml kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml echo "Installing Keycloak Operator in ${KEYCLOAK_NAMESPACE} namespace..." kubectl apply -n ${KEYCLOAK_NAMESPACE} -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/kubernetes.yml kubectl wait --for=condition=available deployment/keycloak-operator -n ${KEYCLOAK_NAMESPACE} --timeout=300s echo "Applying resource configuration based on Goldilocks/VPA recommendations..." kubectl patch deployment keycloak-operator -n ${KEYCLOAK_NAMESPACE} --type='json' -p='[ {"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "704Mi"}, {"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "1Gi"} ]' kubectl wait --for=condition=available deployment/keycloak-operator -n ${KEYCLOAK_NAMESPACE} --timeout=300s # Install Keycloak instance install: #!/bin/bash set -euo pipefail just install-operator just create-credentials just postgres::create-db keycloak just create-database-secret just create-namespace KEYCLOAK_ADMIN_USER=$(just admin-username) \ gomplate -f keycloak-cr.gomplate.yaml -o keycloak-cr.yaml kubectl apply -f keycloak-cr.yaml kubectl wait --for=condition=Ready keycloak/keycloak -n ${KEYCLOAK_NAMESPACE} --timeout=600s # Uninstall Keycloak instance uninstall delete-db='true': #!/bin/bash set -euo pipefail kubectl delete keycloak keycloak -n ${KEYCLOAK_NAMESPACE} --ignore-not-found kubectl wait --for=delete keycloak/keycloak -n ${KEYCLOAK_NAMESPACE} --timeout=300s || true just delete-namespace if [ "{{ delete-db }}" = "true" ]; then just postgres::delete-db keycloak fi # Uninstall Keycloak Operator uninstall-operator: #!/bin/bash set -euo pipefail kubectl delete -n ${KEYCLOAK_NAMESPACE} -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/kubernetes.yml --ignore-not-found kubectl delete -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml --ignore-not-found kubectl delete -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloaks.k8s.keycloak.org-v1.yml --ignore-not-found # Create Keycloak realm create-realm create-client-for-k8s='true' access_token_lifespan='43200' refresh_token_lifespan='86400' sso_session_idle_timeout='7200': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export ACCESS_TOKEN_LIFESPAN={{ access_token_lifespan }} export REFRESH_TOKEN_LIFESPAN={{ refresh_token_lifespan }} export SSO_SESSION_MAX_LIFESPAN={{ refresh_token_lifespan }} export SSO_SESSION_IDLE_TIMEOUT={{ sso_session_idle_timeout }} dotenvx run -q -f ../.env.local -- tsx ./scripts/create-realm.ts if [ "{{ create-client-for-k8s }}" = "true" ]; then just create-k8s-client fi # Create Keycloak client for Kubernetes OIDC authentication create-k8s-client: @just create-client realm=${KEYCLOAK_REALM} client_id=${K8S_OIDC_CLIENT_ID} redirect_url="http://localhost:8000,http://localhost:18000" # Delete Keycloak realm delete-realm realm: #!/bin/bash set -euo pipefail if [ -z "{{ realm }}" ]; then echo "Error: Realm name to delete must be provided as an argument." >&2 echo "Usage: just delete-realm " >&2 exit 1 fi if [ "{{ realm }}" = "master" ]; then echo "Error: Deleting the 'master' realm is not allowed via this script." >&2 exit 1 fi echo "WARNING: You are about to delete the Keycloak realm named '{{ realm }}'." echo "This action is irreversible and will remove all users, clients, and configurations within this realm." if ! gum confirm "Are you absolutely sure you want to delete realm '{{ realm }}'?"; then echo "Realm deletion cancelled." exit 0 fi export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM_TO_DELETE={{ realm }} dotenvx run -q -f ../.env.local -- tsx ./scripts/delete-realm.ts # List all Keycloak clients in realm list-clients realm: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} dotenvx run -q -f ../.env.local -- tsx ./scripts/list-clients.ts # Get detailed Keycloak client configuration get-client realm client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/get-client.ts # Check if Keycloak client exists client-exists realm client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/client-exists.ts # Create Keycloak client [positional-arguments] create-client *args: #!/bin/bash # realm: Keycloak realm name # client_id: Keycloak client ID (required) # redirect_url: Redirect URL for the client (required) # client_secret: Keycloak client secret (empty for public clients) # client_session_idle: Session idle timeout in seconds # client_session_max: Session max lifespan in seconds # client_direct_access_grants: Whether to enable direct access grants (true/false) # client_pkce_method: PKCE method ('S256', 'plain' or empty) # post_logout_redirect_uris: Post logout redirect URIs (comma-separated input, converted to Keycloak ## format) # access_token_lifespan: Access token lifespan in seconds set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) while (( "$#" )); do key="KEYCLOAK_$(echo ${1%%=*} | awk '{print toupper($0)}')" value=${1#*=} export ${key}="${value}" if [ "${KEYCLOAK_DEBUG:-}" = "true" ]; then env | grep "${key}" fi shift done dotenvx run -q -f ../.env.local -- tsx ./scripts/create-client.ts # Add audience mapper to existing client add-audience-mapper client_id audience: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_CLIENT_ID={{ client_id }} export KEYCLOAK_AUDIENCE={{ audience }} dotenvx run -q -f ../.env.local -- tsx ./scripts/add-audience-mapper.ts # Add audience mapper to client scope add-audience-mapper-to-scope realm scope_name audience: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export SCOPE_NAME={{ scope_name }} export KEYCLOAK_AUDIENCE={{ audience }} dotenvx run -q -f ../.env.local -- tsx ./scripts/add-audience-mapper-to-scope.ts # Add groups mapper to client scope add-groups-mapper-to-scope realm scope_name: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export SCOPE_NAME={{ scope_name }} dotenvx run -q -f ../.env.local -- tsx ./scripts/add-groups-mapper-to-scope.ts # Delete Keycloak client delete-client realm client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/delete-client.ts # Enable service account for Keycloak client enable-service-account realm client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/enable-service-account.ts # Add attribute mapper for Keycloak client add-attribute-mapper client_id attribute_name display_name='' claim_name='' options='' default_value='' mapper_name='' view_perms='admin,user' edit_perms='admin': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just keycloak::admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just keycloak::admin-password) export KEYCLOAK_REALM=${KEYCLOAK_REALM} export CLIENT_ID={{ client_id }} export ATTRIBUTE_NAME={{ attribute_name }} export ATTRIBUTE_DISPLAY_NAME="{{ display_name }}" export ATTRIBUTE_CLAIM_NAME="{{ claim_name }}" export ATTRIBUTE_OPTIONS="{{ options }}" export ATTRIBUTE_DEFAULT_VALUE="{{ default_value }}" export MAPPER_NAME="{{ mapper_name }}" export ATTRIBUTE_VIEW_PERMISSIONS="{{ view_perms }}" export ATTRIBUTE_EDIT_PERMISSIONS="{{ edit_perms }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/add-attribute-mapper.ts # Add client roles mapper for Keycloak client add-client-roles-mapper client_id claim_name='client_roles' mapper_name='': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just keycloak::admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just keycloak::admin-password) export KEYCLOAK_REALM=${KEYCLOAK_REALM} export CLIENT_ID={{ client_id }} export CLAIM_NAME="{{ claim_name }}" export MAPPER_NAME="{{ mapper_name }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/add-client-roles-mapper.ts # Update client roles mapper for Keycloak client (force recreation) update-client-roles-mapper client_id claim_name='client_roles' mapper_name='': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just keycloak::admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just keycloak::admin-password) export KEYCLOAK_REALM=${KEYCLOAK_REALM} export CLIENT_ID={{ client_id }} export CLAIM_NAME="{{ claim_name }}" export MAPPER_NAME="{{ mapper_name }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/update-client-roles-mapper.ts # Add Keycloak client groups mapper add-groups-mapper client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM=${KEYCLOAK_REALM} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/add-groups-mapper.ts # Create Keycloak group create-group group_name parent_group='' description='': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export GROUP_NAME="{{ group_name }}" export PARENT_GROUP_NAME="{{ parent_group }}" export GROUP_DESCRIPTION="{{ description }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/create-group.ts # Create Keycloak user create-user username='' password='' email='' first_name='' last_name='' vault_admin='false': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export USERNAME="{{ username }}" export PASSWORD="{{ password }}" while [ -z "${USERNAME}" ]; do USERNAME=$(gum input --prompt="Username: " --width=100) done while [ -z "${PASSWORD}" ]; do PASSWORD=$(gum input --prompt="Password: " --password --width=100) done export EMAIL="{{ email }}" while [ -z "${EMAIL}" ]; do EMAIL=$(gum input --prompt="Email: " --width=100) done export FIRST_NAME="{{ first_name }}" while [ -z "${FIRST_NAME}" ]; do FIRST_NAME=$(gum input --prompt="First name: " --width=100) done export LAST_NAME="{{ last_name }}" while [ -z "${LAST_NAME}" ]; do LAST_NAME=$(gum input --prompt="Last name: " --width=100) done # Ask if user should be vault admin VAULT_ADMIN="{{ vault_admin }}" if [ "${VAULT_ADMIN}" = "false" ]; then if gum confirm "Should this user have Vault admin access?"; then VAULT_ADMIN="true" fi fi # Create user dotenvx run -q -f ../.env.local -- tsx ./scripts/create-user.ts # Set up Kubernetes RBAC kubectl delete clusterrolebinding oidc-${USERNAME} --ignore-not-found kubectl create clusterrolebinding oidc-${USERNAME} --clusterrole=cluster-admin \ --user="https://${KEYCLOAK_HOST}/realms/${KEYCLOAK_REALM}#${USERNAME}" # Add to vault-admins group if requested if [ "${VAULT_ADMIN}" = "true" ]; then echo "Setting up vault-admins group..." # Create vault-admins group if it doesn't exist just create-group "vault-admins" "" "Vault administrators group" || true # Add user to vault-admins group export GROUP_NAME="vault-admins" dotenvx run -f ../.env.local -- tsx ./scripts/add-user-to-group.ts echo "✓ User '${USERNAME}' added to vault-admins group" fi # Add user to group add-user-to-group username group_name: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export USERNAME="{{ username }}" export GROUP_NAME="{{ group_name }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/add-user-to-group.ts # Remove user from group remove-user-from-group username group_name: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export USERNAME="{{ username }}" export GROUP_NAME="{{ group_name }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/delete-user-from-group.ts # Delete Keycloak group delete-group group_name: #!/bin/bash set -euo pipefail if [ -z "{{ group_name }}" ]; then echo "Error: Group name to delete must be provided as an argument." >&2 echo "Usage: just delete-group " >&2 exit 1 fi if [ "{{ group_name }}" = "vault-admins" ]; then echo "WARNING: You are about to delete the 'vault-admins' group." echo "This will remove Vault admin access for all users in this group." if ! gum confirm "Are you sure you want to delete the '{{ group_name }}' group?"; then echo "Group deletion cancelled." exit 0 fi else echo "You are about to delete the Keycloak group '{{ group_name }}'." if ! gum confirm "Are you sure you want to delete the '{{ group_name }}' group?"; then echo "Group deletion cancelled." exit 0 fi fi export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export GROUP_NAME="{{ group_name }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/delete-group.ts # Delete a user delete-user username='': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export USERNAME="{{ username }}" while [ -z "${USERNAME}" ]; do USERNAME=$(gum input --prompt="Username: " --width=100) done dotenvx run -q -f ../.env.local -- tsx ./scripts/delete-user.ts # Put admin credentials to Vault put-admin-credentials-to-vault username password: @just vault::put-root keycloak/admin username={{ username }} password={{ password }} @echo "Admin credentials stored in Vault under 'keycloak/admin'." # Delete admin credentials from Vault delete-admin-credentials-from-vault: @just vault::delete-root keycloak/admin @echo "Admin credentials deleted from Vault." # Create system user {w/o email, first name and last name} create-system-user username='' password='': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export USERNAME="{{ username }}" export PASSWORD="{{ password }}" while [ -z "${USERNAME}" ]; do USERNAME=$(gum input --prompt="Keycloak username: " --width=100) done while [ -z "${PASSWORD}" ]; do PASSWORD=$(gum input --prompt="Keycloak password: " --password --width=100) done export EMAIL="" export FIRST_NAME="" export LAST_NAME="" dotenvx run -q -f ../.env.local -- tsx ./scripts/create-user.ts kubectl delete clusterrolebinding oidc-${USERNAME} --ignore-not-found kubectl create clusterrolebinding oidc-${USERNAME} --clusterrole=cluster-admin \ --user="https://${KEYCLOAK_HOST}/realms/${KEYCLOAK_REALM}#${USERNAME}" # Check if user exists [no-exit-message] user-exists username='': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export USERNAME="{{ username }}" while [ -z "${USERNAME}" ]; do USERNAME=$(gum input --prompt="Username: " --width=100) done dotenvx run -q -f ../.env.local -- tsx ./scripts/user-exists.ts # Print Keycloak admin username admin-username: #!/bin/bash set -euo pipefail if [ -n "${KEYCLOAK_ADMIN_USER}" ]; then echo "${KEYCLOAK_ADMIN_USER}" exit 0 fi just default-admin-username # Print Keycloak admin password admin-password: #!/bin/bash set -euo pipefail if [ -n "${KEYCLOAK_ADMIN_PASSWORD}" ]; then echo "${KEYCLOAK_ADMIN_PASSWORD}" exit 0 fi just default-admin-password # Print default Keycloak admin username default-admin-username: @kubectl get secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} \ -o jsonpath="{.data.admin-user}" | base64 --decode @echo # Print default Keycloak admin password default-admin-password: @kubectl get secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} \ -o jsonpath="{.data.password}" | base64 --decode @echo # Show current realm token settings show-realm-token-settings realm: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} dotenvx run -q -f ../.env.local -- tsx ./scripts/show-realm-token-settings.ts # Update realm token settings (access token lifespan, SSO session settings) update-realm-token-settings realm access_token_lifespan='' sso_session_idle_timeout='' sso_session_max_lifespan='': #!/bin/bash set -euo pipefail export ACCESS_TOKEN_LIFESPAN={{ access_token_lifespan }} export SSO_SESSION_IDLE_TIMEOUT={{ sso_session_idle_timeout }} export SSO_SESSION_MAX_LIFESPAN={{ sso_session_max_lifespan }} while [ -z "${ACCESS_TOKEN_LIFESPAN}" ]; do ACCESS_TOKEN_LIFESPAN=$( gum input --prompt="Access token lifespan (seconds): " --width=100 \ --placeholder="e.g., 43200 for 12 hours" --value="43200" ) done while [ -z "${SSO_SESSION_IDLE_TIMEOUT}" ]; do SSO_SESSION_IDLE_TIMEOUT=$( gum input --prompt="SSO session idle timeout (seconds): " --width=100 \ --placeholder="e.g., 86400 for 1 day" --value="86400" ) done while [ -z "${SSO_SESSION_MAX_LIFESPAN}" ]; do SSO_SESSION_MAX_LIFESPAN=$( gum input --prompt="SSO session max lifespan (seconds): " --width=100 \ --placeholder="e.g., 604800 for 7 days" --value="604800" ) done export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} dotenvx run -q -f ../.env.local -- tsx ./scripts/update-realm-token-settings.ts # Create Keycloak client role create-client-role realm client_id role_name: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} export KEYCLOAK_ROLE_NAME={{ role_name }} dotenvx run -q -f ../.env.local -- tsx ./scripts/create-client-role.ts # Add user to client role add-user-to-client-role realm username client_id role_name: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export USERNAME={{ username }} export KEYCLOAK_CLIENT_ID={{ client_id }} export KEYCLOAK_ROLE_NAME={{ role_name }} dotenvx run -q -f ../.env.local -- tsx ./scripts/add-user-to-client-role.ts # List user's client roles list-user-client-roles realm username client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export USERNAME={{ username }} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/list-user-client-roles.ts # Get user token information and client configuration get-user-token-info realm username client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export USERNAME={{ username }} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/get-user-token.ts # Get client secret from Keycloak get-client-secret realm client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/get-client-secret.ts # Check detailed mapper configuration check-mapper-details realm client_id: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} dotenvx run -q -f ../.env.local -- tsx ./scripts/check-mapper-details.ts # Add client roles mapper to profile scope (generic) add-client-roles-to-profile-scope realm client_id claim_name='client_roles': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} export CLAIM_NAME="{{ claim_name }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/add-client-roles-to-profile-scope.ts # Remove user from client role remove-user-from-client-role realm username client_id role_name: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export USERNAME={{ username }} export KEYCLOAK_CLIENT_ID={{ client_id }} export KEYCLOAK_ROLE_NAME={{ role_name }} dotenvx run -q -f ../.env.local -- tsx ./scripts/remove-user-from-client-role.ts # Create client scope create-client-scope realm scope_name description='': #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export SCOPE_NAME={{ scope_name }} export DESCRIPTION="{{ description }}" dotenvx run -q -f ../.env.local -- tsx ./scripts/create-client-scope.ts # Add scope to client add-scope-to-client realm client_id scope_name: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export KEYCLOAK_CLIENT_ID={{ client_id }} export SCOPE_NAME={{ scope_name }} dotenvx run -q -f ../.env.local -- tsx ./scripts/add-scope-to-client.ts # Get client scope details get-client-scope realm scope_name: #!/bin/bash set -euo pipefail export KEYCLOAK_ADMIN_USER=$(just admin-username) export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password) export KEYCLOAK_REALM={{ realm }} export SCOPE_NAME={{ scope_name }} dotenvx run -q -f ../.env.local -- tsx ./scripts/get-client-scope.ts # Enable Prometheus monitoring enable-monitoring: #!/bin/bash set -euo pipefail echo "Enabling Prometheus monitoring for Keycloak..." # Label namespace to enable monitoring kubectl label namespace ${KEYCLOAK_NAMESPACE} buun.channel/enable-monitoring=true --overwrite # Enable metrics in Keycloak CR kubectl patch keycloak keycloak -n ${KEYCLOAK_NAMESPACE} --type=json -p '[ { "op": "add", "path": "/spec/additionalOptions/-", "value": { "name": "metrics-enabled", "value": "true" } } ]' echo "Waiting for Keycloak to restart with metrics enabled..." kubectl wait --for=condition=Ready keycloak/keycloak -n ${KEYCLOAK_NAMESPACE} --timeout=600s # Create ServiceMonitor echo "Creating ServiceMonitor..." gomplate -f keycloak-servicemonitor.gomplate.yaml | kubectl apply -f - kubectl get servicemonitor keycloak -n ${KEYCLOAK_NAMESPACE} echo "✓ Keycloak monitoring enabled" # Disable Prometheus monitoring disable-monitoring: #!/bin/bash set -euo pipefail echo "Disabling Prometheus monitoring for Keycloak..." # Delete ServiceMonitor kubectl delete servicemonitor keycloak -n ${KEYCLOAK_NAMESPACE} --ignore-not-found # Remove metrics option from Keycloak CR kubectl patch keycloak keycloak -n ${KEYCLOAK_NAMESPACE} --type=json -p '[ { "op": "remove", "path": "/spec/additionalOptions", "value": null } ]' kubectl patch keycloak keycloak -n ${KEYCLOAK_NAMESPACE} --type=merge -p '{"spec":{"additionalOptions":[ {"name":"http-enabled","value":"true"}, {"name":"hostname-strict","value":"false"}, {"name":"hostname-strict-https","value":"false"}, {"name":"proxy","value":"edge"} ]}}' # Remove namespace label kubectl label namespace ${KEYCLOAK_NAMESPACE} buun.channel/enable-monitoring- echo "✓ Keycloak monitoring disabled"