Files
buun-stack/keycloak/justfile

781 lines
32 KiB
Makefile

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 <realm-to-delete>" >&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 <group-name>" >&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, refresh token lifespan, etc.)
update-realm-token-settings realm access_token_lifespan='' refresh_token_lifespan='':
#!/bin/bash
set -euo pipefail
export ACCESS_TOKEN_LIFESPAN={{ access_token_lifespan }}
export REFRESH_TOKEN_LIFESPAN={{ refresh_token_lifespan }}
while [ -z "${ACCESS_TOKEN_LIFESPAN}" ]; do
ACCESS_TOKEN_LIFESPAN=$(
gum input --prompt="Access token lifespan (in seconds): " --width=100 \
--placeholder="e.g., 43200 for 12 hours" --value="43200"
)
done
while [ -z "${REFRESH_TOKEN_LIFESPAN}" ]; do
REFRESH_TOKEN_LIFESPAN=$(
gum input --prompt="Refresh token lifespan (in seconds): " --width=100 \
--placeholder="e.g., 86400 for 24 hours" --value="86400"
)
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"