From 3a03065f22401b9c9e2983129eea67554fe879f9 2023-11-30 23:07:05 From: Branko Majic Date: 2023-11-30 23:07:05 Subject: [PATCH] MAR-189: Refactored admin acocunt handling in the ldap_server role: - Get rid of the admin entry from the directory, and resort to using the directory's olcRootDN/olcRootPW attributes instead. Aligns Buster package deployment with Bullseye one, as implemented via fix for the following Debian bug: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=821331 - Add the helper filter plugin to deal with calculating the salted SHA1 checksum. - Drop the architecture from Molecule instance names. - Move the IPs around a tiny bit for Molecule instances. --- diff --git a/roles/ldap_server/filter_plugins/ldap_filters.py b/roles/ldap_server/filter_plugins/ldap_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..307f5301202817fce7ed8f345d4bbdcd581ba625 --- /dev/null +++ b/roles/ldap_server/filter_plugins/ldap_filters.py @@ -0,0 +1,46 @@ +# (c) 2023, Branko Majic +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + + +import hashlib +import os +import base64 + +from ansible import errors + + +def ldap_password_hash(password, salt=None): + + if not salt: + salt = os.urandom(4) + elif len(salt) != 4: + raise errors.AnsibleFilterError("When passing in salt to ldap_password_hash filter, it must be exactly 4 characters long!") + else: + salt = salt.encode("utf-8") + + hashed_password = hashlib.sha1(password.encode("utf-8")) + hashed_password.update(salt) + + return "{SSHA}" + base64.encodebytes(hashed_password.digest() + salt).strip().decode("utf-8") + + +class FilterModule(object): + + def filters(self): + return { + 'ldap_password_hash': ldap_password_hash + } diff --git a/roles/ldap_server/molecule/default/molecule.yml b/roles/ldap_server/molecule/default/molecule.yml index 74600135db5a2fa41641a0147f60cc011ad31e2d..ffd50944fb7d224a92c60362524c29eb44a8a9ae 100644 --- a/roles/ldap_server/molecule/default/molecule.yml +++ b/roles/ldap_server/molecule/default/molecule.yml @@ -24,7 +24,7 @@ platforms: network_name: private_network type: static - - name: parameters-mandatory-buster64 + - name: parameters-mandatory-buster groups: - parameters-mandatory box: debian/contrib-buster64 @@ -32,11 +32,11 @@ platforms: cpus: 1 interfaces: - auto_config: true - ip: 192.168.56.12 + ip: 192.168.56.21 network_name: private_network type: static - - name: parameters-optional-buster64 + - name: parameters-optional-buster groups: - parameters-optional - backup-server @@ -45,7 +45,7 @@ platforms: cpus: 1 interfaces: - auto_config: true - ip: 192.168.56.13 + ip: 192.168.56.22 network_name: private_network type: static diff --git a/roles/ldap_server/molecule/default/prepare.yml b/roles/ldap_server/molecule/default/prepare.yml index f8d4868ba598ee6f94f201bff14d88234e741d07..5ac944ac6dcbe6116b82e06f1a9c84f85c47ec69 100644 --- a/roles/ldap_server/molecule/default/prepare.yml +++ b/roles/ldap_server/molecule/default/prepare.yml @@ -23,9 +23,9 @@ - "{{ item.name }}" - "{{ item.fqdn }}" with_items: - - name: parameters-mandatory-buster64_ldap + - name: parameters-mandatory-buster_ldap fqdn: parameters-mandatory - - name: parameters-optional-buster64_ldap + - name: parameters-optional-buster_ldap fqdn: parameters-optional - name: Set-up link to generated X.509 material @@ -79,8 +79,8 @@ mode: 0644 state: present with_dict: - 192.168.56.12: parameters-mandatory-buster64 - 192.168.56.13: parameters-optional-buster64 + 192.168.56.21: parameters-mandatory-buster + 192.168.56.22: parameters-optional-buster - hosts: parameters-optional become: true diff --git a/roles/ldap_server/molecule/default/tests/test_default_buster.py b/roles/ldap_server/molecule/default/tests/test_default_buster.py index cca22e20ab0f9edf6c2ac822087a17542c4b831e..26b4849d28bb452875507fb344f352ea20d7cef3 100644 --- a/roles/ldap_server/molecule/default/tests/test_default_buster.py +++ b/roles/ldap_server/molecule/default/tests/test_default_buster.py @@ -4,7 +4,7 @@ import testinfra.utils.ansible_runner testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( - os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-*-buster64') + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-*-buster') def test_ldap_server_uses_correct_dh_parameters(host): diff --git a/roles/ldap_server/molecule/default/tests/test_deprecated.py b/roles/ldap_server/molecule/default/tests/test_deprecated.py new file mode 100644 index 0000000000000000000000000000000000000000..ed082e32b08b002576ebc811038c3ef6b6fb266b --- /dev/null +++ b/roles/ldap_server/molecule/default/tests/test_deprecated.py @@ -0,0 +1,21 @@ +import os + +import testinfra.utils.ansible_runner + +from helpers import parse_ldif + + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-*') + + +def test_server_admin_entry_absent(host): + """ + Tests if the explicit admin entry is absent from directory tree. + """ + + with host.sudo(): + ldapsearch = host.run('ldapsearch -H ldapi:/// -Q -LLL -Y EXTERNAL -b dc=local cn=admin dn') + + assert ldapsearch.rc == 0 + assert ldapsearch.stdout.strip() == '' diff --git a/roles/ldap_server/tasks/main.yml b/roles/ldap_server/tasks/main.yml index a3fcaf7a6bc511196c8a48c0eb15c6dbc5cb5495..757f822f69533fdc8a6785cf1feb3cae13557fcc 100644 --- a/roles/ldap_server/tasks/main.yml +++ b/roles/ldap_server/tasks/main.yml @@ -227,6 +227,11 @@ filter: "(olcSuffix={{ ldap_server_int_basedn }})" rules: "{{ ldap_permissions }}" +- name: Drop the admin entry corresponding to olcRootDN for database from directory + ldap_entry: + dn: "cn=admin,{{ ldap_server_int_basedn }}" + state: absent + - name: Create basic LDAP directory structure ldap_entry: dn: "ou={{ item }},{{ ldap_server_int_basedn }}" @@ -308,6 +313,9 @@ notify: - Restart ferm +# @TODO: This whole thing could be dropped if newer version of Ansible +# was in use (where community collection has the ldap_search +# module. - name: Deploy temporary file with LDAP admin password template: src: "ldap_admin_password.j2" @@ -324,7 +332,11 @@ failed_when: false - name: Update LDAP admin password - command: "ldappasswd -Y EXTERNAL -H ldapi:/// 'cn=admin,{{ ldap_server_int_basedn }}' -T /root/.ldap_admin_password" + ldap_attr: + dn: "olcDatabase={1}mdb,cn=config" + name: olcRootPW + values: "{{ ldap_admin_password | ldap_password_hash }}" + state: exact when: ldap_admin_password_check.rc != 0 - name: Remove temporary file with LDAP admin password