- Go Template 100%
| charts | ||
| media | ||
| values | ||
| .editorconfig | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| BIO-COMPLIANCY.md | ||
| COPYRIGHT | ||
| HLD-tekening-keycloak-deployment.png | ||
| LICENSE.md | ||
| README.md | ||
| TEST-PLAN.md | ||
WM IAM provisioning
This repository creates a Helm Chart that provisions a centralized Keycloak IAM for Watermanagement applications, integrating with ADFS for authentication and AD groups for authorization.
It is based on Crossplane and its Keycloak Provider.
Special notes on this open source release
This project is provided as an open source copy of our custom helmcharts. It expects some middleware on top of plain kubernetes to work. This is up to the implementer to address.
- We rely on the external secrets operator and we use Hashicorp Vault as an external backing for these secrets. OpenBao may work too, we haven't tested this.
- We use a custom TLS trust bundle so that the internal PKI signed certificates work ok.
Questions regarding this code can be sent to et@rws.nl which we will answer on a best effort basis.
Rationale
Within Rijkswaterstaat, Active Directory is used as the primary identity management system for employee authentication and authorization. However, for modern web applications, implementing a more flexible and feature-rich Identity and Access Management (IAM) solution is necessary. This is why we've chosen Keycloak as an intermediary IAM solution rather than integrating applications directly with ADFS.
Why not use ADFS directly?
-
Protocol flexibility: While ADFS supports SAML-based authentication, Keycloak provides broader protocol support, including OAuth 2.0 and OpenID Connect. This allows our applications to use modern authentication protocols.
-
Centralized client management: By implementing Keycloak, we gain a centralized place to manage client applications, reducing the administrative burden on both application teams and the AD administrators. New applications can be onboarded with minimal changes to the AD infrastructure.
-
Fine-grained authorization: Keycloak offers more sophisticated role mapping and attribute processing capabilities, allowing us to transform the relatively simple AD group memberships into more nuanced application-specific authorization models.
-
Self-service capabilities: Our implementation enables a GitOps approach to IAM configuration, empowering development teams to manage their own application's authentication and authorization requirements through version-controlled configuration rather than requiring AD administrator intervention.
-
Multi-tenant support: The realm-based architecture of Keycloak allows us to maintain proper isolation between different environments (DEV, TST, ACC, PRD) while retaining consistent authentication patterns.
Role Mapping
The roles come from ADFS and are mapped using a two-step process:
- IDP-level mapping translates AD group names (e.g., App1
Role1, App1Role2) into Keycloak attributes (e.g., adfs:roles:app1 with value [Role1, Role2]). - Client-level mapping converts these attributes into a 'roles' claim (e.g., ["Role1", "Role2"]) added to the oAuth tokens.
This approach centralizes authentication and authorization in AD, avoids local user management in Keycloak, and ensures seamless role-based access control.
Privacy and Data Processing
We've designed our architecture with privacy-by-design principles and GDPR compliance in mind:
-
Data Minimization: Through our attribute mapping configuration, we ensure only necessary user attributes are passed from AD to Keycloak and from Keycloak to applications. This reduces the privacy footprint and limits potential exposure of personal data.
-
Purpose Limitation: The user attributes we process are strictly limited to those required for authentication and authorization purposes. The mappings are explicitly defined for each client application to ensure only relevant data is shared.
-
No Local Data Storage: With our implementation of transient users (explained below), we significantly reduce personal data persistence within the IAM system itself.
Transient Users
A privacy enhancement in our Keycloak implementation is the use of the "transient users" feature. This provides the following benefits:
-
Reduced Data Persistence: Transient users are not permanently stored in Keycloak's database. After a user's session ends, their profile information is removed from Keycloak, minimizing the amount of personal data stored in the system.
-
Just-in-Time Provisioning: User profiles are created dynamically at login time based on the SAML attributes provided by ADFS. This ensures user information is always current without requiring synchronization processes.
-
No Duplicate User Stores: By not persistently storing user profiles, we avoid creating a duplicate user repository that could become outdated or inconsistent with the authoritative AD source.
-
Session-Based Processing: User attributes and roles are processed during the session lifetime only, aligning with the principle that personal data should only be processed for as long as necessary for the specified purpose.
-
Simplified Compliance: The transient approach simplifies compliance as user data is automatically removed once the session ends rather than requiring explicit deletion processes.
Goals
- Central IAM solution that is sufficient for at least 80% of Watermanagement hosted applications
- Simple deployment configuration
- GitOps realm/client config
- BIO compliancy
- Self-service, including test admin by the suppliers (GitOps)
Architectural decisions
- Single realm per stage/environment is strongly prefered. The possibility of adding a realm for "non-standard" apps remains
- Single client per application. This is the most common setup. It is, of course, possible to have multimple clients per application or a single client for a chain of applications (less probable).
- Admin users:
- Instance admin(s)
- TAB'ers can manage realms (GitOps)
- Client admins - suppliers/FAB'ers
- Power users
- Do not do local user management wherever possible
- Authentication for RWS users is done on AD side
- Authorization for RWS users is done by adding users to AD groups
- Self-service (IAG in the future)?
- Strictly defined protocol for communicating group membership per environment
- Automated group-to-role mapping per client (application)
- Use ADFS/Rijksrotonde to get Authentication with rijksoverheid users (non-RWS users)
- Prefer federation on ADFS level
PLEASE NOTE that the templates in the helm charts that we maintain are rather opiniated as to what flags can be customized. A lot of the values are 'hardcoded' so if you have new flag to add or override, you probably need to release newer chart versions to do so.
Components
The provisioning process results in the creation of several Realms. Each of these realms will have an ADFS-backed IDP, and a number of clients.
The repository contains 3 charts that implement:
- Crossplane Keycloak Provider
- Crossplane Keycloak ProviderConfig
- Crossplane Keycloak Realm/IDP/Clients
Crossplane provider for Keycloak: https://github.com/crossplane-contrib/provider-keycloak Which uses the terraform provider: https://github.com/keycloak/terraform-provider-keycloak
Overview of (custom) resources: https://marketplace.upbound.io/providers/crossplane-contrib/provider-keycloak/v2.6.0
Realm
We will create a realm for each stage of an application lifecycle. These realms will be named after the stage, and will have a number of clients for each stage.
Stages are:
- Development (DEV)
- Testing (TST)
- Acceptance (ACC)
- Production (PRD)
IDP
We will create an ADFS-backed (SAML) IDP for each realm.
Furthermore, multiple IDP-level mappings will be created. These will map SAML attributes coming from ADFS to local (Keycloak) user attributes.
The mappings are as follows:
- Passthrough (IAM team calls them passthru) mappings - these map SAML attributes to local user attributes. Attributes in question are:
- urn:exampleorg:federation:din
- urn:exampleorg:federation:displayName
- urn:exampleorg:federation:emailAddress
- urn:exampleorg:federation:givenName
- urn:exampleorg:federation:mobile
- urn:exampleorg:federation:organization
- urn:exampleorg:federation:organizationDisplayName
- urn:exampleorg:federation:organizationNumber
- urn:exampleorg:federation:organizationType
- urn:exampleorg:federation:rin
- urn:exampleorg:federation:surName
- urn:exampleorg:federation:surNameInfix
- urn:exampleorg:federation:uid
- urn:exampleorg:federation:userPrincipalName
- Mappings for built-in user attributes - these map SAML attributes to local, built-in user attributes. Attributes in question are:
- local email - ADFS (SAML) urn:exampleorg:federation:emailAddress
- local firstName - ADFS urn:exampleorg:federation:givenName
- local lastName - ADFS urn:exampleorg:federation:surName
- local username - ADFS urn:exampleorg:federation:uid
SAML attributes
One of the main goals of our Keycloak setup is to avoid doing local user management. For this reason, we want to offload as much as possible to the ADFS IDP.
To do that we have devised the following protocol - when a user logs in to the IDP, the IDP will send back a SAML response with the following attributes (example):
...
<Attribute Name="urn:exampleorg:federation:role">
<AttributeValue>App1~Role1</AttributeValue>
<AttributeValue>App1~Role2</AttributeValue>
<AttributeValue>App2~Role1</AttributeValue>
</Attribute>
...
These attribute values represent the roles of the user in the respective applications, e.g. Role1 and Role2 in App1 and Role2 in App2. App1 "contains" the roles relevant to a particular client (application).
The role attributes here are represented by a group membership in the AD.
Then, an IDP level mapping (one per client) will remove the application name and the '~' from the attribute values, and map them to a user attribute on the Keycloak side.
A client level mapping will then map these attributes to the roles claim of the oAuth identity/access token.
For example, if a user logs in using the Example client and is a member of the AD group Example~Admin, the IDP will send back the following SAML attributes:
...
<Attribute Name="urn:exampleorg:federation:role">
<AttributeValue>Example~Admin</AttributeValue>
<AttributeValue>Example~DataManager</AttributeValue>
</Attribute>
...
This will lead to the creation of a multivalue attribute called adfs:roles:example for that user. The values of this attribute will be Admin and DataManager. These values (Admin and DataManager) will then be added to the roles claim of the oAuth identity/access token(s).
Client
Multiple clients will be created for each realm. These clients will be used to authenticate users against the Keycloak realm.
Each client deployment will also create 2 separate mappers - one on IDP level, and one on client level. These mappers will map the AD group membership to oAuth identity/access token roles claim.
For example, let's say an Example user is a member of the Example~Role1 and Example~Role2 AD groups. When a user logs in to the Example application, which uses the Example client, the user will be have the Role1 and Role2 added to a roles claim and that claim added to the identity and/or access tokens.
This ensures that all off the authorization attributes are kept in AD, and not in Keycloak.
We implement this with the help of two separate mappers - one on IDP level, and one on client level.
The IDP level mapper will map the AD group membership to the adfs:roles: multivalue user attribute. For example, for the Waterinfo client, this attribute will be adfs:roles:waterinfo. It can have multiple values, like for example Datamanager and/or VWMBeheerder.
The client level mapper will map these values (these roles) to the roles claim of the user's access token.
Additional mappers
One can apply special mappers to their clients. This is done via the values of the realm chart.
Audience
The special Audience mapper(s) allow for adding more complex aud claim to the access token.
It requires that the AD groups you are using are of the format App1~AudienceApp1#Role1.
It deploys 3 mappers:
- An IDP mapper that creates a multivalued audience user attribute with the AudienceApp1 bit
- A client mapper that adds the values of that attribute to the aud claim of the access token
- Another (predefined) client mapper that adds the client name as the aud claim to the identity token
It is used like this:
...
clients:
- name: some-app
... ommitted for brevity
idpRoleMappers: # these are the IDP mappers
- idp: adfs
rolePrefix: App1
audience: true # create a specialized Audience mapper on the IDP side
mappers: # here are the Client mappers
audience: true
Configuration
The Crossplane Keycloak ProviderConfig chart is used to configure the Keycloak provider. Each configuration (ProviderConfig) abstracts a single Keycloak instance.
For example, you'll have an acc ProviderConfig, which will configure the Keycloak instance for the Acceptance instance and a prod ProviderConfig, which will configure the Keycloak instance for the Production instance.
The ProviderConfig chart creates an ExternalSecret, which is used to retrieve the Keycloak credentials from Hashicorp Vault.
As there are multiple crossplane providers that deliver a ProviderConfig custom resource, please make sure to request the proper subtype by providing a fully qualified name:
kubectl get providerconfigs.keycloak.crossplane.io
The name of the ProviderConfig - the name with which provisioning chart refers to it - is the name of the its Helm release. For example, if you deploy the config with:
helm install dev charts/provider-config -f values/dev.provider.yaml
the name of the ProviderConfig will be dev.
Usage
For usage, take a look at the WM IAM deployment repository.
