LDAP
We are going to use OpenLDAP and setup in previous blog OpenLDAP Image and Custom LDIF with User and Group.
$ podman run -d --name openldap \
-e LDAP_ROOT=dc=magnuskkarlsson,dc=se \
-e LDAP_ADMIN_USERNAME=admin \
-e LDAP_ADMIN_PASSWORD=changeit \
-e LDAP_CONFIG_ADMIN_ENABLED=true \
-e LDAP_ALLOW_ANON_BINDING=false \
-e LDAP_CUSTOM_LDIF_DIR=/ldifs \
-p 1389:1389 \
-p 1636:1636 \
-v ./ldifs:/ldifs:Z \
docker.io/bitnami/openldap:2.6
Test it is working.
$ ldapsearch -H ldap://localhost:1389 -D cn=admin,dc=magnuskkarlsson,dc=se -w changeit -b dc=magnuskkarlsson,dc=se -s sub
Keycloak Installation
Here we will use the supported Keycloak version - RH SSO.
$ unzip rh-sso-7.6.0-server-dist.zip
$ mv rh-sso-7.6 rh-sso-7.6.0
$ cd rh-sso-7.6.0/bin/
$ ./add-user-keycloak.sh --user admin --password admin
Configure LDAP logging in RH SSO
$ vim rh-sso-7.6.0/standalone/configuration/standalone.xml
...
<profile>
<subsystem xmlns="urn:jboss:domain:logging:8.0">
...
</logger>
<logger category="org.keycloak.storage.ldap">
<level name="TRACE"/>
</logger>
<root-logger>
...
Start
$ export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
$ ./standalone.sh -Djboss.socket.binding.port-offset=100
Kecloak Configuration User Federation with NO Import
We will start federate openldap user and roles and not import them and test what happens, to understand how user federation works.
Add User federation
Edit mode: READ_ONLY
Import Users: OFF
No Synchronization
Add Mappers:
- password: user-attribute-ldap-mapper; password; userPassword
- role-ldap-mapper: role-ldap-mapper;
We can verify that user is not imported, by clicking on User and View all users
Now test federation, by logging in to user portal: http://localhost:8180/auth/realms/demo/account/
john
bitnami1
kate
bitnami1
And observe server log, that user is fetch by RDN LDAP attribute: uid
LdapOperation: search
baseDn: ou=People,dc=magnuskkarlsson,dc=se
filter: (&(uid=john)(objectclass=inetOrgPerson)(objectclass=organizationalPerson))
searchScope: 1
returningAttrs: [uid, userPassword, modifyTimestamp, cn, mail, createTimestamp, sn]
resultSize: 1
took: 2 ms
LdapOperation: search
baseDn: ou=People,dc=magnuskkarlsson,dc=se
filter: (&(uid=john)(objectclass=inetOrgPerson)(objectclass=organizationalPerson))
searchScope: 1
returningAttrs: [uid, userPassword, modifyTimestamp, cn, mail, createTimestamp, sn]
resultSize: 1
took: 1 ms
LdapOperation: search
baseDn: ou=Groups,dc=magnuskkarlsson,dc=se
filter: (&(member=cn=john,ou=People,dc=magnuskkarlsson,dc=se)(objectclass=groupOfNames))
searchScope: 1
returningAttrs: [cn]
resultSize: 2
took: 1 ms
bitnami1
Kecloak Configuration User Federation with Import
Now Import Users: ON
Then login (user fetced by RDN LDAP attribute: uid), close private window and login again. Now is the user fetched by UUID LDAP attribute: entryUUID
LdapOperation: search
baseDn: ou=People,dc=magnuskkarlsson,dc=se
filter: (&(uid=john)(objectclass=inetOrgPerson)(objectclass=organizationalPerson))
searchScope: 1
returningAttrs: [uid, userPassword, modifyTimestamp, cn, mail, createTimestamp, sn]
resultSize: 1
took: 2 ms
LdapOperation: lookupById
baseDN: ou=People,dc=magnuskkarlsson,dc=se
filter: (&(objectClass=*)(entryUUID=b8f499ce-e341-103d-9c19-dfc37858caec))
searchScope: 1
returningAttrs: [uid, userPassword, modifyTimestamp, cn, mail, createTimestamp, sn]
took: 3 ms
LdapOperation: search
baseDn: ou=Groups,dc=magnuskkarlsson,dc=se
filter: (&(member=cn=john,ou=People,dc=magnuskkarlsson,dc=se)(objectclass=groupOfNames))
searchScope: 1
returningAttrs: [cn]
resultSize: 2
took: 1 ms
...
LdapOperation: lookupById
baseDN: ou=People,dc=magnuskkarlsson,dc=se
filter: (&(objectClass=*)(entryUUID=b8f499ce-e341-103d-9c19-dfc37858caec))
searchScope: 1
returningAttrs: [uid, userPassword, modifyTimestamp, cn, mail, createTimestamp, sn]
took: 4 ms
When User is imported you can search and find User in Web Console and you can see in server.log that User is updated from LDAP.
LdapOperation: lookupById
baseDN: ou=People,dc=magnuskkarlsson,dc=se
filter: (&(objectClass=*)(entryUUID=b8f499ce-e341-103d-9c19-dfc37858caec))
searchScope: 1
returningAttrs: [uid, userPassword, modifyTimestamp, cn, mail, createTimestamp, sn]
took: 2 ms
LdapOperation: lookupById
baseDN: ou=People,dc=magnuskkarlsson,dc=se
filter: (&(objectClass=*)(entryUUID=b8f499ce-e341-103d-9c19-dfc37858caec))
searchScope: 1
returningAttrs: [uid, userPassword, modifyTimestamp, cn, mail, createTimestamp, sn]
took: 1 ms
Side effects
This online dependency has also consequence, if LDAP server is offline than User cannot log in.
But if the LDAP server comes back online, User can login again.
15:04:09,715 WARN [org.keycloak.services] (default task-44) KC-SERVICES0013: Failed authentication: org.keycloak.models.ModelException: LDAP Query failed
...
Caused by: org.keycloak.models.ModelException: Querying of LDAP failed org.keycloak.storage.ldap.idm.query.internal.LDAPQuery@54e2e27
...
Caused by: org.keycloak.models.ModelException: Could not query server using DN [ou=People,dc=magnuskkarlsson,dc=se] and filter [(&(objectClass=*)(entryUUID=b8f499ce-e341-103d-9c19-dfc37858caec))]
...
Caused by: javax.naming.CommunicationException: localhost:1389 [Root exception is java.net.ConnectException: Connection refused (Connection refused)]
...
Caused by: java.net.ConnectException: Connection refused (Connection refused)
...
15:04:09,755 WARN [org.keycloak.events] (default task-44) type=LOGIN_ERROR, realmId=demo, clientId=account-console, userId=null, ipAddress=127.0.0.1, error=invalid_user_credentials, auth_method=openid-connect, auth_type=code, redirect_uri=http://localhost:8180/auth/realms/demo/account/#/, code_id=618ac086-9b7f-4bd1-b8a2-0498ab2e7390, username=john, authSessionParentId=618ac086-9b7f-4bd1-b8a2-0498ab2e7390, authSessionTabId=XLmqDMVrjkc
Or if LDAP server is slow (We have set Connection and Read timeout, this is important, since default values are infinite), the User cannot login.
15:10:14,894 WARN [org.keycloak.services] (default task-44) KC-SERVICES0013: Failed authentication: org.keycloak.models.ModelException: LDAP Query failed
...
Caused by: org.keycloak.models.ModelException: Querying of LDAP failed org.keycloak.storage.ldap.idm.query.internal.LDAPQuery@c684144
...
Caused by: org.keycloak.models.ModelException: Could not query server using DN [ou=People,dc=magnuskkarlsson,dc=se] and filter [(&(objectClass=*)(entryUUID=b8f499ce-e341-103d-9c19-dfc37858caec))]
...
Caused by: javax.naming.CommunicationException: localhost:1389 [Root exception is java.net.SocketTimeoutException: connect timed out]
...
Caused by: java.net.SocketTimeoutException: connect timed out
...
15:10:14,907 WARN [org.keycloak.events] (default task-44) type=LOGIN_ERROR, realmId=demo, clientId=account-console, userId=null, ipAddress=127.0.0.1, error=invalid_user_credentials, auth_method=openid-connect, auth_type=code, redirect_uri=http://localhost:8180/auth/realms/demo/account/#/, code_id=f68f708a-82de-4c5b-957f-9c7084374922, username=john, authSessionParentId=f68f708a-82de-4c5b-957f-9c7084374922, authSessionTabId=RyZud1gnRGs
Another side effect of not periodically import all User, is that User, that have never logged in, will not be visible in RH SSO.
And another side effect, is that User in RH SSO will never be pruned, i.e. if user is deleted in LDAP, that User will never be removed from RH SSO.
And this online dependency, can not be circumvented, by disabling user federation.
Kecloak Configuration User Federation and Different Edit Modes
WRITABLE means data will be synced back to LDAP on demand.
UNSYNCED means user data will be imported, but not synced back to LDAP.
READ_ONLY is a read-only LDAP store. "You cannot change the username, email, first name, last name, and other mapped attributes. Red Hat Single Sign-On shows an error anytime a user attempts to update these fields. Password updates are not supported."