Identity

Secure Keycloak with cnspec

Scan Keycloak realms, clients, and users against security and compliance best practices with cnspec.

Keycloak decides who gets into everything behind it, so a weak realm setting or an over-permissive client reaches every application the realm protects. cnspec reads a Keycloak server through its admin REST API and evaluates realm password and lockout policy, token and session lifespans, the clients registered in each realm and the flows they enable, users and the roles mapped to them, federated identity providers, user federation directories, authentication flows, realm signing keys, and event recording.

If you're new to cnspec, start with the Quickstart. For an overview of every identity provider cnspec can scan, see the identity scanning overview.

Prerequisites

To scan Keycloak with cnspec, you must have:

cnspec only issues GET requests to Keycloak. It never changes a realm.

Authenticate

There are two ways to authenticate.

Admin user

The password grant signs in against the built-in admin-cli client. It's the quickest way to try the provider, and it inherits every privilege of the account:

cnspec shell keycloak --url https://keycloak.example.com --username admin --password YOUR_PASSWORD

Service account

A confidential client with service accounts enabled uses the client credentials grant. It holds only the roles it was granted, so you can limit it to read-only access to one realm:

cnspec shell keycloak --url https://keycloak.example.com --realm production \
  --client-id mondoo-scanner --client-secret YOUR_SECRET

To create the service account, add a client in the realm, disable the standard flow, enable Client authentication and Service accounts roles, then assign these realm-management roles to its service account:

  • view-realm
  • view-users
  • view-clients
  • view-identity-providers
  • view-authorization

A service account needs those roles on every realm it reads. To read every realm on the server, grant the view-realm role of the master realm's realm-management client instead.

Server URL

Pass the base URL of the server. A server installed under a context path keeps that path, for example https://keycloak.example.com/auth. Where the server certificate comes from a private authority, pass that authority with --ca-cert, either as the PEM itself or as a path to it. Trusting the authority keeps the certificate verified, which skipping verification would not.

Environment variables

KEYCLOAK_URL, KEYCLOAK_REALM, KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRET, KEYCLOAK_USERNAME, and KEYCLOAK_PASSWORD supply the same values as the flags:

export KEYCLOAK_URL=https://keycloak.example.com
export KEYCLOAK_USERNAME=admin
export KEYCLOAK_PASSWORD=YOUR_PASSWORD

Connection options

OptionDescription
--urlBase URL of the Keycloak server
--realmScope the scan to a single realm instead of every realm the credentials can read
--auth-realmRealm the token is requested from, which defaults to master for a user, or the scanned realm for a service account
--client-idClient the token is requested for, which defaults to admin-cli for a user
--client-secretSecret of a confidential client, which selects service account authentication
--usernameAdmin user to authenticate as, which selects password authentication
--passwordPassword of the admin user
--ca-certCertificate authority to trust for the server certificate
--discoverWhat to discover: auto (default), all, or realms

Verify with a quick Keycloak check

Confirm that cnspec can reach your server by opening a cnspec shell:

cnspec shell keycloak --url https://keycloak.example.com --username admin --password YOUR_PASSWORD
cnspec> keycloak.realms { name enabled sslRequired bruteForceProtected }
keycloak.realms: [
  0: {
    name: "master"
    enabled: true
    sslRequired: "external"
    bruteForceProtected: false
  }
  1: {
    name: "production"
    enabled: true
    sslRequired: "all"
    bruteForceProtected: true
  }
]

If cnspec connects and lists your realms, you're ready to scan.

Scan Keycloak

cnspec scan keycloak --url https://keycloak.example.com --username admin --password YOUR_PASSWORD

To scan a single realm:

cnspec scan keycloak --url https://keycloak.example.com --realm production \
  --client-id mondoo-scanner --client-secret YOUR_SECRET

To scan each realm as its own asset:

cnspec scan keycloak --url https://keycloak.example.com --username admin --password YOUR_PASSWORD --discover realms

When a scan completes, cnspec prints a summary of all the checks it ran, grouped by policy, along with a risk score from 0 (no risk) to 100 (highest risk). Failed checks include remediation guidance to help you fix issues. To learn more about reading scan results, read Understand cnspec Results.

Mondoo doesn't yet ship an out-of-the-box Keycloak policy, so use the checks below as a starting point and create your own policies to meet your specific requirements.

Explore and test checks interactively

Open a cnspec shell to discover resources and try out checks:

cnspec shell keycloak --url https://keycloak.example.com --username admin --password YOUR_PASSWORD

Review realm password and lockout policy

passwordPolicyRules parses the raw policy into its named rules, which is easier to test against than the string Keycloak stores:

cnspec> keycloak.realms {
    name
    passwordPolicy
    passwordPolicyRules
    bruteForceProtected
    permanentLockout
    failureFactor
    maxFailureWaitSeconds
  }

Review token and session lifespans

cnspec> keycloak.realms {
    name
    accessTokenLifespan
    ssoSessionIdleTimeout
    ssoSessionMaxLifespan
    revokeRefreshToken
    refreshTokenMaxReuse
  }

Review realm login settings

cnspec> keycloak.realms {
    name
    registrationAllowed
    resetPasswordAllowed
    verifyEmail
    loginWithEmailAllowed
    duplicateEmailsAllowed
    editUsernameAllowed
    rememberMe
    sslRequired
  }

List clients and the flows they enable

cnspec> keycloak.clients {
    clientId
    enabled
    publicClient
    standardFlowEnabled
    implicitFlowEnabled
    directAccessGrantsEnabled
    serviceAccountsEnabled
  }

Find clients with wildcard redirect targets

cnspec derives hasWildcardRedirectUri and hasWildcardWebOrigin so you don't have to pattern match the raw lists yourself, and wildcardRedirectUris shows which entries triggered it:

cnspec> keycloak.clients.where(hasWildcardRedirectUri == true) { clientId publicClient wildcardRedirectUris }

List users that hold a realm administration role

cnspec> keycloak.realms { name users.where(hasAdminRole == true) { username email enabled } }

Find service accounts and the clients they belong to

cnspec> keycloak.realms { name users.where(isServiceAccount == true) { username serviceAccountClientId } }

Review user federation directories

A user federation component points at a directory the realm authenticates against, so its transport and bind settings decide whether those credentials cross the network in the clear:

cnspec> keycloak.realms { name components.where(isUserFederation == true) { name providerId connectionUrl connectionEncrypted startTls editMode } }

Review federated identity providers

cnspec> keycloak.identityProviders { alias providerId enabled validateSignature useJwksUrl trustEmail syncMode }

Review realm signing keys

cnspec> keycloak.realms { name keys { kid algorithm type use status isActive } }

Review event recording

cnspec> keycloak.realms { name eventsConfig { eventsEnabled adminEventsEnabled adminEventsDetailsEnabled eventsExpiration eventsListeners } }

Review the required actions a realm asks of a user

cnspec> keycloak.realms { name requiredActions { alias name enabled defaultAction } }

Inspect an authentication flow step by step

cnspec> keycloak.realms { name browserFlowRef { alias executions { level displayName requirement providerId } } }

Walk from a resource to its neighbors

Realms, clients, users, groups, and roles reference each other, so a query can start from any of them and walk in either direction:

cnspec> keycloak.clients { clientId realm { name } }
cnspec> keycloak.realms { name groups { path allRoles { name } } }
cnspec> keycloak.clients.where(serviceAccountsEnabled == true) { clientId serviceAccountUser { username allRoles { name } } }

Example security checks

Ensure every realm requires TLS

sslRequired is all, external, or none. Only all covers requests that reach the server from a private network:

cnspec> keycloak.realms.all(sslRequired == "all")
[ok] value: true

Ensure every realm locks accounts after repeated failed logins

cnspec> keycloak.realms.all(bruteForceProtected == true)
[ok] value: true

Ensure the lockout threshold is strict

cnspec> keycloak.realms.where(bruteForceProtected == true).all(failureFactor <= 5)
[ok] value: true

Ensure no realm lets users register themselves

cnspec> keycloak.realms.none(registrationAllowed == true)
[ok] value: true

Ensure every realm confirms an email address

cnspec> keycloak.realms.all(verifyEmail == true)
[ok] value: true

Ensure every realm sets a password policy

A realm with no policy accepts any password the user chooses:

cnspec> keycloak.realms.all(passwordPolicyRules.length > 0)
[ok] value: true

Ensure two accounts can't share an email address

cnspec> keycloak.realms.none(duplicateEmailsAllowed == true)
[ok] value: true

Ensure access tokens are short lived

cnspec> keycloak.realms.all(accessTokenLifespan <= 300)
[ok] value: true

Ensure a refresh token is invalidated once it's used

cnspec> keycloak.realms.all(revokeRefreshToken == true)
[ok] value: true

Ensure single sign-on sessions don't idle indefinitely

cnspec> keycloak.realms.all(ssoSessionIdleTimeout <= 1800)
[ok] value: true

Ensure no realm signs tokens with a symmetric algorithm

An HMAC algorithm signs with a secret the realm shares, so any holder of that secret can mint a token:

cnspec> keycloak.realms.none(defaultSignatureAlgorithm == "HS256")
[ok] value: true

Ensure no client accepts a wildcard redirect target

A wildcard redirect URI lets an authorization code be delivered to a URL the operator never approved:

cnspec> keycloak.clients.none(hasWildcardRedirectUri == true)
[ok] value: true

Ensure no client allows every browser origin

cnspec> keycloak.clients.none(hasWildcardWebOrigin == true)
[ok] value: true

Ensure the implicit flow is disabled everywhere

The implicit flow returns tokens in the browser URL, where they land in history and referrer headers:

cnspec> keycloak.clients.none(implicitFlowEnabled == true)
[ok] value: true

Ensure every public client uses PKCE

A public client authenticates without a secret, so without a proof key its authorization code can be exchanged by whoever intercepts it:

cnspec> keycloak.clients.where(publicClient == true).all(pkceCodeChallengeMethod == "S256")
[ok] value: true

Ensure no public client uses the direct access grant

The direct access grant takes a username and password from the client itself, which bypasses the browser flow and any step it enforces:

cnspec> keycloak.clients.where(publicClient == true).none(directAccessGrantsEnabled == true)
[ok] value: true

Ensure no client receives every role of the user

With the full scope on, a token minted for one application carries the roles that authorize the others:

cnspec> keycloak.clients.none(fullScopeAllowed == true)
[ok] value: true

Ensure every user federation connection is encrypted

cnspec> keycloak.realms.all(components.where(isUserFederation == true).all(connectionEncrypted == true))
[ok] value: true

Ensure every identity provider verifies assertion signatures

Without signature validation, an assertion that claims to come from the provider is accepted on its word:

cnspec> keycloak.identityProviders.where(enabled == true).all(validateSignature == true)
[ok] value: true

Ensure no identity provider is trusted to confirm an email address

trustEmail accepts the address from the provider without the realm confirming it, which matters where accounts are matched by email:

cnspec> keycloak.identityProviders.where(enabled == true).none(trustEmail == true)
[ok] value: true

Ensure login and admin events are recorded

cnspec> keycloak.realms.all(eventsConfig.eventsEnabled == true && eventsConfig.adminEventsEnabled == true)
[ok] value: true

Ensure every realm can require a one-time password

cnspec> keycloak.realms.all(requiredActions.where(alias == "CONFIGURE_TOTP").all(enabled == true))
[ok] value: true

Review accounts that never confirmed their email address

cnspec> keycloak.realms { name users.where(enabled == true && emailVerified == false) { username email } }

Learn more

On this page

PrerequisitesAuthenticateAdmin userService accountServer URLEnvironment variablesConnection optionsVerify with a quick Keycloak checkScan KeycloakExplore and test checks interactivelyReview realm password and lockout policyReview token and session lifespansReview realm login settingsList clients and the flows they enableFind clients with wildcard redirect targetsList users that hold a realm administration roleFind service accounts and the clients they belong toReview user federation directoriesReview federated identity providersReview realm signing keysReview event recordingReview the required actions a realm asks of a userInspect an authentication flow step by stepWalk from a resource to its neighborsExample security checksEnsure every realm requires TLSEnsure every realm locks accounts after repeated failed loginsEnsure the lockout threshold is strictEnsure no realm lets users register themselvesEnsure every realm confirms an email addressEnsure every realm sets a password policyEnsure two accounts can't share an email addressEnsure access tokens are short livedEnsure a refresh token is invalidated once it's usedEnsure single sign-on sessions don't idle indefinitelyEnsure no realm signs tokens with a symmetric algorithmEnsure no client accepts a wildcard redirect targetEnsure no client allows every browser originEnsure the implicit flow is disabled everywhereEnsure every public client uses PKCEEnsure no public client uses the direct access grantEnsure no client receives every role of the userEnsure every user federation connection is encryptedEnsure every identity provider verifies assertion signaturesEnsure no identity provider is trusted to confirm an email addressEnsure login and admin events are recordedEnsure every realm can require a one-time passwordReview accounts that never confirmed their email addressLearn more