feat: add keycloak and postgres
This commit is contained in:
1
keycloak/.gitignore
vendored
Normal file
1
keycloak/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
keycloak-values.yaml
|
||||
408
keycloak/justfile
Normal file
408
keycloak/justfile
Normal file
@@ -0,0 +1,408 @@
|
||||
set fallback := true
|
||||
|
||||
export KEYCLOAK_NAMESPACE := env("KEYCLOAK_NAMESPACE", "keycloak")
|
||||
export KEYCLOAK_CHART_VERSION := env("KEYCLOAK_CHART_VERSION", "25.0.2")
|
||||
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 VAULT_ENABLED := env("VAULT_ENABLED", "true")
|
||||
|
||||
[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 kubectl get secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} &>/dev/null; then
|
||||
kubectl delete --ignore-not-found secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE}
|
||||
fi
|
||||
kubectl create secret generic keycloak-credentials -n ${KEYCLOAK_NAMESPACE} \
|
||||
--from-literal=admin-user="${admin_user}" \
|
||||
--from-literal=password="${password}"
|
||||
|
||||
if [ "${VAULT_ENABLED}" != "false" ]; then
|
||||
just put-admin-credentials-to-vault "${admin_user}" "${password}"
|
||||
fi
|
||||
|
||||
# Delete Keycloak secret
|
||||
delete-credentials:
|
||||
@kubectl delete secret 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
|
||||
install:
|
||||
#!/bin/bash
|
||||
set -euxo pipefail
|
||||
just create-credentials
|
||||
just postgres::create-db keycloak
|
||||
just create-database-secret
|
||||
KEYCLOAK_ADMIN_USER=$(just admin-username) \
|
||||
gomplate -f keycloak-values.gomplate.yaml -o keycloak-values.yaml
|
||||
helm upgrade --cleanup-on-fail --install \
|
||||
keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \
|
||||
--version ${KEYCLOAK_CHART_VERSION} -n ${KEYCLOAK_NAMESPACE} --wait \
|
||||
-f keycloak-values.yaml
|
||||
|
||||
# Uninstall Keycloak
|
||||
uninstall delete-db='true':
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
helm uninstall keycloak -n ${KEYCLOAK_NAMESPACE} --ignore-not-found --wait
|
||||
just delete-namespace
|
||||
if [ "{{ delete-db }}" = "true" ]; then
|
||||
just postgres::delete-db keycloak
|
||||
fi
|
||||
|
||||
# Create Keycloak realm
|
||||
create-realm create-client-for-k8s='true':
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
export KEYCLOAK_ADMIN_USER=$(just admin-username)
|
||||
export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password)
|
||||
dotenvx run -f ../.env.local -- tsx ./scripts/create-realm.ts
|
||||
if [ "{{ create-client-for-k8s }}" = "true" ]; then
|
||||
just create-client ${KEYCLOAK_REALM} ${K8S_OIDC_CLIENT_ID} "http://localhost:8000"
|
||||
fi
|
||||
|
||||
# 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 -f ../.env.local -- tsx ./scripts/delete-realm.ts
|
||||
|
||||
# Create Keycloak client
|
||||
create-client realm client_id redirect_url client_secret='':
|
||||
#!/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_CLIENT_SECRET={{ client_secret }}
|
||||
export KEYCLOAK_REDIRECT_URL={{ redirect_url }}
|
||||
dotenvx run -f ../.env.local -- tsx ./scripts/create-client.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 -f ../.env.local -- tsx ./scripts/delete-client.ts
|
||||
|
||||
# Add Keycloak client audience mapper
|
||||
add-audience-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 -f ../.env.local -- tsx ./scripts/add-audience-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 -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 -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 -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 -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 -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 -f ../.env.local -- tsx ./scripts/delete-user.ts
|
||||
|
||||
# Create an admin user
|
||||
# create-admin-user username='' password='':
|
||||
# #!/bin/bash
|
||||
# set -euo pipefail
|
||||
# echo "Creating a new admin user in Keycloak"
|
||||
# export KEYCLOAK_ADMIN_USER=$(just admin-user)
|
||||
# export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password)
|
||||
# export USERNAME="{{ username }}"
|
||||
# export PASSWORD="{{ password }}"
|
||||
# while [ -z "${USERNAME}" ]; do
|
||||
# USERNAME=$(gum input --prompt="Admin username: " --width=100)
|
||||
# done
|
||||
# if [ -z "${PASSWORD}" ]; then
|
||||
# PASSWORD=$(
|
||||
# gum input --prompt="Admin assword: " --password --width=100 \
|
||||
# --placeholder="Empty to generate a random password"
|
||||
# )
|
||||
# fi
|
||||
# if [ -z "${PASSWORD}" ]; then
|
||||
# PASSWORD=$(just utils::random-password)
|
||||
# fi
|
||||
# export EMAIL=""
|
||||
# export FIRST_NAME=""
|
||||
# export LAST_NAME=""
|
||||
# export CREATE_AS_ADMIN=true
|
||||
# dotenvx run -f ../.env.local -- tsx ./scripts/create-user.ts
|
||||
# if [ "${VAULT_ENABLED}" != "false" ]; then
|
||||
# just put-admin-credentials-to-vault "${USERNAME}" "${PASSWORD}"
|
||||
# fi
|
||||
|
||||
# Put admin credentials to Vault
|
||||
put-admin-credentials-to-vault username password:
|
||||
@just vault::put 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 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-user)
|
||||
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 -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
|
||||
user-exists username='':
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
export KEYCLOAK_ADMIN_USER=$(just admin-user)
|
||||
export KEYCLOAK_ADMIN_PASSWORD=$(just admin-password)
|
||||
export USERNAME="{{ username }}"
|
||||
while [ -z "${USERNAME}" ]; do
|
||||
USERNAME=$(gum input --prompt="Username: " --width=100)
|
||||
done
|
||||
dotenvx run -f ../.env.local -- tsx ./scripts/user-exists.ts
|
||||
|
||||
# Print Keycloak admin username
|
||||
admin-username:
|
||||
#!/bin/bash
|
||||
set -euxo pipefail
|
||||
if [ -n "${KEYCLOAK_ADMIN_USER}" ]; then
|
||||
echo "${KEYCLOAK_ADMIN_USER}"
|
||||
exit 0
|
||||
fi
|
||||
if [ "${VAULT_ENABLED}" != "false" ]; then
|
||||
just vault::setup-env
|
||||
if just vault::exist keycloak/admin; then
|
||||
just vault::get keycloak/admin username
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
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
|
||||
if [ "${VAULT_ENABLED}" != "false" ]; then
|
||||
just vault::setup-env
|
||||
if just vault::exist keycloak/admin; then
|
||||
just vault::get keycloak/admin password
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
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
|
||||
43
keycloak/keycloak-values.gomplate.yaml
Normal file
43
keycloak/keycloak-values.gomplate.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
auth:
|
||||
adminUser: {{ .Env.KEYCLOAK_ADMIN_USER }}
|
||||
existingSecret: keycloak-credentials
|
||||
passwordSecretKey: password
|
||||
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
externalDatabase:
|
||||
host: postgres-cluster-rw.postgres
|
||||
port: 5432
|
||||
database: keycloak
|
||||
existingSecret: database-config
|
||||
existingSecretUserKey: user
|
||||
existingSecretPasswordKey: password
|
||||
|
||||
tls:
|
||||
enabled: true
|
||||
autoGenerated:
|
||||
enabled: true
|
||||
engine: helm
|
||||
|
||||
# Keycloak pod may not start with the default memory limits
|
||||
resources:
|
||||
limits:
|
||||
memory: 1.5Gi
|
||||
requests:
|
||||
memory: 1Gi
|
||||
|
||||
# image:
|
||||
# debug: true
|
||||
|
||||
# logging:
|
||||
# level: DEBUG
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: traefik
|
||||
hostname: {{ .Env.KEYCLOAK_HOST }}
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: traefik
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
tls: true
|
||||
70
keycloak/scripts/add-audience-mapper.ts
Normal file
70
keycloak/scripts/add-audience-mapper.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const clientId = process.env.KEYCLOAK_CLIENT_ID;
|
||||
invariant(clientId, "KEYCLOAK_CLIENT_ID environment variable is required");
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
const clients = await kcAdminClient.clients.find({ clientId });
|
||||
if (clients.length === 0) {
|
||||
throw new Error(`Client '${clientId}' not found.`);
|
||||
}
|
||||
const client = clients[0];
|
||||
invariant(client.id, "Client ID is not set");
|
||||
|
||||
const mapperName = `aud-mapper-${clientId}`;
|
||||
const audienceMapper = {
|
||||
name: mapperName,
|
||||
protocol: "openid-connect",
|
||||
protocolMapper: "oidc-audience-mapper",
|
||||
config: {
|
||||
"included.client.audience": clientId,
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
},
|
||||
};
|
||||
|
||||
const existingMappers = await kcAdminClient.clients.listProtocolMappers({ id: client.id });
|
||||
|
||||
if (existingMappers.some((mapper) => mapper.name === mapperName)) {
|
||||
console.warn(`Audience Mapper '${mapperName}' already exists for the client.`);
|
||||
} else {
|
||||
await kcAdminClient.clients.addProtocolMapper({ id: client.id }, audienceMapper);
|
||||
console.log(`Audience Mapper '${mapperName}' added directly to the client.`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("An error occurred:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
121
keycloak/scripts/add-minio-policy.ts
Normal file
121
keycloak/scripts/add-minio-policy.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const minioClientId = process.env.MINIO_OIDC_CLIENT_ID;
|
||||
invariant(minioClientId, "MINIO_OIDC_CLIENT_ID environment variable is required");
|
||||
|
||||
const policyValue = process.env.MINIO_POLICY || "readwrite";
|
||||
console.log(`Setting minioPolicy attribute with value: ${policyValue}`);
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
// Set realm to work with
|
||||
kcAdminClient.setConfig({
|
||||
realmName,
|
||||
});
|
||||
|
||||
// Get current User Profile configuration
|
||||
const userProfile = await kcAdminClient.users.getProfile();
|
||||
|
||||
// Check if minioPolicy attribute already exists
|
||||
const existingAttribute = userProfile.attributes?.find(
|
||||
(attr: any) => attr.name === "minioPolicy"
|
||||
);
|
||||
|
||||
if (existingAttribute) {
|
||||
console.log("minioPolicy attribute already exists in User Profile.");
|
||||
} else {
|
||||
// Add minioPolicy attribute to User Profile with proper permissions
|
||||
if (!userProfile.attributes) {
|
||||
userProfile.attributes = [];
|
||||
}
|
||||
|
||||
userProfile.attributes.push({
|
||||
name: "minioPolicy",
|
||||
displayName: "MinIO Policy",
|
||||
permissions: {
|
||||
view: ["admin", "user"],
|
||||
edit: ["admin"],
|
||||
},
|
||||
validations: {
|
||||
options: { options: ["readwrite", "readonly", "writeonly"] },
|
||||
},
|
||||
});
|
||||
|
||||
// Update User Profile
|
||||
await kcAdminClient.users.updateProfile(userProfile);
|
||||
console.log(
|
||||
"minioPolicy attribute added to User Profile successfully with admin edit permissions."
|
||||
);
|
||||
}
|
||||
|
||||
// Create protocol mapper for the minioPolicy attribute if it doesn't exist
|
||||
const minioClient = await kcAdminClient.clients.find({ clientId: minioClientId });
|
||||
if (minioClient.length === 0) {
|
||||
console.error(`Client '${minioClientId}' not found.`);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
const clientId = minioClient[0].id;
|
||||
invariant(clientId, "Client ID is required");
|
||||
|
||||
// Check if the mapper already exists
|
||||
const mappers = await kcAdminClient.clients.listProtocolMappers({ id: clientId });
|
||||
const existingMapper = mappers.find((mapper) => mapper.name === "MinIO Policy");
|
||||
|
||||
if (existingMapper) {
|
||||
console.log("MinIO Policy mapper already exists.");
|
||||
} else {
|
||||
// Create the protocol mapper
|
||||
await kcAdminClient.clients.addProtocolMapper(
|
||||
{ id: clientId },
|
||||
{
|
||||
name: "MinIO Policy",
|
||||
protocol: "openid-connect",
|
||||
protocolMapper: "oidc-usermodel-attribute-mapper",
|
||||
config: {
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "minioPolicy",
|
||||
"jsonType.label": "String",
|
||||
"user.attribute": "minioPolicy",
|
||||
multivalued: "false",
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("MinIO Policy mapper created successfully.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("An error occurred:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
76
keycloak/scripts/add-user-to-group.ts
Normal file
76
keycloak/scripts/add-user-to-group.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const username = process.env.USERNAME;
|
||||
invariant(username, "USERNAME environment variable is required");
|
||||
|
||||
const groupName = process.env.GROUP_NAME;
|
||||
invariant(groupName, "GROUP_NAME environment variable is required");
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
// Find user
|
||||
const users = await kcAdminClient.users.find({ username });
|
||||
const user = users.find(u => u.username === username);
|
||||
if (!user) {
|
||||
throw new Error(`User '${username}' not found`);
|
||||
}
|
||||
|
||||
// Find group
|
||||
const groups = await kcAdminClient.groups.find({ search: groupName });
|
||||
const group = groups.find(g => g.name === groupName);
|
||||
if (!group) {
|
||||
throw new Error(`Group '${groupName}' not found`);
|
||||
}
|
||||
|
||||
// Check if user is already in group
|
||||
const userGroups = await kcAdminClient.users.listGroups({ id: user.id! });
|
||||
const isAlreadyMember = userGroups.some(ug => ug.id === group.id);
|
||||
|
||||
if (isAlreadyMember) {
|
||||
console.log(`User '${username}' is already a member of group '${groupName}'`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add user to group
|
||||
await kcAdminClient.users.addToGroup({
|
||||
id: user.id!,
|
||||
groupId: group.id!,
|
||||
});
|
||||
|
||||
console.log(`User '${username}' added to group '${groupName}' successfully`);
|
||||
} catch (error) {
|
||||
console.error("Error adding user to group:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
64
keycloak/scripts/create-client.ts
Normal file
64
keycloak/scripts/create-client.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const clientId = process.env.KEYCLOAK_CLIENT_ID;
|
||||
invariant(clientId, "KEYCLOAK_CLIENT_ID environment variable is required");
|
||||
|
||||
const clientSecret = process.env.KEYCLOAK_CLIENT_SECRET;
|
||||
|
||||
const redirectUrl = process.env.KEYCLOAK_REDIRECT_URL;
|
||||
invariant(redirectUrl, "KEYCLOAK_REDIRECT_URL environment variable is required");
|
||||
|
||||
const redirectUris = redirectUrl.split(',').map(url => url.trim());
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
const existingClients = await kcAdminClient.clients.find({ clientId });
|
||||
if (existingClients.length > 0) {
|
||||
console.warn(`Client '${clientId}' already exists.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const createdClient = await kcAdminClient.clients.create({
|
||||
clientId: clientId,
|
||||
secret: clientSecret,
|
||||
enabled: true,
|
||||
redirectUris: redirectUris,
|
||||
publicClient: clientSecret && clientSecret !== '' ? false : true,
|
||||
});
|
||||
console.log(`Client created successfully with ID: ${createdClient.id}`);
|
||||
} catch (error) {
|
||||
console.error("An error occurred:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
82
keycloak/scripts/create-group.ts
Normal file
82
keycloak/scripts/create-group.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const groupName = process.env.GROUP_NAME;
|
||||
invariant(groupName, "GROUP_NAME environment variable is required");
|
||||
|
||||
const parentGroupName = process.env.PARENT_GROUP_NAME || "";
|
||||
const groupDescription = process.env.GROUP_DESCRIPTION || "";
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
// Check if group already exists
|
||||
const existingGroups = await kcAdminClient.groups.find({ search: groupName });
|
||||
const existingGroup = existingGroups.find(group => group.name === groupName);
|
||||
|
||||
if (existingGroup) {
|
||||
console.log(`Group '${groupName}' already exists with ID: ${existingGroup.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find parent group if specified
|
||||
let parentGroupId: string | undefined;
|
||||
if (parentGroupName) {
|
||||
const parentGroups = await kcAdminClient.groups.find({ search: parentGroupName });
|
||||
const parentGroup = parentGroups.find(group => group.name === parentGroupName);
|
||||
if (!parentGroup) {
|
||||
throw new Error(`Parent group '${parentGroupName}' not found`);
|
||||
}
|
||||
parentGroupId = parentGroup.id;
|
||||
}
|
||||
|
||||
// Create group payload
|
||||
const groupPayload = {
|
||||
name: groupName,
|
||||
...(groupDescription && { attributes: { description: [groupDescription] } }),
|
||||
};
|
||||
|
||||
// Create group
|
||||
const group = parentGroupId
|
||||
? await kcAdminClient.groups.createChildGroup({ id: parentGroupId }, groupPayload)
|
||||
: await kcAdminClient.groups.create(groupPayload);
|
||||
|
||||
console.log(`Group '${groupName}' created successfully with ID: ${group.id}`);
|
||||
|
||||
if (parentGroupName) {
|
||||
console.log(`Group '${groupName}' created as child of '${parentGroupName}'`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating group:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
50
keycloak/scripts/create-realm.ts
Normal file
50
keycloak/scripts/create-realm.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
const existingRealms = await kcAdminClient.realms.find();
|
||||
const realmExists = existingRealms.some((realm) => realm.realm === realmName);
|
||||
if (realmExists) {
|
||||
console.warn(`Realm '${realmName}' already exists.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await kcAdminClient.realms.create({
|
||||
realm: realmName,
|
||||
enabled: true,
|
||||
});
|
||||
console.log(`Realm '${realmName}' created successfully.`);
|
||||
} catch (error) {
|
||||
console.error("An error occurred:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
99
keycloak/scripts/create-user.ts
Normal file
99
keycloak/scripts/create-user.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const username = process.env.USERNAME;
|
||||
invariant(username, "USERNAME environment variable is required");
|
||||
|
||||
const email = process.env.EMAIL;
|
||||
const firstName = process.env.FIRST_NAME;
|
||||
const lastName = process.env.LAST_NAME;
|
||||
|
||||
const password = process.env.PASSWORD;
|
||||
invariant(password, "PASSWORD environment variable is required");
|
||||
|
||||
const createAsAdmin = process.env.CREATE_AS_ADMIN === "true";
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
const userPayload = {
|
||||
username,
|
||||
email,
|
||||
emailVerified: true,
|
||||
firstName,
|
||||
lastName,
|
||||
enabled: true,
|
||||
};
|
||||
const user = await kcAdminClient.users.create(userPayload);
|
||||
console.log(`User created successfully with ID: ${user.id}`);
|
||||
|
||||
if (createAsAdmin && realmName === "master") {
|
||||
const adminRole = await kcAdminClient.roles.findOneByName({
|
||||
realm: "master",
|
||||
name: "admin",
|
||||
});
|
||||
|
||||
const createRealmRole = await kcAdminClient.roles.findOneByName({
|
||||
realm: "master",
|
||||
name: "create-realm",
|
||||
});
|
||||
|
||||
await kcAdminClient.users.addRealmRoleMappings({
|
||||
realm: "master",
|
||||
id: user.id,
|
||||
roles: [
|
||||
{
|
||||
id: adminRole!.id!,
|
||||
name: adminRole!.name!,
|
||||
},
|
||||
{
|
||||
id: createRealmRole!.id!,
|
||||
name: createRealmRole!.name!,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
await kcAdminClient.users.resetPassword({
|
||||
id: user.id!,
|
||||
credential: {
|
||||
type: "password",
|
||||
value: password,
|
||||
temporary: false,
|
||||
},
|
||||
});
|
||||
console.log(`Password set for user '${user.id}'.`);
|
||||
} catch (error) {
|
||||
console.error("Error creating user:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
54
keycloak/scripts/delete-client.ts
Normal file
54
keycloak/scripts/delete-client.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const clientId = process.env.KEYCLOAK_CLIENT_ID;
|
||||
invariant(clientId, "KEYCLOAK_CLIENT_ID environment variable is required");
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: 'master',
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
const existingClients = await kcAdminClient.clients.find({ clientId });
|
||||
if (existingClients.length === 0) {
|
||||
console.warn(`Client '${clientId}' does not exist.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const client = existingClients[0];
|
||||
invariant(client.id, "Client ID is not set");
|
||||
|
||||
await kcAdminClient.clients.del({ id: client.id });
|
||||
console.log(`Client '${clientId}' successfully deleted.`);
|
||||
} catch (error) {
|
||||
console.error("An error occurred:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
76
keycloak/scripts/delete-group.ts
Normal file
76
keycloak/scripts/delete-group.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const groupName = process.env.GROUP_NAME;
|
||||
invariant(groupName, "GROUP_NAME environment variable is required");
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
// Find group to delete
|
||||
const groups = await kcAdminClient.groups.find({ search: groupName });
|
||||
const group = groups.find(g => g.name === groupName);
|
||||
|
||||
if (!group) {
|
||||
console.log(`Group '${groupName}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if group has members
|
||||
const groupMembers = await kcAdminClient.groups.listMembers({ id: group.id! });
|
||||
if (groupMembers.length > 0) {
|
||||
console.log(`Warning: Group '${groupName}' has ${groupMembers.length} members:`);
|
||||
groupMembers.forEach(member => {
|
||||
console.log(` - ${member.username} (${member.firstName} ${member.lastName})`);
|
||||
});
|
||||
console.log("All members will be removed from the group when it's deleted.");
|
||||
}
|
||||
|
||||
// Check for subgroups
|
||||
const subGroups = await kcAdminClient.groups.listSubGroups({ id: group.id! });
|
||||
if (subGroups.length > 0) {
|
||||
console.log(`Warning: Group '${groupName}' has ${subGroups.length} subgroups:`);
|
||||
subGroups.forEach(subGroup => {
|
||||
console.log(` - ${subGroup.name}`);
|
||||
});
|
||||
console.log("All subgroups will be deleted as well.");
|
||||
}
|
||||
|
||||
// Delete group
|
||||
await kcAdminClient.groups.del({ id: group.id! });
|
||||
|
||||
console.log(`Group '${groupName}' deleted successfully`);
|
||||
} catch (error) {
|
||||
console.error("Error deleting group:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
61
keycloak/scripts/delete-realm.ts
Normal file
61
keycloak/scripts/delete-realm.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmNameToDelete = process.env.KEYCLOAK_REALM_TO_DELETE;
|
||||
invariant(realmNameToDelete, "KEYCLOAK_REALM_TO_DELETE environment variable is required");
|
||||
|
||||
if (realmNameToDelete === "master") {
|
||||
console.error("Error: Deleting the 'master' realm is a highly destructive operation and is not allowed by this script.");
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master", // Authenticate against master realm to delete other realms
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful with master realm.");
|
||||
|
||||
// Check if realm exists before attempting deletion
|
||||
const realm = await kcAdminClient.realms.findOne({ realm: realmNameToDelete });
|
||||
|
||||
if (!realm) {
|
||||
console.warn(`Realm '${realmNameToDelete}' not found. Nothing to delete.`);
|
||||
return; // Exit gracefully if realm doesn't exist
|
||||
}
|
||||
|
||||
console.log(`Attempting to delete realm: '${realmNameToDelete}'...`);
|
||||
await kcAdminClient.realms.del({ realm: realmNameToDelete });
|
||||
console.log(`Realm '${realmNameToDelete}' deleted successfully.`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`An error occurred while trying to delete realm '${realmNameToDelete}':`, error);
|
||||
const err = error as any;
|
||||
if (err.response?.data) {
|
||||
console.error("Error details:", JSON.stringify(err.response.data, undefined, 2));
|
||||
}
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
|
||||
76
keycloak/scripts/delete-user-from-group.ts
Normal file
76
keycloak/scripts/delete-user-from-group.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const username = process.env.USERNAME;
|
||||
invariant(username, "USERNAME environment variable is required");
|
||||
|
||||
const groupName = process.env.GROUP_NAME;
|
||||
invariant(groupName, "GROUP_NAME environment variable is required");
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
// Find user
|
||||
const users = await kcAdminClient.users.find({ username });
|
||||
const user = users.find(u => u.username === username);
|
||||
if (!user) {
|
||||
throw new Error(`User '${username}' not found`);
|
||||
}
|
||||
|
||||
// Find group
|
||||
const groups = await kcAdminClient.groups.find({ search: groupName });
|
||||
const group = groups.find(g => g.name === groupName);
|
||||
if (!group) {
|
||||
throw new Error(`Group '${groupName}' not found`);
|
||||
}
|
||||
|
||||
// Check if user is in group
|
||||
const userGroups = await kcAdminClient.users.listGroups({ id: user.id! });
|
||||
const isMember = userGroups.some(ug => ug.id === group.id);
|
||||
|
||||
if (!isMember) {
|
||||
console.log(`User '${username}' is not a member of group '${groupName}'`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove user from group
|
||||
await kcAdminClient.users.delFromGroup({
|
||||
id: user.id!,
|
||||
groupId: group.id!,
|
||||
});
|
||||
|
||||
console.log(`User '${username}' removed from group '${groupName}' successfully`);
|
||||
} catch (error) {
|
||||
console.error("Error removing user from group:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
60
keycloak/scripts/delete-user.ts
Normal file
60
keycloak/scripts/delete-user.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const usernameToDelete = process.env.USERNAME;
|
||||
invariant(
|
||||
usernameToDelete,
|
||||
"USERNAME environment variable (for the user to be deleted) is required"
|
||||
);
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
const users = await kcAdminClient.users.find({ username: usernameToDelete });
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn(`User '${usernameToDelete}' not found in realm '${realmName}'.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
invariant(user.id, `User ID not found for user '${usernameToDelete}'.`);
|
||||
|
||||
await kcAdminClient.users.del({ id: user.id });
|
||||
console.log(
|
||||
`User '${usernameToDelete}' (ID: ${user.id}) successfully deleted from realm '${realmName}'.`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("An error occurred during user deletion:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
57
keycloak/scripts/user-exists.ts
Normal file
57
keycloak/scripts/user-exists.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import KcAdminClient from "@keycloak/keycloak-admin-client";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const main = async () => {
|
||||
const keycloakHost = process.env.KEYCLOAK_HOST;
|
||||
invariant(keycloakHost, "KEYCLOAK_HOST environment variable is required.");
|
||||
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USER;
|
||||
invariant(adminUsername, "KEYCLOAK_ADMIN_USER environment variable is required.");
|
||||
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
invariant(adminPassword, "KEYCLOAK_ADMIN_PASSWORD environment variable is required");
|
||||
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
invariant(realmName, "KEYCLOAK_REALM environment variable is required");
|
||||
|
||||
const username = process.env.USERNAME;
|
||||
invariant(username, "USERNAME environment variable is required");
|
||||
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: `https://${keycloakHost}`,
|
||||
realmName: "master",
|
||||
});
|
||||
|
||||
try {
|
||||
await kcAdminClient.auth({
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: "password",
|
||||
clientId: "admin-cli",
|
||||
});
|
||||
console.log("Authentication successful.");
|
||||
|
||||
kcAdminClient.setConfig({ realmName });
|
||||
|
||||
const users = await kcAdminClient.users.find({
|
||||
username,
|
||||
exact: true,
|
||||
});
|
||||
|
||||
if (users && users.length > 0) {
|
||||
console.log(`User '${username}' exists with ID: ${users[0].id}`);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log(`User '${username}' does not exist.`);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking user existence:", error);
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user