From a362e1cb5ee9bbdd169ea570889aca67aabce505 Mon Sep 17 00:00:00 2001 From: Brian Veltman <2551674+brianveltman@users.noreply.github.com> Date: Thu, 21 Nov 2024 02:11:36 -1000 Subject: [PATCH] Add LDAP support and configuration - Introduced `ldap_connections` variable for LDAP settings. - Added mock LDAP server setup in the test environment. - Created tasks to manage LDAP connections via Nexus API. - Included new tasks for creating, updating, and deleting LDAP configurations. - Ensured desired order of LDAP connections. --- defaults/main.yml | 2 + molecule/default/group_vars/all.yml | 62 ++++++++++++ molecule/default/molecule.yml | 12 +++ tasks/ldap-api.yml | 27 ++++++ tasks/ldap-tasks.yml | 141 ++++++++++++++++++++++++++++ tasks/main.yml | 4 + vars/main.yml | 1 + 7 files changed, 249 insertions(+) create mode 100644 tasks/ldap-api.yml create mode 100644 tasks/ldap-tasks.yml diff --git a/defaults/main.yml b/defaults/main.yml index 59c1883..367a35d 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -8,6 +8,8 @@ nexus_enable_pro: false nexus_ssl_truststore: [] +ldap_connections: [] + # Nexus Security Realms # # Each realm will be activated and configured in the same order as you listed. diff --git a/molecule/default/group_vars/all.yml b/molecule/default/group_vars/all.yml index b692dd1..0acbea1 100644 --- a/molecule/default/group_vars/all.yml +++ b/molecule/default/group_vars/all.yml @@ -7,6 +7,68 @@ nexus_admin_username: admin nexus_admin_password: changeme nexus_enable_pro: true +ldap_connections: + - name: Mock ldap server STATIC # Required + protocol: LDAPS # Required. Values: "LDAP" or "LDAPS" + host: "{{ nexus_hostname }}" # Required + searchBase: dc=slapd-server-mock # Required + port: 389 # Required + groupType: STATIC # Required if ldapGroupsAsRoles is true. Values: "DYNAMIC" or "STATIC" + groupObjectClass: posixGroup # Required if groupType is static + groupIdAttribute: cn # Required if groupType is static + groupMemberAttribute: memberUID # Required if groupType is static + groupMemberFormat: ${username} # Required if groupType is static + #userMemberOfAttribute: memberOf # Required if groupType is dynamic + authScheme: "NONE" # Required. Values: "NONE","SIMPLE","DIGEST_MD5" or "CRAM_MD5" + #authRealm: # Required if authScheme is CRAM_MD5 or DIGEST_MD5 + #authUsername: # Required if authScheme other than NONE + #authPassword: # Required if authScheme other than NONE + connectionTimeoutSeconds: 30 # Required + connectionRetryDelaySeconds: 300 # Required + maxIncidentsCount: 3 # Required + useTrustStore: true + userBaseDn: ou=people + userLdapFilter: (|(mail=*@example.com)(uid=dom*)) + userIdAttribute: guid + userRealNameAttribute: usrPrincipalName + userEmailAddressAttribute: email + userPasswordAttribute: pswd + userObjectClass: person + ldapGroupsAsRoles: true + groupBaseDn: ou=groups + userSubtree: false + groupSubtree: false + - name: Mock ldap server DYNAMIC # Required + protocol: LDAP # Required. Values: "LDAP" or "LDAPS" + host: "{{ nexus_hostname }}" # Required + searchBase: dc=slapd-server-mock # Required + port: 389 # Required + groupType: DYNAMIC # Required if ldapGroupsAsRoles is true. Values: "DYNAMIC" or "STATIC" + #groupObjectClass: posixGroup # Required if groupType is static + #groupIdAttribute: cn # Required if groupType is static + #groupMemberAttribute: memberUID # Required if groupType is static + #groupMemberFormat: ${username} # Required if groupType is static + userMemberOfAttribute: memberOf # Required if groupType is dynamic + authScheme: "NONE" # Required. Values: "NONE","SIMPLE","DIGEST_MD5" or "CRAM_MD5" + #authRealm: # Required if authScheme is CRAM_MD5 or DIGEST_MD5 + #authUsername: # Required if authScheme other than NONE + #authPassword: # Required if authScheme other than NONE + connectionTimeoutSeconds: 30 # Required + connectionRetryDelaySeconds: 300 # Required + maxIncidentsCount: 3 # Required + # useTrustStore: false + #userBaseDn: ou=people + #userLdapFilter: (|(mail=*@example.com)(uid=dom*)) + userIdAttribute: guid # Required + userRealNameAttribute: usrPrincipalName # Required + userEmailAddressAttribute: email # Required + #userPasswordAttribute: pswd + userObjectClass: person # Required + ldapGroupsAsRoles: true + #groupBaseDn: "ou=devs" + #userSubtree: false + #groupSubtree: false + nexus_ssl_truststore: certificates: - name: | diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml index c721b28..b8d4bee 100644 --- a/molecule/default/molecule.yml +++ b/molecule/default/molecule.yml @@ -47,6 +47,18 @@ platforms: - nexus networks: *nexus_networks + - name: slapd-server-mock + hostname: slapd-server-mock + image: thoteam/slapd-server-mock:latest + override_command: false + pull: true + pre_build_image: true + env: + LDAP_DOMAIN: slapd-server-mock + groups: + - mockldap + networks: *nexus_networks + provisioner: name: ansible options: diff --git a/tasks/ldap-api.yml b/tasks/ldap-api.yml new file mode 100644 index 0000000..d7abf46 --- /dev/null +++ b/tasks/ldap-api.yml @@ -0,0 +1,27 @@ +--- +- name: Construct API url + ansible.builtin.set_fact: + api_url: > + {% if method in ['POST'] %} + {{ nexus_protocol }}://{{ nexus_hostname }}:{{ nexus_port }}/service/rest/v1/security/ldap + {% elif method in ['PUT', 'DELETE'] %} + {{ nexus_protocol }}://{{ nexus_hostname }}:{{ nexus_port }}/service/rest/v1/security/ldap/{{ item.name | urlencode }} + {% endif %} + tags: ldap + +- name: "{{ method }} {{ item.name }} LDAP connection" + ansible.builtin.uri: + url: "{{ api_url }}" + method: "{{ method }}" + validate_certs: false + user: "{{ nexus_admin_username }}" + password: "{{ nexus_admin_password }}" + force_basic_auth: true + body: "{{ item | combine({'id': item.id}) | to_json if method == 'PUT' else item | to_json }}" + status_code: "{{ '201' if method == 'POST' else '204' if method in ['PUT', 'DELETE'] else '200' }}" + headers: + Accept: "application/json" + Content-Type: "application/json" + register: __nexus_ldap_config_update__ + changed_when: true + tags: ldap diff --git a/tasks/ldap-tasks.yml b/tasks/ldap-tasks.yml new file mode 100644 index 0000000..c085cf5 --- /dev/null +++ b/tasks/ldap-tasks.yml @@ -0,0 +1,141 @@ +--- +- name: Get all LDAP configurations + ansible.builtin.uri: + url: "{{ nexus_protocol }}://{{ nexus_hostname }}:{{ nexus_port }}/service/rest/v1/security/ldap" + method: GET + validate_certs: false + user: "{{ nexus_admin_username }}" + password: "{{ nexus_admin_password }}" + force_basic_auth: true + headers: + Accept: "application/json" + status_code: 200 + register: __nexus_ldap_config__ + tags: ldap + +- name: Set fact for current_nexus_ldap_config + ansible.builtin.set_fact: + current_nexus_ldap_config: "{{ __nexus_ldap_config__.json }}" + when: __nexus_ldap_config__.status == 200 + tags: ldap + +- name: Set fact for desired_nexus_ldap_config + ansible.builtin.set_fact: + desired_nexus_ldap_config: "{{ ldap_connections }}" + tags: ldap + +- name: Determine LDAP connections to be created + ansible.builtin.set_fact: + nxs_create_ldap_connetions: "{{ ldap_connections | rejectattr('name', 'in', current_nexus_ldap_config | map(attribute='name') | list) | list }}" + tags: ldap + +- name: Determine LDAP connections to be deleted + ansible.builtin.set_fact: + nxs_delete_ldap_connetions: "{{ current_nexus_ldap_config | rejectattr('name', 'in', ldap_connections | map(attribute='name') | list) | list }}" + tags: ldap + +- name: Compare LDAP config for changes if ldap_connections is not empty + when: current_nexus_ldap_config | length > 0 + block: + - name: Compare LDAP config for changes + ansible.builtin.set_fact: + nxs_update_ldap_connections: "{{ nxs_update_ldap_connections + [item | combine({'id': (current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).id})] }}" + loop: "{{ ldap_connections | list }}" + when: > + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first) is not defined or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).protocol | default(omit) != item.protocol | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).host | default(omit) != item.host | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).searchBase | default(omit) != item.searchBase | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).port | default(omit) != item.port | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).groupType | default(omit) != item.groupType | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).groupObjectClass | default(omit) != item.groupObjectClass | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).groupIdAttribute | default(omit) != item.groupIdAttribute | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).groupMemberAttribute | default(omit) != item.groupMemberAttribute | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).groupMemberFormat | default(omit) != item.groupMemberFormat | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userMemberOfAttribute | default(omit) != item.userMemberOfAttribute | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).authScheme | default(omit) != item.authScheme | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).authRealm | default(omit) != item.authRealm | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).authUsername | default(omit) != item.authUsername | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).authPassword | default(omit) != item.authPassword | default(omit) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).useTrustStore | default(false) != item.useTrustStore | default(false) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).connectionTimeoutSeconds | default(omit) != item.connectionTimeoutSeconds | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).connectionRetryDelaySeconds | default(omit) != item.connectionRetryDelaySeconds | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).maxIncidentsCount | default(omit) != item.maxIncidentsCount | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userBaseDn | default(omit) != item.userBaseDn | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userLdapFilter | default(omit) != item.userLdapFilter | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userIdAttribute | default(omit) != item.userIdAttribute | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userRealNameAttribute | default(omit) != item.userRealNameAttribute | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userEmailAddressAttribute | default(omit) != item.userEmailAddressAttribute | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userPasswordAttribute | default(omit) != item.userPasswordAttribute | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userObjectClass | default(omit) != item.userObjectClass | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).ldapGroupsAsRoles | default(false) != item.ldapGroupsAsRoles | default(false) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).groupBaseDn | default(omit) != item.groupBaseDn | default(None) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).userSubtree | default(false) != item.userSubtree | default(false) or + ( current_nexus_ldap_config | selectattr('name', 'equalto', item.name) | first).groupSubtree | default(false) != item.groupSubtree | default(false) + tags: ldap + +- name: Show LDAP connections to be created + ansible.builtin.debug: + var: nxs_create_ldap_connetions | length + tags: ldap + +- name: Show LDAP connections to be updated + ansible.builtin.debug: + var: nxs_update_ldap_connections + tags: ldap + +- name: Show LDAP connections to be deleted + ansible.builtin.debug: + var: nxs_delete_ldap_connetions | length + tags: ldap + +- name: Create configured LDAP connections using Nexus API + ansible.builtin.include_tasks: ldap-api.yml + vars: + ldap_connections: "{{ item | default([]) }}" + method: POST + with_items: + - "{{ nxs_create_ldap_connetions | default([]) }}" + when: nxs_create_ldap_connetions | length > 0 + tags: ldap + +- name: Update configured LDAP connections using Nexus API + ansible.builtin.include_tasks: ldap-api.yml + vars: + ldap_connections: "{{ item | default([]) }}" + method: PUT + with_items: + - "{{ nxs_update_ldap_connections | default([]) }}" + when: nxs_update_ldap_connections | length > 0 + tags: ldap + +- name: Delete unconfigured LDAP connections using Nexus API + ansible.builtin.include_tasks: ldap-api.yml + vars: + ldap_connections: "{{ item | default([]) }}" + method: DELETE + with_items: + - "{{ nxs_delete_ldap_connetions | default([]) }}" + when: nxs_delete_ldap_connetions | length > 0 + tags: ldap + +- name: Ensure LDAP connections are in desired order + ansible.builtin.uri: + url: "{{ nexus_protocol }}://{{ nexus_hostname }}:{{ nexus_port }}/service/rest/v1/security/ldap/change-order" + method: POST + validate_certs: false + user: "{{ nexus_admin_username }}" + password: "{{ nexus_admin_password }}" + force_basic_auth: true + headers: + Accept: "application/json" + Content-Type: "application/json" + status_code: 204 + body: "{{ ldap_connections | map(attribute='name') | to_json }}" + register: __nexus_ldap_order__ + # The API always returns 204, even if the order is not changed + # So we need to check if the order is changed to be idempotent + changed_when: > + ldap_connections | map(attribute='name') | list != current_nexus_ldap_config | map(attribute='name') | list + when: ldap_connections | length > 0 + tags: ldap diff --git a/tasks/main.yml b/tasks/main.yml index f585eea..1f49416 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -17,6 +17,10 @@ ansible.builtin.include_tasks: ssl-truststore-api.yml tags: ssl-truststore +- name: Include LDAP configuration + ansible.builtin.include_tasks: ldap-tasks.yml + tags: ldap + - name: Include security-realms-api ansible.builtin.include_tasks: security-realms-api.yml tags: security-realms diff --git a/vars/main.yml b/vars/main.yml index a52557c..544b627 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -35,6 +35,7 @@ nxs_update_conan_proxy_repos: [] nxs_update_conan_hosted_repos: [] nxs_update_go_proxy_repos: [] nxs_update_go_group_repos: [] +nxs_update_ldap_connections: [] nxs_update_cleanuppolicies: [] nxs_update_routingrules: [] nxs_update_users: []