Prerequisites¶
- Keycloak database created.
- OpenLDAP running, with the reader service account and the
ou=Peopleandou=Groupsbranches ready. - cert-manager and
ingress in place, so
keycloak.<BASE_DOMAIN>can be served over HTTPS.
Deploy¶
ansible-playbook -i /path/to/inventory --tags keycloak kubernetes.yml
Add DNS for keycloak.<BASE_DOMAIN> — a CNAME to the ingress is typical. An
/etc/hosts entry will carry you through the configuration below, but it is not
a deployment; fix DNS before anything else points at Keycloak.
Deploying by kustomize instead of the playbook
Older deployments applied Keycloak with kustomize, generating its secrets and configuration inline. The shape of that configuration is still a useful reference for what Keycloak needs:
secretGenerator:
- name: dbuser
literals:
- username=<USERNAME>
- password=<GENERATED_SECRET>
- name: kcadmin
literals:
- username=<USERNAME>
- password=<GENERATED_SECRET>
configMapGenerator:
- name: keycloak-config
literals:
- KEYCLOAK_HOSTNAME=keycloak.<BASE_DOMAIN>
- KEYCLOAK_LOGLEVEL=INFO
- DB_VENDOR=postgres
- DB_ADDR=<DATABASE_HOST>
- DB_PORT=5432
- PROXY_ADDRESS_FORWARDING=true
- JDBC_PARAMS=connectTimeout=21600
namespace: keycloak
resources:
- deployment.yaml
- service.yaml
generatorOptions:
disableNameSuffixHash: true
kubectl apply -k ./base/ -n keycloak
Generate every literal at install time. Never commit a rendered
kustomization.yaml containing real secrets.
Realm¶
Create a realm named for your site (<SITE>). It can take a few moments to
appear; confirm it is selected as the current realm before continuing — the rest
of this configuration silently lands in master otherwise.
Configure → Realm settings:
| Setting | Value |
|---|---|
| Realm name | <SITE> |
| Display name / HTML display name | Whatever users should see |
| Frontend URL | blank |
| Require SSL | All requests |
Leave everything else at its default, and save at the bottom of the page.
LDAP user federation¶
Configure → User federation → Add LDAP provider, with the realm selected:
| Setting | Value |
|---|---|
| Vendor | Other |
| Connection URL | ldap://openldap.openldap |
| StartTLS | off |
| Use Truststore SPI | Always |
| Connection pooling | on |
| Connection timeout | blank |
| Bind type | simple |
| Bind DN | uid=ldap_reader,ou=People,<LDAP_BASE_DN> (from ldap_cn and ldap_base_dn) |
| Bind credentials | the value of ldap_ldap_reader_pw |
Use Test connection and Test authentication before going further. A toast in the upper right reports the result; if either fails, nothing below will work.
LDAP searching and updating:
| Setting | Value |
|---|---|
| Edit mode | READ_ONLY |
| Users DN | the bind DN without the uid component, e.g. ou=People,<LDAP_BASE_DN> |
| Relative user creation | blank |
| Username LDAP attribute | uid |
| RDN LDAP attribute | uid |
| UUID LDAP attribute | uidNumber |
| User object classes | inetOrgPerson, posixAccount |
| LDAP filter | blank |
| Search scope | One Level |
| Read timeout | blank |
| Pagination | on |
| Referral | blank |
Synchronization:
| Setting | Value |
|---|---|
| Import users | on |
| Sync registrations | off |
| Batch size | 500 |
| Periodic full sync | on, period 86400 |
| Periodic changed users sync | on, period 3600 |
| Remove invalid users during searches | on |
Leave Kerberos integration off, cache settings at DEFAULT, and advanced
settings off. Save.
READ_ONLY and sync registrations off are deliberate
Accounts are created in LDAP by the User Portal, not in Keycloak. Letting Keycloak write back would produce two systems of record for one account.
LDAP mappers¶
Configure → User federation → (the LDAP provider) → Mappers → Add mapper. Three mappers are needed.
entitlement¶
| Setting | Value |
|---|---|
| Name | entitlement |
| Mapper type | group-ldap-mapper |
| LDAP Groups DN | ou=Groups,<LDAP_BASE_DN> |
| Group Object Classes | posixGroup |
| Preserve Group Inheritance | off |
| Membership LDAP Attribute | memberUid |
| Membership Attribute Type | UID |
| Membership User LDAP Attribute | uid |
| Mode | READ_ONLY |
| User Groups Retrieve Strategy | LOAD_GROUPS_BY_MEMBER_ATTRIBUTE |
| Member-Of LDAP Attribute | memberOf |
| Drop non-existing groups during sync | on |
| Groups Path | / |
Preserve Group Inheritance must be off because POSIX groups are flat; leaving it on makes the sync fail on groups that have no parent.
name¶
| Setting | Value |
|---|---|
| Name | name |
| Mapper type | user-attribute-ldap-mapper |
| User Model Attribute | name (typed into the text box, nothing selected from the dropdown) |
| LDAP attribute | cn |
| Read Only | on |
| Always Read Value From LDAP | on |
| Is Mandatory in LDAP | on |
| Force a Default Value | on |
roles¶
| Setting | Value |
|---|---|
| Name | roles |
| Mapper type | role-ldap-mapper |
| LDAP Roles DN | ou=Groups,<LDAP_BASE_DN> (same as the entitlement mapper) |
| Role Object Classes | posixGroup |
| Membership LDAP Attribute | memberUid |
| Membership Attribute Type | UID |
| Mode | READ_ONLY |
Everything not listed can stay at its default.
Client scope mappers¶
The DE reads name and entitlement out of the token, so both have to be added
to the profile client scope. Manage → Client scopes → profile → Mappers →
Add mapper → By configuration:
| Mapper | Type | Settings |
|---|---|---|
name |
User Property | Name, Property, and Token Claim Name all set to name |
entitlement |
Group Membership | Name and Token Claim Name set to entitlement; Full group path off |
Leaving Full group path on prefixes every group with /, and the DE's
entitlement checks then match nothing.
Authentication¶
Configure → Authentication. Leave the Flows tab alone. In Required actions, enable exactly these and disable the rest:
- Terms and Conditions
- Update Password
- Update Profile
- Verify Email
- Delete Credential
- Linking Identity Provider
- Update User Locale
Realm roles¶
Manage → Realm roles, add:
| Role | Held by |
|---|---|
app-runner |
Accounts permitted to run apps |
cyverse-emailer |
The service that sends mail |
cyverse-ldap-reader |
The directory reader |
cyverse-subscription-updater |
Subscription and quota updates |
vice-operator |
The VICE operator service account |
Clients¶
Eight clients. Manage → Clients → Create client. The create wizard presents fields in a different order than the finished client page, so work from the tables below rather than from wizard step order, and re-check the client detail page when you are done.
Every client secret below is read from the Credentials tab on the client
detail page and written into group_vars/all.yml in your private inventory.
Interactive clients¶
| Client ID | Root / Home URL | Valid redirect URIs | Web origins | Admin URL |
|---|---|---|---|---|
de-<SITE> |
https://de.<BASE_DOMAIN> |
https://de.<BASE_DOMAIN>/* |
https://de.<BASE_DOMAIN> |
https://de.<BASE_DOMAIN> |
portal-<SITE> |
https://user.<BASE_DOMAIN> |
https://user.<BASE_DOMAIN>/* |
https://user.<BASE_DOMAIN> |
https://user.<BASE_DOMAIN> |
vice-<SITE> |
— | https://* |
https://*.vice.<BASE_DOMAIN>/* |
— |
All three: client authentication on, authorization off, valid post logout
redirect URIs +. Authentication flow: Standard flow and Direct access grants
checked and nothing else — except vice-<SITE>, which also needs Service account
roles.
| Client | Client ID variable | Secret variable |
|---|---|---|
de-<SITE> |
keycloak_client_id |
keycloak_client_secret |
vice-<SITE> |
keycloak_vice_client_id |
keycloak_vice_client_secret |
portal-<SITE> |
portal_keycloak_client |
portal_keycloak_secret |
VICE service clients¶
| Client ID | Purpose | Configuration |
|---|---|---|
vice-api |
Lets the app-exposer backend reach the VICE operator | Client authentication on; only Service account roles checked. After saving: Service account roles → Assign role → Realm roles → vice-operator → Assign |
vice-users |
VICE authentication callback | Client authentication on; Standard flow and Direct access grants checked; valid redirect URI https://vice-api.vice.<BASE_DOMAIN>/auth/callback; web origins + |
vice-swagger |
VICE API documentation UI | Client authentication on, authorization off; Standard flow checked; valid redirect URI https://vice-api.vice.<BASE_DOMAIN>/docs/callback; web origins https://vice-api.vice.<BASE_DOMAIN>; post logout redirect + |
Their client IDs default to the names above, so only the secrets need setting:
| Client | Secret variable |
|---|---|
vice-api |
vice_api_keycloak_client_secret |
vice-users |
vice_operator_keycloak_client_secret |
vice-swagger |
vice_operator_swagger_client_secret |
Two variable names do not match their client names
vice-users and vice-swagger write into variables named for the operator.
That is what the playbooks read, so copy the values into the variables exactly
as listed rather than "correcting" them.
Remaining service clients¶
| Client ID | Purpose | Configuration | Variables |
|---|---|---|---|
formation-service-account |
Service-to-service access for formation | Client authentication on; Service account roles checked; everything else default | formation_keycloak_client_id, formation_keycloak_client_secret |
de-admin-api |
DE administrative API | Root and Home URL https://de.<BASE_DOMAIN>; redirect https://de.<BASE_DOMAIN>/*; post logout +; web origins +; Admin URL https://de.<BASE_DOMAIN>; client authentication on; Standard flow and Service account roles checked, Direct access grants unchecked |
keycloak_admin_client_id, keycloak_admin_client_secret |
After Keycloak¶
The client secrets you just collected are inputs to the phase that follows, so finish this document before running further playbooks. Next:
- Generate service signing keys.
- Apply configuration, ingress, networking, and NATS.