INF-162 - Enable the LDAP configuration from REST API
- Create LDAP configuration
- Create LDAP mappers
- Enable Audit logging
- Verify the LDAP Authentication in Molecule environment
- Verify the user operation, create from Keycloak and search from LDAP
Change-Id: Ie6ea7f40cfe403ee3747a30b0bfb3acc9c72057f
diff --git a/README.md b/README.md
index 29133b7..94062b6 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,9 @@
```yaml
- hosts: all
vars:
+ keycloak_ldap_userdn: "ou=people,dc=testing,dc=org"
+ keycloak_ldap_admin_dn: "cn=Your Admin DN"
+ keycloak_ldap_admin_password: "changeme"
keycloak_admin_password: changeme
keycloak_client_settings:
- name: ...
diff --git a/defaults/main.yml b/defaults/main.yml
index ba627dd..57b65e7 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -6,6 +6,8 @@
#
keycloak_java_version: 11
+keycloak_server: "http://localhost:8080"
+keycloak_admin_api: "{{ keycloak_server }}/auth/admin/realms/master"
keycloak_username: "keycloak"
keycloak_groupname: "keycloak"
keycloak_comment: "Keycloak, an identity and access management solution "
@@ -28,3 +30,16 @@
# client_settings is a list of Keycloak client to configure - see README.md
keycloak_client_settings: []
+
+# ldap configuration
+keycloak_ldap_sync_registration: "true"
+keycloak_ldap_vendor: "rhds"
+keycloak_ldap_username: "uid"
+keycloak_ldap_rdn: "uid"
+keycloak_ldap_uuid: "nsuniqueid"
+keyclaok_ldap_user_object: "inetOrgPerson,organizationalPerson"
+keycloak_ldap_server: "ldap://127.0.0.1:389"
+keycloak_ldap_userdn: "ou=people,dc=testing,dc=org"
+keycloak_ldap_admin_dn: "cn=Directory Manager"
+keycloak_ldap_admin_password: "changeme"
+keycloak_ldap_group_dn: "ou=groups,dc=testing,dc=org"
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
index 5796c8d..3e075cf 100644
--- a/molecule/default/molecule.yml
+++ b/molecule/default/molecule.yml
@@ -19,6 +19,14 @@
inventory:
host_vars:
debian-11-priv:
+ keycloak_server: "http://localhost:8080"
+ keycloak_admin_api: "{{ keycloak_server }}/auth/admin/realms/master"
+ keycloak_ldap_testing_user: "test2"
+ keycloak_ldap_server: "ldap://127.0.0.1:389"
+ keycloak_ldap_userdn: "ou=people,dc=testing,dc=org"
+ keycloak_ldap_admin_dn: "cn=Directory Manager"
+ keycloak_ldap_admin_password: "changeme"
+ keyclaok_ldap_user_object: "inetOrgPerson,organizationalPerson"
keycloak_admin_username: "admin"
keycloak_admin_password: "changeme"
keycloak_client_settings:
diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml
new file mode 100644
index 0000000..7c28828
--- /dev/null
+++ b/molecule/default/prepare.yml
@@ -0,0 +1,15 @@
+---
+# keycloak molecule/default/prepare.yml
+#
+# SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
+# SPDX-License-Identifier: Apache-2.0
+
+- name: Prepare for Keycloak by installing dependency roles
+ hosts: all
+
+ vars:
+ ds389_suffix: "dc=testing,dc=org"
+ ds389_root_password: >-
+ {SSHA512}hZUoI/9C3PeGS4nphClWw6Rg2CTX4P0S74GS3YxsmfQdeBobBEkKyUycxQ1HNB9TJHVokY9dpChFOfDPbKzmf3BbiOC1YAFI
+ roles:
+ - ds389
diff --git a/molecule/default/templates/ldap.testconnection.j2 b/molecule/default/templates/ldap.testconnection.j2
new file mode 100644
index 0000000..7b2950f
--- /dev/null
+++ b/molecule/default/templates/ldap.testconnection.j2
@@ -0,0 +1,15 @@
+{#
+SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
+SPDX-License-Identifier: Apache-2.0
+#}
+{
+ "action":"testAuthentication",
+ "connectionUrl":"{{ keycloak_ldap_server }}",
+ "authType":"simple",
+ "bindDn":"{{ keycloak_ldap_admin_dn }}",
+ "bindCredential":"{{ keycloak_ldap_admin_password }}",
+ "useTruststoreSpi":"ldapsOnly",
+ "connectionTimeout":"",
+ "startTls":"",
+ "componentId":"{{ ldap_id }}"
+}
diff --git a/molecule/default/templates/ldap.testuser.j2 b/molecule/default/templates/ldap.testuser.j2
new file mode 100644
index 0000000..faa527e
--- /dev/null
+++ b/molecule/default/templates/ldap.testuser.j2
@@ -0,0 +1,16 @@
+{#
+SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
+SPDX-License-Identifier: Apache-2.0
+#}
+{
+ "enabled":true,
+ "attributes":{
+ },
+ "groups":[
+ ],
+ "emailVerified":"",
+ "username":"{{ keycloak_ldap_testing_user }}",
+ "email":"test@test.com",
+ "firstName":"first",
+ "lastName":"last"
+}
diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml
index 74ba85a..15e0431 100644
--- a/molecule/default/verify.yml
+++ b/molecule/default/verify.yml
@@ -9,7 +9,7 @@
tasks:
- name: "Create Token for service Keycloak"
uri:
- url: http://localhost:8080/auth/realms/master/protocol/openid-connect/token
+ url: "{{ keycloak_server }}/auth/realms/master/protocol/openid-connect/token"
method: POST
body_format: form-urlencoded
body:
@@ -21,7 +21,7 @@
- name: "Get Client List"
uri:
- url: http://localhost:8080/auth/admin/realms/master/clients
+ url: "{{ keycloak_admin_api }}/clients"
method: GET
headers:
Accept: "application/json"
@@ -38,3 +38,92 @@
assert:
that:
- find is defined
+
+ - name: "Get existing LDAP configuration"
+ uri:
+ url: "{{ keycloak_admin_api }}/components?type=org.keycloak.storage.UserStorageProvider"
+ method: GET
+ headers:
+ Accept: "application/json"
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+ register: keycloak_components_list
+
+ - name: Check if the Keycloak already has the LDAP configuration
+ set_fact:
+ ldap_id: "{{ item.id }}"
+ with_items: "{{ keycloak_components_list.json }}"
+ when: item.name == "ldap"
+
+ - name: Generate a local json file for LDAP configuration
+ become: false
+ delegate_to: localhost
+ template:
+ src: "{{ item }}.j2"
+ dest: "/tmp/{{ item }}"
+ mode: "0600"
+ with_items:
+ - ldap.testconnection
+ - ldap.testuser
+
+ - name: Test LDAP Authentication
+ uri:
+ url: "{{ keycloak_admin_api }}/testLDAPConnection"
+ method: POST
+ src: /tmp/ldap.testconnection
+ status_code: [204]
+ headers:
+ Content-Type: application/json
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+
+ - name: Create user via Keycloak
+ uri:
+ url: "{{ keycloak_admin_api }}/users"
+ method: POST
+ src: /tmp/ldap.testuser
+ status_code: [201]
+ headers:
+ Content-Type: application/json
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+ register: keycloak_create_user_response
+
+ - name: Get User ID from previous response
+ set_fact:
+ user_id: "{{ keycloak_create_user_response.location | basename }}"
+
+ - name: Verify created user via LDAP
+ community.general.ldap_entry:
+ dn: "uid={{ keycloak_ldap_testing_user }},{{ keycloak_ldap_userdn }}"
+ objectClass: "{{ keyclaok_ldap_user_object }}"
+ server_uri: "{{ keycloak_ldap_server }}"
+ bind_dn: "{{ keycloak_ldap_admin_dn }}"
+ bind_pw: "{{ keycloak_ldap_admin_password }}"
+ register: result
+
+ - name: Delete user via Keycloak
+ uri:
+ url: "{{ keycloak_admin_api }}/users/{{ user_id }}"
+ method: DELETE
+ status_code: [204]
+ headers:
+ Content-Type: application/json
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+
+ - name: Verify removed user via LDAP
+ community.general.ldap_entry:
+ dn: "uid={{ keycloak_ldap_testing_user }},{{ keycloak_ldap_userdn }}"
+ objectClass: "{{ keyclaok_ldap_user_object }}"
+ server_uri: "{{ keycloak_ldap_server }}"
+ bind_dn: "{{ keycloak_ldap_admin_dn }}"
+ bind_pw: "{{ keycloak_ldap_admin_password }}"
+ register: result
+ failed_when:
+ - '"missing attribute" not in result.details'
+
+ - name: Remove local LDAP json file
+ delegate_to: localhost
+ file:
+ path: "/tmp/{{ item }}"
+ state: absent
+ with_items:
+ - ldap.testconnection
+ - ldap.testuser
diff --git a/tasks/main.yml b/tasks/main.yml
index 5722125..aa31ddb 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -57,7 +57,7 @@
chdir: "{{ keycloak_working_dir }}/bin/"
cmd: >
./kcadm.sh get users
- --server http://localhost:8080/auth
+ --server {{ keycloak_server }}/auth
--realm master --user {{ keycloak_admin_username }}
--password {{ keycloak_admin_password }}
register: kcadm_result
@@ -94,7 +94,7 @@
- name: Configure Keycloak client
community.general.keycloak_client:
- auth_keycloak_url: http://localhost:8080/auth
+ auth_keycloak_url: "{{ keycloak_server }}/auth"
auth_realm: "{{ item.auth_realm }}"
auth_username: "{{ keycloak_admin_username }}"
auth_password: "{{ keycloak_admin_password }}"
@@ -107,3 +107,112 @@
protocol_mappers: "{{ item.protocol_mappers }}"
state: present
with_items: "{{ keycloak_client_settings }}"
+
+- name: "Create Token for Keycloak service"
+ uri:
+ url: "{{ keycloak_server }}/auth/realms/master/protocol/openid-connect/token"
+ method: POST
+ body_format: form-urlencoded
+ body:
+ username: "{{ keycloak_admin_username }}"
+ password: "{{ keycloak_admin_password }}"
+ grant_type: "password"
+ client_id: "admin-cli"
+ register: keycloak_token
+
+- name: "Get existing LDAP configuration"
+ uri:
+ url: "{{ keycloak_admin_api }}/components?type=org.keycloak.storage.UserStorageProvider"
+ method: GET
+ headers:
+ Accept: "application/json"
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+ register: keycloak_components_list
+
+- name: Check if the Keycloak already has the LDAP configuration
+ set_fact:
+ ldap_id: "{{ item.id }}"
+ with_items: "{{ keycloak_components_list.json }}"
+ when: item.name == "ldap"
+
+- name: Generate a local json file for LDAP configuration
+ become: false
+ delegate_to: localhost
+ template:
+ src: "ldap.config.j2"
+ dest: "/tmp/ldap.config"
+ mode: "0600"
+ changed_when: false
+
+- name: "Create LDAP Provider if not exist"
+ uri:
+ url: "{{ keycloak_admin_api }}/components"
+ method: POST
+ src: /tmp/ldap.config
+ status_code: [201]
+ headers:
+ Content-Type: application/json
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+ register: keycloak_create_ldap_response
+ when: ldap_id is not defined
+
+- name: "Update LDAP Provider if exist"
+ uri:
+ url: "{{ keycloak_admin_api }}/components/{{ ldap_id }}"
+ method: PUT
+ src: /tmp/ldap.config
+ status_code: [204]
+ headers:
+ Content-Type: application/json
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+ when: ldap_id is defined
+
+- name: Update LDAP_ID with new created LDAP components
+ set_fact:
+ ldap_id: "{{ keycloak_create_ldap_response.location | basename }}"
+ when: ldap_id is not defined
+
+- name: Generate a local json file for LDAP mapper configuration
+ become: false
+ delegate_to: localhost
+ template:
+ src: "{{ item }}.j2"
+ dest: "/tmp/{{ item }}"
+ mode: "0600"
+ with_items:
+ - ldap.mapper.group
+ - keycloak.event.config
+ changed_when: false
+
+- name: Create LDAP mapper from local json configuraiton
+ uri:
+ url: "{{ keycloak_admin_api }}/components/"
+ method: POST
+ src: "/tmp/{{ item }}"
+ status_code: [201]
+ headers:
+ Content-Type: application/json
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+ with_items:
+ - ldap.mapper.group
+
+- name: Create LDAP mapper from local json configuraiton
+ uri:
+ url: "{{ keycloak_admin_api }}/events/config"
+ method: PUT
+ src: "/tmp/keycloak.event.config"
+ status_code: [204]
+ headers:
+ Content-Type: application/json
+ Authorization: "Bearer {{ keycloak_token.json.access_token }}"
+
+- name: Remove local LDAP json file
+ delegate_to: localhost
+ file:
+ path: "/tmp/{{ item }}"
+ state: absent
+ with_items:
+ - ldap.config
+ - ldap.mapper.group
+ - keycloak.event.config
+ changed_when: false
diff --git a/templates/keycloak.event.config.j2 b/templates/keycloak.event.config.j2
new file mode 100644
index 0000000..d13367d
--- /dev/null
+++ b/templates/keycloak.event.config.j2
@@ -0,0 +1,93 @@
+{#
+SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
+SPDX-License-Identifier: Apache-2.0
+#}
+{
+ "eventsEnabled":true,
+ "eventsListeners":[
+ "jboss-logging"
+ ],
+ "enabledEventTypes":[
+ "SEND_RESET_PASSWORD",
+ "UPDATE_CONSENT_ERROR",
+ "GRANT_CONSENT",
+ "VERIFY_PROFILE_ERROR", "REMOVE_TOTP",
+ "REVOKE_GRANT",
+ "UPDATE_TOTP",
+ "LOGIN_ERROR",
+ "CLIENT_LOGIN",
+ "RESET_PASSWORD_ERROR",
+ "IMPERSONATE_ERROR",
+ "CODE_TO_TOKEN_ERROR",
+ "CUSTOM_REQUIRED_ACTION",
+ "OAUTH2_DEVICE_CODE_TO_TOKEN_ERROR",
+ "RESTART_AUTHENTICATION",
+ "IMPERSONATE",
+ "UPDATE_PROFILE_ERROR",
+ "LOGIN",
+ "OAUTH2_DEVICE_VERIFY_USER_CODE",
+ "UPDATE_PASSWORD_ERROR",
+ "CLIENT_INITIATED_ACCOUNT_LINKING",
+ "TOKEN_EXCHANGE",
+ "AUTHREQID_TO_TOKEN",
+ "LOGOUT",
+ "REGISTER",
+ "DELETE_ACCOUNT_ERROR",
+ "CLIENT_REGISTER",
+ "IDENTITY_PROVIDER_LINK_ACCOUNT",
+ "DELETE_ACCOUNT",
+ "UPDATE_PASSWORD",
+ "CLIENT_DELETE",
+ "FEDERATED_IDENTITY_LINK_ERROR",
+ "IDENTITY_PROVIDER_FIRST_LOGIN",
+ "CLIENT_DELETE_ERROR",
+ "VERIFY_EMAIL",
+ "CLIENT_LOGIN_ERROR",
+ "RESTART_AUTHENTICATION_ERROR",
+ "EXECUTE_ACTIONS",
+ "REMOVE_FEDERATED_IDENTITY_ERROR",
+ "TOKEN_EXCHANGE_ERROR",
+ "PERMISSION_TOKEN",
+ "SEND_IDENTITY_PROVIDER_LINK_ERROR",
+ "EXECUTE_ACTION_TOKEN_ERROR",
+ "SEND_VERIFY_EMAIL",
+ "OAUTH2_DEVICE_AUTH",
+ "EXECUTE_ACTIONS_ERROR",
+ "REMOVE_FEDERATED_IDENTITY",
+ "OAUTH2_DEVICE_CODE_TO_TOKEN",
+ "IDENTITY_PROVIDER_POST_LOGIN",
+ "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR",
+ "OAUTH2_DEVICE_VERIFY_USER_CODE_ERROR",
+ "UPDATE_EMAIL",
+ "REGISTER_ERROR",
+ "REVOKE_GRANT_ERROR",
+ "EXECUTE_ACTION_TOKEN",
+ "LOGOUT_ERROR",
+ "UPDATE_EMAIL_ERROR",
+ "CLIENT_UPDATE_ERROR",
+ "AUTHREQID_TO_TOKEN_ERROR",
+ "UPDATE_PROFILE",
+ "CLIENT_REGISTER_ERROR",
+ "FEDERATED_IDENTITY_LINK",
+ "SEND_IDENTITY_PROVIDER_LINK",
+ "SEND_VERIFY_EMAIL_ERROR",
+ "RESET_PASSWORD",
+ "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR",
+ "OAUTH2_DEVICE_AUTH_ERROR",
+ "UPDATE_CONSENT",
+ "REMOVE_TOTP_ERROR",
+ "VERIFY_EMAIL_ERROR",
+ "SEND_RESET_PASSWORD_ERROR",
+ "CLIENT_UPDATE",
+ "CUSTOM_REQUIRED_ACTION_ERROR",
+ "IDENTITY_PROVIDER_POST_LOGIN_ERROR",
+ "UPDATE_TOTP_ERROR",
+ "CODE_TO_TOKEN",
+ "VERIFY_PROFILE",
+ "GRANT_CONSENT_ERROR",
+ "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR"
+ ],
+ "adminEventsEnabled":true,
+ "adminEventsDetailsEnabled":true,
+ "eventsExpiration":null
+}
diff --git a/templates/ldap.config.j2 b/templates/ldap.config.j2
new file mode 100644
index 0000000..81a87b3
--- /dev/null
+++ b/templates/ldap.config.j2
@@ -0,0 +1,138 @@
+{#
+SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
+SPDX-License-Identifier: Apache-2.0
+#}
+
+{
+{% if ldap_id is defined %}
+ "id": "{{ ldap_id}}",
+{% endif %}
+ "name":"ldap",
+ "providerId":"ldap",
+ "providerType":"org.keycloak.storage.UserStorageProvider",
+ "parentId":"master",
+ "config":{
+ "enabled":[
+ "true"
+ ],
+ "priority":[
+ "0"
+ ],
+ "fullSyncPeriod":[
+ "-1"
+ ],
+ "changedSyncPeriod":[
+ "-1"
+ ],
+ "cachePolicy":[
+ "DEFAULT"
+ ],
+ "evictionDay":[
+ ],
+ "evictionHour":[
+ ],
+ "evictionMinute":[
+ ],
+ "maxLifespan":[
+ ],
+ "batchSizeForSync":[
+ "1000"
+ ],
+ "editMode":[
+ "WRITABLE"
+ ],
+ "importEnabled":[
+ "true"
+ ],
+ "syncRegistrations":[
+ "{{ keycloak_ldap_sync_registration }}"
+ ],
+ "vendor":[
+ "{{ keycloak_ldap_vendor }}"
+ ],
+ "usePasswordModifyExtendedOp":[
+ ],
+ "usernameLDAPAttribute":[
+ "{{ keycloak_ldap_username }}"
+ ],
+ "rdnLDAPAttribute":[
+ "{{ keycloak_ldap_rdn }}"
+ ],
+ "uuidLDAPAttribute":[
+ "{{ keycloak_ldap_uuid }}"
+ ],
+ "userObjectClasses":[
+ "{{ keyclaok_ldap_user_object }}"
+ ],
+ "connectionUrl":[
+ "{{ keycloak_ldap_server }}"
+ ],
+ "usersDn":[
+ "{{ keycloak_ldap_userdn }}"
+ ],
+ "authType":[
+ "simple"
+ ],
+ "startTls":[
+ ],
+ "bindDn":[
+ "{{ keycloak_ldap_admin_dn }}"
+ ],
+ "bindCredential":[
+ "{{ keycloak_ldap_admin_password }}"
+ ],
+ "customUserSearchFilter":[
+ ],
+ "searchScope":[
+ "1"
+ ],
+ "validatePasswordPolicy":[
+ "false"
+ ],
+ "trustEmail":[
+ "false"
+ ],
+ "useTruststoreSpi":[
+ "ldapsOnly"
+ ],
+ "connectionPooling":[
+ "true"
+ ],
+ "connectionPoolingAuthentication":[
+ ],
+ "connectionPoolingDebug":[
+ ],
+ "connectionPoolingInitSize":[
+ ],
+ "connectionPoolingMaxSize":[
+ ],
+ "connectionPoolingPrefSize":[
+ ],
+ "connectionPoolingProtocol":[
+ ],
+ "connectionPoolingTimeout":[
+ ],
+ "connectionTimeout":[
+ ],
+ "readTimeout":[
+ ],
+ "pagination":[
+ "true"
+ ],
+ "allowKerberosAuthentication":[
+ "false"
+ ],
+ "serverPrincipal":[
+ ],
+ "keyTab":[
+ ],
+ "kerberosRealm":[
+ ],
+ "debug":[
+ "false"
+ ],
+ "useKerberosForPasswordAuthentication":[
+ "false"
+ ]
+ }
+}
diff --git a/templates/ldap.mapper.group.j2 b/templates/ldap.mapper.group.j2
new file mode 100644
index 0000000..c8e2269
--- /dev/null
+++ b/templates/ldap.mapper.group.j2
@@ -0,0 +1,55 @@
+{#
+SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
+SPDX-License-Identifier: Apache-2.0
+#}
+{
+ "config":{
+ "groups.dn":[
+ "{{ keycloak_ldap_group_dn }}"
+ ],
+ "group.name.ldap.attribute":[
+ "cn"
+ ],
+ "group.object.classes":[
+ "groupOfNames"
+ ],
+ "preserve.group.inheritance":[
+ "true"
+ ],
+ "ignore.missing.groups":[
+ "false"
+ ],
+ "membership.ldap.attribute":[
+ "member"
+ ],
+ "membership.attribute.type":[
+ "DN"
+ ],
+ "membership.user.ldap.attribute":[
+ "uid"
+ ],
+ "groups.ldap.filter":[
+ ],
+ "mode":[
+ "LDAP_ONLY"
+ ],
+ "user.roles.retrieve.strategy":[
+ "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE"
+ ],
+ "memberof.ldap.attribute":[
+ "memberOf"
+ ],
+ "mapped.group.attributes":[
+ ],
+ "drop.non.existing.groups.during.sync":[
+ "true"
+ ],
+ "groups.path":[
+ "/"
+ ]
+ },
+ "name":"group",
+ "providerId":"group-ldap-mapper",
+ "providerType":"org.keycloak.storage.ldap.mappers.LDAPStorageMapper",
+ "parentId":"{{ ldap_id }}"
+}