Declarative Keycloak Configuration⚓︎
Introduction⚓︎
The IAM BB is designed to allow declarative setup of
all major configuration items. This setup is split into
a basic realm configuration represented by a
KeycloakRealmImport resource and an extended realm
configuration represented by Crossplane resources.
In principle, this allows defining the complete static
Keycloak configuration without ever using the Keycloak
Admin UI.
This document gives a short introduction in the aspects of the setup that are part of the IAM BB itself and provides some guidance regarding additional configuration to be contributed for integrating other EOEPCA Building Blocks with the IAM.
Platform Operators may also find this information helpful for integrating custom components into their EOEPCA-based platforms.
Basic Realm Configuration⚓︎
This section gives an overview of the basic configuration of the
EOEPCA realm. All items described here can be configured in
the iam.keycloak.realm section of the
values.yaml file
of the IAM-BB Helm chart, which automatically builds a
KeycloakRealmImport resource from them.
If desired, this automatism can also be disabled, so that the
KeycloakRealmImport resource can be provided separately.
Base Settings⚓︎
Basic realm settings that deviate from Keycloak’s defaults can be
made in the customSettings subsection. If placeholders are used,
these can be defined in the customPlaceholders subsection.
The customSettings may contain anything that can be defined in
the spec.realm section of a KeycloakRealmImport resource,
except clients and users. Note that the spec.realm section
has exactly the same structure as a Keycloak realm export.
Thus it can basically be obtained by exporting a preconfigured
realm, reviewing and potentially reducing the result and
converting it to yaml.
The values.yaml file includes some (commented) example settings,
including e-mail settings that are typically required in every
setup.
This is an example how a basic configuration including e-mail settings could look like. Note that the SMTP password is taken from a secret through a placeholder.
iam:
keycloak:
configuration:
realm:
customPlaceholders:
smtp_password:
secret:
name: eoepca-realm
key: smtp_password
customSettings:
loginWithEmailAllowed: false
smtpServer:
starttls: "true"
auth: "true"
port: "587"
host: "smtp.gmail.com"
from: "eoepca@gmail.com"
fromDisplayName: "EOEPCA Develop Cluster"
user: "eoepca@gmail.com"
password: ${smtp_password}
Federated Identity Providers⚓︎
Federated identity providers can be defined in the same way as
basic settings, i.e. in the
iam.keycloak.configuration.realm.customSettings section of the
values.yaml file.
The identity providers themselves are defined in a subsection named
identityProviders. Mappers can be defined in a subsection named
identityProviderMappers.
Here is an example configuration for a GitHub IdP. The client secret is taken from a secret. Note that no mappers are required for GitHub.
iam:
keycloak:
configuration:
realm:
customPlaceholders:
github_client_secret:
secret:
name: eoepca-realm
key: github_client_secret
customSettings:
identityProviders:
- alias: "github"
displayName: "GitHub"
providerId: "github"
enabled: true
updateProfileFirstLoginMode: "on"
trustEmail: false
storeToken: false
addReadTokenRoleOnCreate: false
authenticateByDefault: false
linkOnly: false
hideOnLogin: false
config:
clientId: "a_github_client_id"
acceptsPromptNoneForwardFromClient: "false"
disableUserInfo: "false"
filteredByClaim: "false"
syncMode: "FORCE"
clientSecret: "${github_client_secret}"
caseSensitiveOriginalUsername: "false"
Clients and Users⚓︎
Crossplane Client⚓︎
The Helm chart automatically creates a client with a service account
(M2M client) to be used by the Crossplane Keycloak Provider.
This client is included into the realm definition if
iam.keycloak.configuration.provider.createServiceAccount is set
to true. See the iam.keycloak.configuration.provider section
of the values.yaml file for additional configuration options.
Additional Clients and Users⚓︎
Normally, the Crossplane client is the only one that should be part of the realm definition. Any other clients and users should be created via Crossplane.
However, if further clients or users have to be included into the
realm definition for some reason, they can be defined in the
customClients and customUsers subsections, respectively.
Master Admin Account⚓︎
The Keycloak setup also creates a bootstrap Master Admin user. This admin user is defined in the master realm and thus neither part of the EOEPCA realm definition nor the Crossplane configuration.
A username and password can be set in the
keycloak-operator.keycloak.bootstrapAdmin section of the
values.yaml file. If no password is set, the Keycloak
Operator automatically generates one and stores it in a secret.
See Keycloak documentation
for more information.
iam-management Namespace⚓︎
The iam-management namespace is the preferred place for
Crossplane resources that link the IAM with other Building Blocks
and other (non-EOEPCA) components of a platform.
The iam-bb-config Helm chart prepares this namespace by adding
some predefined resources as a basis for further setup.
This section describes these resources.
Note that neither the iam-bb Helm chart nor the iam-bb-config
Helm chart automatically create the iam-management namespace.
If the iam-bb-config Helm chart is applied as a subchart of
the iam-bb Helm chart (recommended), the iam-management
namespace must be created explicitly in advance. If the
iam-bb-config Helm chart is applied separately, the
--create-namespace option can be used to create it.
Crossplane Keycloak Provider Config⚓︎
The Keycloak Provider Config acts as the main interface between Keycloak and the Crossplane-based Keycloak configuration.
The Helm chart generates a resource of type ProviderConfig
(API keycloak.m.crossplane.io) with the fixed name
keycloak-provider-config. This provider config accesses
Keycloak as the Crossplane M2M client created during the
initial realm import.
All other Crossplane resources in the iam-management namespace
should refer to this provider as follows:
OPA Client⚓︎
The Helm chart optionally creates a Keycloak client for OPA
as a Crossplane resource (if OPA is enabled and
iam.keycloak.configuration.createClients is set to true).
This client is used by the OPA route and is only relevant if
OPA should be accessible from outside the cluster.
The client can be taken as an example for client configuration.
Miscellaneous Setup⚓︎
Some secrets are required in both the main IAM namespace
(typically named iam) and the iam-management namespace.
If secrets are generated through sealed secrets, it is typically easiest to generate two identical sealed secrets in both namespaces.
Alternatively, External Secrets Operator can be leveraged to
promote secrets from the iam to the iam-management
namespace. The iam-bb Helm chart supports this by optionally
creating a Cluster Secret Store for the iam namespace.
The store is named eso-store-iam. It is created if
iam.keycloak.configuration.createSecretStore is set to true.
By default, the secret store can only be referenced from the
iam-management namespace. If required, however, it can also
be configured to be accessible from other namespaces, though
this is not recommended.
The iam-bb-config Helm chart can optionally generate
ExternalSecret resources for the secrets that need to
be available in the iam-management namespace.
EOEPCA Admin User⚓︎
An EOEPCA Admin user is not created by the Helm chart, but it
is part of the IAM configuration on the demo cluster.
See eoepca-admin.yaml
for details. (TODO: Point link to deploy-develop branch after merge)
The configuration picks up the existing realm-admin role as
a Crossplane resource, creates a user named eoepca-admin and
assigns the realm-admin role to it. This may serve as an example
how to define an admin user or a user in general.
Furthermore, the representation of the realm-admin role can be
used to create further users or clients with realm administration
privileges, e.g. for use by a separate Crossplane ProviderConfig
in a BB namespace.
Crossplane-based Configuration⚓︎
This section provides guidance and recommendations regarding the Crossplane-based integration of the IAM BB with other Building Blocks and non-EOEPCA components.
Crossplane resources for static setup should be defined in the
iam-management namespace. However, Building Blocks like
the Workspace BB that use them for dynamic configuration, should
create their dynamic resources in their own namespace(s).
Clients⚓︎
Every Building Block or component that needs to integrate with the IAM requires at least one client in Keycloak that represents it.
Clients should generally be configured as restrictively as possible. In particular they should
- restrict tokens to only the required scopes and audiences.
- disable
fullScopeAllowed. - disable authentication flows that are not required.
- maybe restrict redirect URIs and web origins by path (not always possible).
The following subsections provide some guidance how clients for different purposes should be configured.
Frontend Clients⚓︎
Frontend clients are public clients that are used for authentication by a frontend application, typically running in a browser. This is a rare case in EOEPCA.
A frontend application cannot guarantee confidentiality, which implies that
- client authentication does not make sense, because the client credentials could easily be stolen anyway.
- frontend clients should generally require PKCE to ensure that tokens they request are not misdirected.
- frontend clients should only allow the standard flow.
- it is typically not very useful to address a frontend client as audience. So the normal case would be to address some backend client instead.
The following example illustrates how a frontend client can be configured, including a mapper that adds a backend client to the token audience:
apiVersion: openidclient.keycloak.m.crossplane.io/v1alpha1
kind: Client
metadata:
name: frontend-example-client
namespace: iam-management
spec:
providerConfigRef:
kind: ProviderConfig
name: keycloak-provider-config
forProvider:
clientId: frontend-example-client
name: frontend-example-client
realmId: eoepca
accessType: PUBLIC
baseUrl: "https://develop.eoepca.org/example-frontend"
description: "Example Frontend Client"
enabled: true
fullScopeAllowed: false
pkceCodeChallengeMethod: S256
serviceAccountsEnabled: false
standardFlowEnabled: true
standardTokenExchangeEnabled: false
validRedirectUris:
- /*
webOrigins:
- /*
---
apiVersion: client.keycloak.m.crossplane.io/v1alpha1
kind: ProtocolMapper
metadata:
name: audience-backend2
namespace: iam-management
spec:
providerConfigRef:
kind: ProviderConfig
name: keycloak-provider-config
forProvider:
name: audience-backend
realmId: eoepca
clientIdRef:
name: frontend-example-client
protocol: openid-connect
protocolMapper: oidc-audience-mapper
config:
access.token.claim: 'true'
included.client.audience: backend-example-client
Backend Clients⚓︎
Backend clients are (typically confidential) clients that are used for authentication by or on behalf of a backend application. This is the typical case in EOEPCA.
This includes APISIX, which acts as a gateway and PEP on behalf
of a backend service. Therefore the backend service and
the APISIX route to it share a common client. Using separate
clients for the gateway and the backend is not easily possible
anyway, because APISIX’s openid-connect plugin does not
support token exchange. Therefore we deliberately do not
distinguish between gateway clients and backend clients here.
A typical backend client should have the following properties:
- The client should be confidential, i.e. require client authentication. If it uses token exchange, it must be confidential.
- The client should not have standard token exchange enabled unless it actually requires it.
- Service account roles should be disabled.
Simple Backend Client Example⚓︎
The following example shows how a simple backend client for a leaf service (i.e., one that does not delegate to other services) may be configured. Such a client does typically not require token exchange.
apiVersion: openidclient.keycloak.m.crossplane.io/v1alpha1
kind: Client
metadata:
name: backend-leaf-example-client
namespace: iam-management
spec:
providerConfigRef:
kind: ProviderConfig
name: keycloak-provider-config
forProvider:
clientId: backend-leaf-example-client
name: backend-leaf-example-client
realmId: eoepca
accessType: CONFIDENTIAL
baseUrl: "https://develop.eoepca.org/example-backend"
description: "Example Backend Client for a leaf service"
enabled: true
fullScopeAllowed: false
pkceCodeChallengeMethod: S256
serviceAccountsEnabled: false
standardFlowEnabled: true
standardTokenExchangeEnabled: false
validRedirectUris:
- /*
webOrigins:
- /*
Backend Client Example with Token Exchange⚓︎
The following example shows how a backend client with token exchange can be configured. Token exchange should be used by services that delegate to other services in order to customize the scope and audience of the token to send to the delegate service.
Note that the definition includes a an example client scope for adding an audience to a token and attaches it to the client as an optional scope. This is necessary, because audiences can indeed be filtered directly, but they can only be added through a client scope.
piVersion: openidclient.keycloak.m.crossplane.io/v1alpha1
kind: Client
metadata:
name: backend-delegating-example-client
namespace: iam-management
spec:
providerConfigRef:
kind: ProviderConfig
name: keycloak-provider-config
forProvider:
clientId: backend-delegating-example-client
name: backend-delegating-example-client
realmId: eoepca
accessType: CONFIDENTIAL
baseUrl: "https://develop.eoepca.org/example-backend2"
description: "Example Backend Client with Token Exchange"
enabled: true
fullScopeAllowed: false
pkceCodeChallengeMethod: S256
serviceAccountsEnabled: false
standardFlowEnabled: true
standardTokenExchangeEnabled: true
validRedirectUris:
- /*
webOrigins:
- /*
---
apiVersion: openidclient.keycloak.m.crossplane.io/v1alpha1
kind: ClientScope
metadata:
name: backend-leaf-example-client-audience
namespace: iam-management
spec:
providerConfigRef:
kind: ProviderConfig
name: keycloak-provider-config
forProvider:
name: backend-leaf-example-client-audience
realmId: eoepca
---
apiVersion: client.keycloak.m.crossplane.io/v1alpha1
kind: ProtocolMapper
metadata:
name: audience-backend-leaf-example-client
namespace: iam-management
spec:
providerConfigRef:
kind: ProviderConfig
name: keycloak-provider-config
forProvider:
name: audience-backend-leaf-example-client
realmId: eoepca
clientScopeIdRef:
name: backend-leaf-example-client-audience
protocol: openid-connect
protocolMapper: oidc-audience-mapper
config:
access.token.claim: 'true'
included.client.audience: backend-leaf-example-client
---
apiVersion: openidclient.keycloak.m.crossplane.io/v1alpha1
kind: ClientOptionalScopes
metadata:
name: backend-delegating-example-client-opt-scopes
namespace: iam-management
spec:
providerConfigRef:
kind: ProviderConfig
name: keycloak-provider-config
forProvider:
realmId: eoepca
clientIdRef:
name: backend-delegating-example-client
optionalScopes:
- backend-leaf-example-client-audience
The client defined above is capable of performing token exchange.
It can exchange a token addressed to it (via azp or aud) for
a dedicated token that only has backend-leaf-example-client as
audience by requesting scope backend-leaf-example-client-audience
and restricting audience to backend-leaf-example-client.
Machine-to-Machine (M2M) Clients⚓︎
A M2M Client is a client with an attached service account that represents a service that acts as a M2M user. A service uses such a client to authenticate against another service that it intends to use.
In principle, a service account can be added to any confidential client. However, for security reasons it is recommended to use separate clients for authentication of users against a service and for representing a service as a M2M user. A M2M Client and its credentials should be kept as confidential as possible.
In particular, a M2M Client should only be used by the service itself and not e.g. by an APISIX route that protects the service. Notably this is important, because we do not use s separate gateway client for APISIX. A combined M2M and backend client would thus allow APISIX to act on behalf of the protected service and thereby foil its protection function.
The following example demonstrates how a simple M2M client can
be defined. The service account is created implicitly. The example
picks up the created service account as a User resource,
which can be referenced by further resources, e.g. to assign roles
or groups to it.
apiVersion: openidclient.keycloak.m.crossplane.io/v1alpha1
kind: Client
metadata:
name: m2m-example-client
namespace: iam-management
spec:
providerConfigRef:
kind: ProviderConfig
name: keycloak-provider-config
forProvider:
clientId: m2m-example-client
name: m2m-example-client
realmId: eoepca
accessType: CONFIDENTIAL
description: "Example M2M Client"
enabled: true
fullScopeAllowed: false
serviceAccountsEnabled: true
standardFlowEnabled: false
standardTokenExchangeEnabled: false
---
# Optional: Pick up the service account user as a Crossplane resource
# for further customization
apiVersion: user.keycloak.m.crossplane.io/v1alpha1
kind: User
metadata:
name: service-account-m2m-example-client
namespace: iam-management
annotations:
crossplane.io/external-name: eoepca/service-account-m2m-example-client
spec:
providerConfigRef:
name: keycloak-provider-config
kind: ProviderConfig
managementPolicies:
- Observe
forProvider:
username: service-account-m2m-example-client
realmId: eoepca
Users, Roles and Groups⚓︎
Users⚓︎
Users are usually managed dynamically and are thus not created via Crossplane. An exception to this rule are example or admin users that are part of a Building Block’s infrastructure. Common examples include:
- the standard demo users
alice,bobanderic - the EOEPCA realm admin user
eoepca-admin
See eoepca-admin.yaml for an example that also demonstrates how a role can be assigned to a user.
Roles⚓︎
Roles are usually directly related to platform capabilities and often need to be evaluated by code. Thus they are static and should be configured as Crossplane resources.
The following example demonstrates how to create a realm role and a client role. The only difference is that a client role references a client whereas a realm role does not.
# Realm role
apiVersion: role.keycloak.m.crossplane.io/v1alpha1
kind: Role
metadata:
name: example-realm-role
namespace: iam-management
spec:
providerConfigRef:
name: keycloak-provider-config
kind: ProviderConfig
forProvider:
realmId: eoepca
name: example-realm-role
---
# Client role
apiVersion: role.keycloak.m.crossplane.io/v1alpha1
kind: Role
metadata:
name: example-client-role
namespace: iam-management
spec:
providerConfigRef:
name: keycloak-provider-config
kind: ProviderConfig
forProvider:
realmId: eoepca
name: example-client-role
clientIdRef:
name: backend-leaf-example-client
policy:
resolution: Required
Roles can be assigned to users or user groups using Roles resources
(API user.keycloak.m.crossplane.io or group.keycloak.m.crossplane.io,
respectively). An example can be found in
eoepca-admin.yaml.
Groups⚓︎
User groups may be static or dynamic. Standard user groups for predefined purposes should be configured as Crossplane resources. Additionally, operators may create user groups for their own purposes and structuring needs. Such groups are not reflected by Crossplane resources.
The following examples demonstrates how to create a user group.
apiVersion: group.keycloak.m.crossplane.io/v1alpha1
kind: Group
metadata:
name: example-group
namespace: iam-management
spec:
providerConfigRef:
name: keycloak-provider-config
kind: ProviderConfig
forProvider:
realmId: eoepca
name: example-group
Groups can be assigned to users using Groups resources
(API user.keycloak.m.crossplane.io).
Picking Up Existing Objects⚓︎
Sometimes it is necessary to create Crossplane resources for existing objects, e.g., to assign a user to an existing group or add a mapper to an existing client.
Crossplane has a mechanism for this purpose that is meanwhile also
supported by the Keycloak provider. The mechanism uses an annotation
named crossplane.io/external-name. The value of this annotation
is a reference to an existing object.
The Keycloak Provider interprets this reference as a path with up
to three components. The first path component is always the realm
name. So eoepca would reference the EOEPCA realm itself.
The second path component is the name of an object that is defined in the realm, e.g. a user, group, realm role, client etc. The type of the object is determined by the type of the annotated resource. Examples:
eoepca/realm-managementrefers to the realm management client of the EOEPCA realm.eoepca/ericrefers to the example usereric- More generally,
eoepca/foorefers to an object namedfooin the EOEPCA realm of the type represented by the resource being defined. Depending on the resource type, this may be a client, user, role, group or whatever.
The third path component is used to address objects that belong to a client. The second path component is always a client name in this case.
Example: eoepca/realm-management/manage-realm refers to the
manage-realm client role of the realm-management client.
Usage examples for the crossplane.io/external-name annotation
can be found in
eoepca-admin.yaml.
Note that Crossplane replaces the value of the
crossplane.io/external-name annotation with the actual internal
ID of the referenced object during reconciliation. This may cause
inconsistencies if the resource is updated later. In ArgoCD it is
important to ensure that differences in this annotation are
ignored. Ideally, the annotation should never be updated.
Refer to this pull request for further information and a discussion about the reference mechanism.
Other Resources⚓︎
Besides the resource types addressed in this document, the Crossplane Keycloak Provider supports further resource types that are less relevant in the context of EOEPCA. A complete list of available resource types with documentation can be found here.