How to Create the Apicurio Migration Client
This guide shows you how to create the apicurio-migration Keycloak client and its migration-admin realm role for the Apicurio Registry v2 to v3 upgrade, in the Admin Console or with a script, and how to verify the client.
Type |
How-to guide |
Goal |
Give the upgrade a Keycloak client that keeps register and export access on Apicurio Registry v2 while every other client is read-only. |
Audience |
Platform Operator with admin access to the Keycloak Admin Console for the Apicurio realm. |
When to use |
Use this guide before you freeze schema registration in How to Migrate Schemas to Apicurio v3. |
Prerequisites
Confirm the following before you begin.
Access and permissions required
You need the following access and permissions:
-
Admin access to the Keycloak Admin Console for the realm set in your Apicurio values under
security.authentication.keycloak.realm. -
For the script: an admin user in the Keycloak
masterrealm who can manage that realm.
Tools and versions required
You need the following tools for the script and the verification:
-
curl>= 7.76 (the script uses--fail-with-body) -
jq
Resources that must exist before starting
The following resources must already exist before you start:
-
Apicurio Registry v2 running on Axual Streaming
2.0.0, with its Keycloak. See Upgrade Apicurio Registry from v2 to v3.
Create the client in the Admin Console
The apicurio-migration client is the only principal that keeps register and export access on v2 during the registration freeze. It holds the migration-admin role, which becomes the sole admin role on v2 while every other client, including the platform’s apicurio-api, is read-only.
Log in to the Keycloak Admin Console for the realm set in your Apicurio values under security.authentication.keycloak.realm, then follow these steps:
-
Create the
migration-adminrealm role: Navigate to Realm roles, click Create role, entermigration-adminas the role name, and save. -
Create the
apicurio-migrationclient: Navigate to Clients and click Create client. Set the Client ID toapicurio-migration, then apply these settings on the Capability config step:Setting Value Client authentication
On
Authorization
Off
Standard flow
unchecked
Implicit flow
unchecked
Direct access grants
unchecked
Service accounts roles
checked
OAuth 2.0 Device Authorization Grant
unchecked
OIDC CIBA Grant
unchecked
Save the client and copy the client secret from the Credentials tab. You need it for
MIGRATION_CLIENT_SECRETin Step 1: Set shell variables. -
Assign the role to the service account: Open the
apicurio-migrationclient, go to the Service account roles tab and click Assign role. In the filter dropdown (top-left of the dialog), switch from Filter by clients to Filter by realm roles, then selectmigration-adminand click Assign.
Create the client with a script
Instead of clicking through the Admin Console, you can run the three steps in Create the client in the Admin Console as a single script against the Keycloak Admin REST API. The script is idempotent: it skips creating the role or client when either already exists, but it always (re-)assigns the migration-admin role to the service account.
Edit the four variables in the # --- Config --- block at the top of the script before running it:
-
KC: the base Keycloak URL. Append/authif your Keycloak deployment serves its Admin REST API under that path. Older WildFly-based Keycloak distributions do this by default, as does a Quarkus-based Keycloak explicitly configured withKC_HTTP_RELATIVE_PATH=/auth. A newer Quarkus-based Keycloak (17+, default configuration) serves the API at the root and needs no/auth. The upgrade guides always reach Keycloak athttps://<APICURIO_KEYCLOAK_HOST>/auth/realms/…;(see Step 1: Set shell variables). If thosecurlcalls work unmodified against your Keycloak, setKCto the full URL of the same host with/authappended, including the scheme, for exampleKC="https://apicurio.example.com/auth". -
REALM: the realm from your Apicurio values'security.authentication.keycloak.realm, the same realm used in the manual steps. -
ADMIN_USER: the username of an admin user in themasterrealm who can manageREALM. An admin defined only inside the Apicurio realm can’t log in through this script. -
ADMIN_PASS: the password for that admin. The script exchanges it for a token with a password grant against themasterrealm’sadmin-cliclient, not a service-account token.
#!/usr/bin/env bash
set -euo pipefail
# --- Config ---
KC=""
REALM=""
ADMIN_USER=""
ADMIN_PASS=""
ROLE_NAME="migration-admin"
CLIENT_ID="apicurio-migration"
# --------------
echo "==> Getting admin token"
TOKEN=$(curl -sS --fail-with-body -X POST "$KC/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
--data-urlencode "username=$ADMIN_USER" \
--data-urlencode "password=$ADMIN_PASS" \
| jq -r '.access_token')
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
echo "ERROR: failed to obtain admin token" >&2
exit 1
fi
auth=(-H "Authorization: Bearer $TOKEN")
# --- 1. Create the realm role ---
echo "==> Creating realm role: $ROLE_NAME"
if curl -s -o /dev/null -w '%{http_code}' "${auth[@]}" \
"$KC/admin/realms/$REALM/roles/$ROLE_NAME" | grep -q '^200$'; then
echo " role already exists, skipping"
else
curl -sS --fail-with-body -X POST "$KC/admin/realms/$REALM/roles" "${auth[@]}" \
-H "Content-Type: application/json" \
-d "{\"name\": \"$ROLE_NAME\"}"
echo " created"
fi
# --- 2. Create the client ---
# UI mapping (Capability config):
# Client authentication On -> publicClient: false
# Authorization Off -> authorizationServicesEnabled: false
# Standard flow unchecked -> standardFlowEnabled: false
# Implicit flow unchecked -> implicitFlowEnabled: false
# Direct access grants unchecked -> directAccessGrantsEnabled: false
# Service accounts roles checked -> serviceAccountsEnabled: true
# OAuth2 Device Grant unchecked -> attr oauth2.device.authorization.grant.enabled: false
# OIDC CIBA Grant unchecked -> attr oidc.ciba.grant.enabled: false
echo "==> Creating client: $CLIENT_ID"
INTERNAL_ID=$(curl -sS --fail-with-body "${auth[@]}" \
"$KC/admin/realms/$REALM/clients?clientId=$CLIENT_ID" | jq -r '.[0].id // empty')
if [[ -n "$INTERNAL_ID" ]]; then
echo " client already exists (id=$INTERNAL_ID), skipping"
else
curl -sS --fail-with-body -X POST "$KC/admin/realms/$REALM/clients" "${auth[@]}" \
-H "Content-Type: application/json" \
-d "{
\"clientId\": \"$CLIENT_ID\",
\"enabled\": true,
\"publicClient\": false,
\"authorizationServicesEnabled\": false,
\"standardFlowEnabled\": false,
\"implicitFlowEnabled\": false,
\"directAccessGrantsEnabled\": false,
\"serviceAccountsEnabled\": true,
\"attributes\": {
\"oauth2.device.authorization.grant.enabled\": \"false\",
\"oidc.ciba.grant.enabled\": \"false\"
}
}"
INTERNAL_ID=$(curl -sS --fail-with-body "${auth[@]}" \
"$KC/admin/realms/$REALM/clients?clientId=$CLIENT_ID" | jq -r '.[0].id')
echo " created (id=$INTERNAL_ID)"
fi
# --- 3. Assign realm role to the service account ---
echo "==> Resolving service-account user"
SA_USER_ID=$(curl -sS --fail-with-body "${auth[@]}" \
"$KC/admin/realms/$REALM/clients/$INTERNAL_ID/service-account-user" | jq -r '.id')
echo "==> Fetching role representation"
ROLE_JSON=$(curl -sS --fail-with-body "${auth[@]}" "$KC/admin/realms/$REALM/roles/$ROLE_NAME")
echo "==> Assigning $ROLE_NAME to service account $SA_USER_ID"
curl -sS --fail-with-body -X POST "$KC/admin/realms/$REALM/users/$SA_USER_ID/role-mappings/realm" \
"${auth[@]}" \
-H "Content-Type: application/json" \
-d "[$ROLE_JSON]"
echo
echo "==> Done."
The script doesn’t create or print a client secret. After it finishes, open the apicurio-migration client in the Keycloak Admin Console and copy the secret from the Credentials tab, as in the manual flow.
Verify the migration client
Check that the client can get a token, which proves it exists, is confidential and has service accounts enabled.
Replace every <VALUE> placeholder with your actual value before running the command. Drop /auth from the URL if your Keycloak serves its API at the root, as described for KC above.
|
curl -skS --fail-with-body -X POST \
"https://<APICURIO_KEYCLOAK_HOST>/auth/realms/<APICURIO_REALM>/protocol/openid-connect/token" \
-d "grant_type=client_credentials&client_id=apicurio-migration&client_secret=<MIGRATION_CLIENT_SECRET>" \
| jq -r '.access_token' | cut -c1-20
The command prints the first characters of a JSON Web Token, starting with eyJ. Then open the apicurio-migration client in the Admin Console and confirm that the Service account roles tab lists migration-admin.