Import from Keycloak

--from keycloak reads a realm directly from a running Keycloak instance through its Admin REST API and recreates it in FerrisKey.

ferris-ctl realm import --from keycloak \
  --source-url https://keycloak.example.com \
  --source-realm acme \
  --source-client-id ferriskey-import \
  --source-client-secret "$KC_SECRET" \
  --target-realm acme

Required flags

FlagDescription
--source-urlBase URL of the Keycloak instance
--source-realmRealm to read from Keycloak

Authentication

Two options:

  • Client credentials: pass --source-client-id and --source-client-secret. The CLI runs a client-credentials grant against the token endpoint of the source realm, so the client has to be registered in that realm, with a service account that may read the realm through the Admin REST API.
  • A ready token: pass --source-token when you already have a bearer token in hand.

When --source-token is set, the client id and secret are ignored.

ferris-ctl source add kc-prod --kind keycloak \
  --url https://keycloak.example.com --realm acme \
  --client-id ferriskey-import --client-secret "$KC_SECRET"

ferris-ctl realm import --source-ref kc-prod --target-realm acme

What gets imported

The importer reads the realm representation and then clients, roles, and users under /admin/realms/{realm}, and maps them like this:

KeycloakFerrisKey
RealmA realm named like the source realm, or --target-realm
Realm settings accessTokenLifespan, registrationAllowed, resetPasswordAllowed, rememberMe, verifyEmailaccess_token_lifetime, user_registration_enabled, forgot_password_enabled, remember_me_enabled, email_verification_enabled
Realm rolesRealm roles, with name and description
ClientsClients with clientId, name, enabled, protocol, serviceAccountsEnabled, directAccessGrantsEnabled, and redirectUris. A client with publicClient set is public, any other is confidential
Client rolesRoles scoped to their client, with name and description
UsersUsers with username, email, first name, last name, and the email-verified flag

Only those five realm settings are mapped; every other Keycloak setting keeps the FerrisKey default. Users are read 100 at a time until the list runs out.

What does not come across

  • Passwords. Keycloak never exports password hashes, so users arrive without credentials.
  • Role assignments. The roles are created, but who holds them is not carried over.
  • Permissions. Roles are created with none, so a role that meant something in Keycloak grants nothing until you set its permissions in FerrisKey.
  • The enabled flag of a user. It is not read, so every user is created with FerrisKey’s default.
  • Per-client OAuth details: post-logout redirect URIs, web origins, the PKCE requirement, token lifetimes, and the device authorization grant. Clients arrive with FerrisKey’s defaults for these and need finishing by hand.
  • Client secrets. A confidential client gets a new secret from FerrisKey, listed in the import report; the applications that used the Keycloak secret need the new one.
  • Everything the importer does not request, such as groups, identity providers, client scopes, and mappers.

A client is never created as system.

A failed read of one client’s roles is swallowed without a message: that client simply arrives with no roles. Compare the role count in the report with what you expect.

Go through the imported realm afterwards and re-establish credentials, permissions, and role assignments. Run --dry-run -o yaml first to see precisely what would be created.