From 1eb046a8db33ee04325e797f350e46dc7de8341c Mon Sep 17 00:00:00 2001 From: cleberb Date: Thu, 19 Oct 2023 17:25:59 -0300 Subject: [PATCH 1/6] PASSWORD Improvements - Add options: - faillock_enable - faillock - admin_group - audit - deny - dir - even_deny_root - fail_interval - local_users_only - no_log_info - nodelay - root_unlock_time - silent - unlock_time - login_defs - login_retries - login_timeout - pass_max_days - pass_min_days - pass_warn_age - password_remember - Change options: - pwquality - dcredit - dictcheck - dictpath - difok - enforce_for_root - enforcing - gecoscheck - lcredit - local_users_only - maxclassrepeat - maxrepeat - minclass - minlen - ocredit - retry - ucredit - usercheck - usersubstr - Improvements of password algorithm - Add e change templates - Remove pam_tally2 - Verify hash of passwords with mkpasswd with yescrypt support --- README.md | 41 +++- defaults/main/password.yml | 30 ++- defaults/main/templates.yml | 6 +- molecule/default/verify.yml | 175 +++++++++++++++--- tasks/facts.yml | 33 ++++ tasks/logindefs.yml | 1 + tasks/password.yml | 107 +++++------ .../etc/audit/rules.d/hardening.rules.j2 | 2 +- templates/etc/login.defs.j2 | 17 +- templates/etc/pam.d/common-account.j2 | 6 +- templates/etc/pam.d/common-auth.j2 | 3 +- templates/etc/pam.d/common-password.j2 | 7 +- templates/etc/security/faillock.conf.j2 | 35 ++++ templates/etc/security/pwquality.conf.j2 | 29 +++ 14 files changed, 383 insertions(+), 109 deletions(-) create mode 100644 templates/etc/security/faillock.conf.j2 create mode 100644 templates/etc/security/pwquality.conf.j2 diff --git a/README.md b/README.md index 3e47fd75..d2acb721 100644 --- a/README.md +++ b/README.md @@ -434,22 +434,57 @@ and packages to be removed (`packages_blocklist`). ### ./defaults/main/password.yml ```yaml -pwquality_config: +faillock_enable: true +faillock: + admin_group: [] + audit: true + deny: 5 + dir: /var/run/faillock + even_deny_root: true + fail_interval: 900 + local_users_only: true + no_log_info: false + nodelay: true + root_unlock_time: 600 + silent: false + unlock_time: 600 +login_defs: + login_retries: 5 + login_timeout: 60 + pass_max_days: 60 + pass_min_days: 1 + pass_warn_age: 7 +password_remember: 5 +pwquality: dcredit: -1 dictcheck: 1 + dictpath: 1 difok: 8 + enforce_for_root: true enforcing: 1 + gecoscheck: 1 lcredit: -1 + local_users_only: true maxclassrepeat: 4 maxrepeat: 3 minclass: 4 minlen: 15 ocredit: -1 + retry: 3 ucredit: -1 + usercheck: 1 + usersubstr: 3 ``` -Configure the [libpwquality](https://manpages.ubuntu.com/manpages/jammy/man5/pwquality.conf.5.html) -library. +`faillock_enable` set to `false` for disable faillock library. + +`password_remember` set the size of the password history that the user will not be able to reuse. + +Configure the [pam_faillock](https://manpages.ubuntu.com/manpages/lunar/en/man5/faillock.conf.5.html) library. + +Configure the [login.defs](https://manpages.ubuntu.com/manpages/lunar/en/man5/login.defs.5.html) configuration. + +Configure the [libpwquality](https://manpages.ubuntu.com/manpages/jammy/man5/pwquality.conf.5.html) library. ### ./defaults/main/sshd.yml diff --git a/defaults/main/password.yml b/defaults/main/password.yml index 3df48997..bf06e2c4 100644 --- a/defaults/main/password.yml +++ b/defaults/main/password.yml @@ -1,13 +1,41 @@ --- -pwquality_config: +faillock_enable: true +faillock: + admin_group: [] + audit: true + deny: 5 + dir: /var/run/faillock + even_deny_root: true + fail_interval: 900 + local_users_only: true + no_log_info: false + nodelay: true + root_unlock_time: 600 + silent: false + unlock_time: 600 +login_defs: + login_retries: 5 + login_timeout: 60 + pass_max_days: 60 + pass_min_days: 1 + pass_warn_age: 7 +password_remember: 5 +pwquality: dcredit: -1 dictcheck: 1 + dictpath: 1 difok: 8 + enforce_for_root: true enforcing: 1 + gecoscheck: 1 lcredit: -1 + local_users_only: true maxclassrepeat: 4 maxrepeat: 3 minclass: 4 minlen: 15 ocredit: -1 + retry: 3 ucredit: -1 + usercheck: 1 + usersubstr: 3 diff --git a/defaults/main/templates.yml b/defaults/main/templates.yml index 70cbf1e8..be00659a 100644 --- a/defaults/main/templates.yml +++ b/defaults/main/templates.yml @@ -4,6 +4,7 @@ common_account_template: etc/pam.d/common-account.j2 common_auth_template: etc/pam.d/common-auth.j2 common_password_template: etc/pam.d/common-password.j2 coredump_conf_template: etc/systemd/coredump.conf.j2 +faillock_conf_template: etc/security/faillock.conf.j2 hardening_rules_template: etc/audit/rules.d/hardening.rules.j2 hosts_allow_template: etc/hosts.allow.j2 hosts_deny_template: etc/hosts.deny.j2 @@ -11,11 +12,12 @@ initpath_sh_template: etc/profile.d/initpath.sh.j2 issue_template: etc/issue.j2 journald_conf_template: etc/systemd/journald.conf.j2 limits_conf_template: etc/security/limits.conf.j2 -logind_conf_template: etc/systemd/logind.conf.j2 login_defs_template: etc/login.defs.j2 login_template: etc/pam.d/login.j2 +logind_conf_template: etc/systemd/logind.conf.j2 logrotate_conf_template: etc/logrotate.conf.j2 motd_template: etc/motd.j2 +pwquality_conf_template: etc/security/pwquality.conf.j2 resolved_conf_template: etc/systemd/resolved.conf.j2 rkhunter_template: etc/default/rkhunter.j2 ssh_config_template: etc/ssh/ssh_config.j2 @@ -23,5 +25,5 @@ sshd_config_template: etc/ssh/sshd_config.j2 system_conf_template: etc/systemd/system.conf.j2 timesyncd_conf_template: etc/systemd/timesyncd.conf.j2 tmp_mount_template: etc/systemd/tmp.mount.j2 -useradd_template: etc/default/useradd.j2 user_conf_template: etc/systemd/user.conf.j2 +useradd_template: etc/default/useradd.j2 diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index f431ff3a..7f8252e6 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -471,6 +471,39 @@ failed_when: mounts_tmp.rc != 0 changed_when: mounts_tmp.rc != 0 + - name: Get PAM version + tags: + - fact + - common-account + - common-auth + - pam + block: + - name: Gather package facts + ansible.builtin.package_facts: + + - name: Get libpam version in Debian family + ansible.builtin.set_fact: + pam_version: "{{ ansible_facts.packages['libpam-modules'][0].version }}" + when: + - ansible_os_family == "Debian" + - "'libpam-modules' in ansible_facts.packages" + + - name: Get libpam version in RedHat family + ansible.builtin.set_fact: + pam_version: "{{ ansible_facts.packages['pam'][0].version }}" + when: + - ansible_os_family == "RedHat" + - "'pam' in ansible_facts.packages" + + - name: Set hashing algorithm for password (yescrypt|sha512) + ansible.builtin.set_fact: + password_algorithm: "{{ 'yescrypt' if (pam_version is version('1.4.0', '>=')) else 'sha512' }}" + tags: + - fact + - pam + - CIS-UBUNTU2204-5.4.4 + - M1041 + - name: Verify login.defs settings become: true ansible.builtin.shell: grep "^{{ item }}$" /etc/login.defs @@ -478,12 +511,14 @@ failed_when: login_defs.rc != 0 changed_when: login_defs.rc != 0 with_items: - - ENCRYPT_METHOD SHA512 + - ENCRYPT_METHOD {{ password_algorithm | upper }} - FAILLOG_ENAB yes - LOG_OK_LOGINS yes - - PASS_MAX_DAYS 60 - - PASS_MIN_DAYS 1 - - PASS_WARN_AGE 7 + - LOGIN_RETRIES {{ login_defs.login_retries }} + - LOGIN_TIMEOUT {{ login_defs.login_timeout }} + - PASS_MAX_DAYS {{ login_defs.pass_max_days }} + - PASS_MIN_DAYS {{ login_defs.pass_min_days }} + - PASS_WARN_AGE {{ login_defs.pass_warn_age }} - SHA_CRYPT_MAX_ROUNDS 65536 - SHA_CRYPT_MIN_ROUNDS 10000 - SU_NAME su @@ -491,19 +526,45 @@ - SYSLOG_SU_ENAB yes - UMASK {{ umask_value }} + - name: Set pwquality_parameters + ansible.builtin.set_fact: + pwquality_parameters: + - dcredit = {{ pwquality.dcredit }} + - dictcheck = {{ pwquality.dictcheck }} + - dictpath = {{ pwquality.dictpath }} + - difok = {{ pwquality.difok }} + - >- + {%- if (pwquality.enforce_for_root | bool) -%} + enforce_for_root + {%- endif -%} + - enforcing = {{ pwquality.enforcing }} + - gecoscheck = {{ pwquality.gecoscheck }} + - lcredit = {{ pwquality.lcredit }} + - >- + {%- if (pwquality.local_users_only | bool) -%} + local_users_only + {%- endif -%} + - maxclassrepeat = {{ pwquality.maxclassrepeat }} + - maxrepeat = {{ pwquality.maxrepeat }} + - minclass = {{ pwquality.minclass }} + - minlen = {{ pwquality.minlen }} + - ocredit = {{ pwquality.ocredit }} + - retry = {{ pwquality.retry }} + - ucredit = {{ pwquality.ucredit }} + - usercheck = {{ pwquality.usercheck }} + - usersubstr = {{ pwquality.usersubstr }} + - name: Verify pwquality.conf settings become: true ansible.builtin.lineinfile: - path: /etc/security/pwquality.conf - line: "{{ item.key }} = {{ item.value }}" - state: present + dest: /etc/security/pwquality.conf mode: "0644" - owner: root - group: root + state: present + line: "{{ item }}" check_mode: true - register: pwquality_conf - failed_when: pwquality_conf is changed - with_dict: "{{ pwquality_config }}" + register: verify_pwquality + failed_when: verify_pwquality is changed + loop: "{{ pwquality_parameters | select() }}" - name: Verify RedHat GRUB audit settings become: true @@ -725,6 +786,53 @@ path: /etc/security/faillock.conf register: faillockconf + - name: Get PAM version + when: faillockconf.stat.exists + tags: + - common-account + - common-auth + - pam + block: + - name: Gather package facts + ansible.builtin.package_facts: + + - name: Get libpam version in Debian family + ansible.builtin.set_fact: + pam_version: "{{ ansible_facts.packages['libpam-modules'][0].version }}" + when: + - ansible_os_family == "Debian" + - "'libpam-modules' in ansible_facts.packages" + + - name: Get libpam version in RedHat family + ansible.builtin.set_fact: + pam_version: "{{ ansible_facts.packages['pam'][0].version }}" + when: + - ansible_os_family == "RedHat" + - "'pam' in ansible_facts.packages" + + - name: Set faillock_parameters + ansible.builtin.set_fact: + faillock_parameters: + - dir = {{ faillock.dir }} + - "{{ 'audit' if (faillock.audit | bool) }}" + - "{{ 'silent' if (faillock.silent | bool) }}" + - "{{ 'no_log_info' if (faillock.no_log_info | bool) }}" + - "{{ 'local_users_only' if (faillock.local_users_only | bool) }}" + - >- + {%- if (pam_version is version('1.5.1', '>=')) -%} + {{ 'nodelay' if (faillock.nodelay | bool) }} + {%- endif -%} + - deny = {{ faillock.deny }} + - fail_interval = {{ faillock.fail_interval }} + - unlock_time = {{ faillock.unlock_time }} + - "{{ 'even_deny_root' if (faillock.even_deny_root | bool) }}" + - root_unlock_time = {{ faillock.root_unlock_time }} + - >- + {%- for group in faillock.admin_group -%} + admin_group = {{ group }} + {%- endfor -%} + when: faillockconf.stat.exists + - name: Verify faillock.conf become: true ansible.builtin.lineinfile: @@ -735,11 +843,7 @@ check_mode: true register: verify_faillock failed_when: verify_faillock is changed - with_items: - - audit - - local_users_only - - deny = 5 - - fail_interval = 900 + loop: "{{ faillock_parameters | select() }}" when: faillockconf.stat.exists - name: Verify wireless state @@ -809,29 +913,46 @@ - "!/var/log/audit" - "!/var/log/journal" + - name: Install mkpasswd + become: true + ansible.builtin.package: + name: mkpasswd + + - name: Create hash password + vars: + options: >- + {%- if (password_algorithm == 'sha512') -%} + --salt={{ lookup('password', '/dev/null chars=ascii_lowercase,ascii_uppercase,digits length=16') }} --rounds=65536 --method=sha-512 + {%- else -%} + --method=yescrypt --rounds=11 + {%- endif -%} + ansible.builtin.shell: |- + set -o pipefail + echo -n '{{ item }}' | mkpasswd --stdin {{ options }} | tr -d '\n' + args: + executable: /bin/bash + register: mkpasswd + changed_when: false + failed_when: mkpasswd.rc != 0 + loop: + - 'Ansible Role Test User' + - 'Change Ansible Role Test User' + - name: Create test user become: true ansible.builtin.user: name: roletestuser - password: "{{ 'Ansible Role Test User' | password_hash('sha512') }}" + password: "{{ mkpasswd.results[0].stdout }}" state: present shell: /bin/bash - - name: Create test user salt - ansible.builtin.set_fact: - test_user_salt: "{{ lookup('password', '/dev/null chars=ascii_lowercase,ascii_uppercase,digits length=16') }}" - - name: Change test user password become: true ansible.builtin.user: name: roletestuser - password: "{{ 'roletestuser' | password_hash('sha512', test_user_salt, rounds=656000) }}" + password: "{{ mkpasswd.results[1].stdout }}" register: test_user_pass - - name: Debug test user salt - ansible.builtin.debug: - msg: "{{ test_user_salt }}" - - name: Debug test user password ansible.builtin.debug: msg: "{{ test_user_pass }}" diff --git a/tasks/facts.yml b/tasks/facts.yml index 3ec1b8f5..c39f8636 100644 --- a/tasks/facts.yml +++ b/tasks/facts.yml @@ -92,6 +92,39 @@ ansible.builtin.set_fact: crypto_policies_config: "{{ stat_crypto_policies_config.stat.exists }}" +- name: Get PAM version + tags: + - fact + - common-account + - common-auth + - pam + block: + - name: Gather package facts + ansible.builtin.package_facts: + + - name: Get libpam version in Debian family + ansible.builtin.set_fact: + pam_version: "{{ ansible_facts.packages['libpam-modules'][0].version }}" + when: + - ansible_os_family == "Debian" + - "'libpam-modules' in ansible_facts.packages" + + - name: Get libpam version in RedHat family + ansible.builtin.set_fact: + pam_version: "{{ ansible_facts.packages['pam'][0].version }}" + when: + - ansible_os_family == "RedHat" + - "'pam' in ansible_facts.packages" + +- name: Set hashing algorithm for password (yescrypt|sha512) + ansible.builtin.set_fact: + password_algorithm: "{{ 'yescrypt' if (pam_version is version('1.4.0', '>=')) else 'sha512' }}" + tags: + - fact + - pam + - CIS-UBUNTU2204-5.4.4 + - M1041 + - name: Update current facts ansible.builtin.setup: tags: diff --git a/tasks/logindefs.yml b/tasks/logindefs.yml index 65e70173..a29a8498 100644 --- a/tasks/logindefs.yml +++ b/tasks/logindefs.yml @@ -25,6 +25,7 @@ - CIS-UBUNTU2004-5.5.1.2 - CIS-UBUNTU2004-5.5.1.3 - CIS-UBUNTU2004-5.5.4 + - CIS-UBUNTU2204-5.4.4 - UBTU-20-010007 - UBTU-20-010008 - UBTU-20-010016 diff --git a/tasks/password.yml b/tasks/password.yml index 17560f3d..a6965c75 100644 --- a/tasks/password.yml +++ b/tasks/password.yml @@ -1,39 +1,14 @@ --- -- name: Stat faillock - become: true - ansible.builtin.find: - paths: [/usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin, /sbin, /bin, /snap/bin] - patterns: faillock - recurse: true - register: faillock - tags: - - common-account - - common-auth - - pam - -- name: Stat faillock configuration file - become: true - ansible.builtin.stat: - path: /etc/security/faillock.conf - register: faillockconf - tags: - - common-account - - common-auth - - pam - - name: Configure faillock.conf become: true - ansible.builtin.lineinfile: + ansible.builtin.template: + src: "{{ faillock_conf_template }}" dest: /etc/security/faillock.conf + backup: true mode: "0644" - state: present - line: "{{ item }}" - loop: - - audit - - local_users_only - - deny = 5 - - fail_interval = 900 - when: faillockconf.stat.exists + owner: root + group: root + when: faillock_enable | bool tags: - common-account - common-auth @@ -103,64 +78,84 @@ become: true when: ansible_os_family == "RedHat" block: - - name: Remove nullok from system-auth + - name: Remove 'nullok' ansible.builtin.replace: - dest: /etc/pam.d/system-auth - regexp: nullok + path: "{{ item }}" + regexp: '\s*nullok' mode: "0644" owner: root group: root + loop: + - /etc/pam.d/password-auth + - /etc/pam.d/sssd-shadowutils + - /etc/pam.d/system-auth tags: - - system-auth - pam + - password-auth + - sssd-shadowutils + - system-auth - CCE-80841-0 - - name: Remove nullok from password-auth + - name: Set hashing algorithm for password ansible.builtin.replace: - dest: /etc/pam.d/password-auth - regexp: nullok + path: "{{ item }}" + regexp: '\s+(bigcrypt|blowfish|gost_yescrypt|md5|sha256|sha512|yescrypt)\s+' + replace: ' {{ password_algorithm }} ' mode: "0644" owner: root group: root + loop: + - /etc/pam.d/password-auth + - /etc/pam.d/system-auth tags: - - password-auth - pam + - password-auth + - system-auth + - CCE-80666-1 - - name: Remove nullok from sssd-shadowutils - become: true + - name: Set rounds ansible.builtin.replace: - dest: /etc/pam.d/sssd-shadowutils - regexp: nullok + path: "{{ item }}" + regexp: '(\s+{{ password_algorithm }}\s+(?!rounds=)\S*)(\s+rounds=[0-9]+)*' + replace: '\1 rounds={{ "65536" if (password_algorithm == "sha512") else "11" }}' mode: "0644" owner: root group: root + loop: + - /etc/pam.d/password-auth + - /etc/pam.d/system-auth tags: - - sssd-shadowutils - pam + - password-auth + - system-auth + - CCE-80666-1 - - name: Set system-auth remember + - name: Set remember ansible.builtin.replace: - regexp: use_authtok(\s+.*) - replace: use_authtok remember=5 - dest: /etc/pam.d/system-auth + path: "{{ item }}" + regexp: '(\s+use_authtok\s+(?!remember=)\S*)(\s+remember=[0-9]+)*' + replace: '\1 remember={{ password_remember }}' mode: "0644" owner: root group: root + loop: + - /etc/pam.d/password-auth + - /etc/pam.d/system-auth tags: + - pam + - password-auth - system-auth - CCE-80666-1 - name: Configure pwquality become: true - ansible.builtin.lineinfile: - path: /etc/security/pwquality.conf - line: "{{ item.key }} = {{ item.value }}" - regexp: ".*{{ item.key }} = " - state: present + ansible.builtin.template: + src: "{{ pwquality_conf_template }}" + dest: /etc/security/pwquality.conf + backup: true mode: "0644" owner: root group: root - with_dict: "{{ pwquality_config }}" tags: - pwquality - pam @@ -192,11 +187,11 @@ tags: - libuser -- name: Set libuser crypt_style to sha512 +- name: Set libuser crypt_style become: true ansible.builtin.replace: regexp: crypt_style(\s+.*) - replace: crypt_style = sha512 + replace: crypt_style = {{ password_algorithm }} dest: /etc/libuser.conf mode: "0644" owner: root diff --git a/templates/etc/audit/rules.d/hardening.rules.j2 b/templates/etc/audit/rules.d/hardening.rules.j2 index 0dbeae85..59f9f5ed 100644 --- a/templates/etc/audit/rules.d/hardening.rules.j2 +++ b/templates/etc/audit/rules.d/hardening.rules.j2 @@ -179,7 +179,7 @@ -w /var/log/faillog -p wa -k login -w /var/log/lastlog -p wa -k login -w /var/log/tallylog -p wa -k login --w /var/run/faillock -p wa -k login +-w {{ faillock.dir }} -p wa -k login # SELinux configuration -w /etc/selinux -p wa -k mac-policy diff --git a/templates/etc/login.defs.j2 b/templates/etc/login.defs.j2 index 9c2fa50e..736dc3ae 100644 --- a/templates/etc/login.defs.j2 +++ b/templates/etc/login.defs.j2 @@ -1,9 +1,12 @@ # {{ ansible_managed }} # Generated by Ansible role {{ ansible_role_name }} +# Shadow password suite configuration +# See login.defs(5) for more information + CHFN_RESTRICT rwh DEFAULT_HOME no -ENCRYPT_METHOD SHA512 +ENCRYPT_METHOD {{ password_algorithm | upper }} ENV_PATH PATH=/usr/local/bin:/usr/sbin:/usr/bin:/bin:/snap/bin ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin ERASECHAR 0177 @@ -13,17 +16,17 @@ GID_MAX 60000 GID_MIN 1000 HUSHLOGIN_FILE .hushlogin KILLCHAR 025 -LOGIN_RETRIES 5 -LOGIN_TIMEOUT 60 +LOGIN_RETRIES {{ login_defs.login_retries }} +LOGIN_TIMEOUT {{ login_defs.login_timeout }} LOG_OK_LOGINS yes LOG_UNKFAIL_ENAB no MAIL_DIR /var/mail -PASS_MAX_DAYS 60 -PASS_MIN_DAYS 1 +PASS_MAX_DAYS {{ login_defs.pass_max_days }} +PASS_MIN_DAYS {{ login_defs.pass_min_days }} {% if ansible_distribution == "CentOS" %} -PASS_MIN_LEN 15 +PASS_MIN_LEN {{ pwquality.minlen }} {% endif %} -PASS_WARN_AGE 7 +PASS_WARN_AGE {{ login_defs.pass_warn_age }} SHA_CRYPT_MAX_ROUNDS 65536 SHA_CRYPT_MIN_ROUNDS 10000 SU_NAME su diff --git a/templates/etc/pam.d/common-account.j2 b/templates/etc/pam.d/common-account.j2 index 53078fe9..cea366be 100644 --- a/templates/etc/pam.d/common-account.j2 +++ b/templates/etc/pam.d/common-account.j2 @@ -4,8 +4,6 @@ account [success=1 new_authtok_reqd=done default=ignore] pam_unix.so account requisite pam_deny.so account required pam_permit.so -{% if faillock.matched >= 1 %} -account required pam_faillock.so -{% else %} -account required pam_tally2.so +{% if (faillock_enable | bool) %} +account required pam_faillock.so {% endif %} diff --git a/templates/etc/pam.d/common-auth.j2 b/templates/etc/pam.d/common-auth.j2 index 2a5c5476..4ccacb67 100644 --- a/templates/etc/pam.d/common-auth.j2 +++ b/templates/etc/pam.d/common-auth.j2 @@ -1,14 +1,13 @@ # {{ ansible_managed }} # Generated by Ansible role {{ ansible_role_name }} -{% if faillock.matched >= 1 %} +{% if (faillock_enable | bool) %} auth required pam_faillock.so preauth auth [success=1 default=ignore] pam_unix.so auth [default=die] pam_faillock.so authfail auth sufficient pam_faillock.so authsucc {% else %} auth [success=1 default=ignore] pam_unix.so -auth required pam_tally2.so onerr=fail audit silent deny=3 unlock_time=900 {% endif %} auth requisite pam_deny.so auth required pam_permit.so diff --git a/templates/etc/pam.d/common-password.j2 b/templates/etc/pam.d/common-password.j2 index 3ec290e8..5f85189b 100644 --- a/templates/etc/pam.d/common-password.j2 +++ b/templates/etc/pam.d/common-password.j2 @@ -2,11 +2,6 @@ # Generated by Ansible role {{ ansible_role_name }} password requisite pam_pwquality.so retry=3 -{% if ansible_os_family == 'Debian' %} -password required pam_pwhistory.so use_authtok remember=5 -password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass sha512 rounds=65536 -{% else %} -password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass sha512 rounds=65536 remember=5 -{% endif %} +password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass {{ password_algorithm }} rounds={{ '65536' if (password_algorithm == 'sha512') else '11' }} remember={{ password_remember }} password requisite pam_deny.so password required pam_permit.so diff --git a/templates/etc/security/faillock.conf.j2 b/templates/etc/security/faillock.conf.j2 new file mode 100644 index 00000000..6bfd9894 --- /dev/null +++ b/templates/etc/security/faillock.conf.j2 @@ -0,0 +1,35 @@ +#jinja2: trim_blocks: "true", lstrip_blocks: "true" +# {{ ansible_managed }} +# Generated by Ansible role {{ ansible_role_name }} + +# pam_faillock configuration file +# See faillock.conf(5) for more information + +dir = {{ faillock.dir }} +{% if (faillock.audit | bool) %} +audit +{% endif%} +{% if (faillock.silent | bool) %} +silent +{% endif %} +{% if (faillock.no_log_info | bool) %} +no_log_info +{% endif %} +{% if (faillock.local_users_only | bool) %} +local_users_only +{% endif %} +{% if (pam_version is version('1.5.1', '>=')) %} +{% if (faillock.nodelay | bool) %} +nodelay +{% endif %} +{% endif %} +deny = {{ faillock.deny }} +fail_interval = {{ faillock.fail_interval }} +unlock_time = {{ faillock.unlock_time }} +{% if (faillock.even_deny_root | bool) %} +even_deny_root +{% endif %} +root_unlock_time = {{ faillock.root_unlock_time }} +{% for group in faillock.admin_group %} +admin_group = {{ group }} +{% endfor %} diff --git a/templates/etc/security/pwquality.conf.j2 b/templates/etc/security/pwquality.conf.j2 new file mode 100644 index 00000000..0f6b549e --- /dev/null +++ b/templates/etc/security/pwquality.conf.j2 @@ -0,0 +1,29 @@ +#jinja2: trim_blocks: "true", lstrip_blocks: "true" +# {{ ansible_managed }} +# Generated by Ansible role {{ ansible_role_name }} + +# Configuration for the libpwquality library +# See pwquality.conf(5) for more information + +dcredit = {{ pwquality.dcredit }} +dictcheck = {{ pwquality.dictcheck }} +dictpath = {{ pwquality.dictpath }} +difok = {{ pwquality.difok }} +{% if (pwquality.enforce_for_root | bool) %} +enforce_for_root +{% endif%} +enforcing = {{ pwquality.enforcing }} +gecoscheck = {{ pwquality.gecoscheck }} +lcredit = {{ pwquality.lcredit }} +{% if (pwquality.local_users_only | bool) %} +local_users_only +{% endif%} +maxclassrepeat = {{ pwquality.maxclassrepeat }} +maxrepeat = {{ pwquality.maxrepeat }} +minclass = {{ pwquality.minclass }} +minlen = {{ pwquality.minlen }} +ocredit = {{ pwquality.ocredit }} +retry = {{ pwquality.retry }} +ucredit = {{ pwquality.ucredit }} +usercheck = {{ pwquality.usercheck }} +usersubstr = {{ pwquality.usersubstr }} From d2ad1ad2a218dffbe677608f4210e6b92e441245 Mon Sep 17 00:00:00 2001 From: cleberb Date: Fri, 20 Oct 2023 12:34:09 -0300 Subject: [PATCH 2/6] Fix package mkpasswd --- molecule/default/verify.yml | 73 ++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index 7f8252e6..23fadaa2 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -913,36 +913,59 @@ - "!/var/log/audit" - "!/var/log/journal" - - name: Install mkpasswd - become: true - ansible.builtin.package: - name: mkpasswd - - name: Create hash password - vars: - options: >- - {%- if (password_algorithm == 'sha512') -%} - --salt={{ lookup('password', '/dev/null chars=ascii_lowercase,ascii_uppercase,digits length=16') }} --rounds=65536 --method=sha-512 - {%- else -%} - --method=yescrypt --rounds=11 - {%- endif -%} - ansible.builtin.shell: |- - set -o pipefail - echo -n '{{ item }}' | mkpasswd --stdin {{ options }} | tr -d '\n' - args: - executable: /bin/bash - register: mkpasswd - changed_when: false - failed_when: mkpasswd.rc != 0 - loop: - - 'Ansible Role Test User' - - 'Change Ansible Role Test User' + block: + - name: Install mkpasswd RedHat family + become: true + ansible.builtin.package: + name: mkpasswd + when: + - password_algorithm == "yescrypt" + - ansible_os_family == "RedHat" + - ansible_distribution_major_version | int >= 9 + + - name: Install mkpasswd Debian family + become: true + ansible.builtin.package: + name: whois + when: + - password_algorithm == "yescrypt" + - ansible_os_family == "Debian" + + - name: Create yescrypt password hash + ansible.builtin.shell: |- + set -o pipefail + echo -n '{{ item }}' | mkpasswd --stdin --method=yescrypt --rounds=11 | tr -d '\n' + args: + executable: /bin/bash + register: mkpasswd + changed_when: false + failed_when: mkpasswd.rc != 0 + loop: + - 'Ansible Role Test User' + - 'Change Ansible Role Test User' + when: password_algorithm == "yescrypt" + + - name: Set passwords + ansible.builtin.set_fact: + passwords: "{{ mkpasswd.results | map(attribute='stdout') | list }}" + when: mkpasswd.rc != 0 + + - name: Create sha512 password hash + vars: + salt: "{{ lookup('password', '/dev/null chars=ascii_lowercase,ascii_uppercase,digits length=16') }}" + ansible.builtin.set_fact: + passwords: "{{ passwords | default([]) | union([item | password_hash('sha512', salt, rounds=65536)]) }}" + loop: + - 'Ansible Role Test User' + - 'Change Ansible Role Test User' + when: password_algorithm == "sha512" - name: Create test user become: true ansible.builtin.user: name: roletestuser - password: "{{ mkpasswd.results[0].stdout }}" + password: "{{ passwords[0] }}" state: present shell: /bin/bash @@ -950,7 +973,7 @@ become: true ansible.builtin.user: name: roletestuser - password: "{{ mkpasswd.results[1].stdout }}" + password: "{{ passwords[1] }}" register: test_user_pass - name: Debug test user password From 6b5d3aa9e8d042780437bbb48bba810b929419d2 Mon Sep 17 00:00:00 2001 From: cleberb Date: Fri, 20 Oct 2023 19:19:04 -0300 Subject: [PATCH 3/6] Fix rounds for yescrypt --- molecule/default/verify.yml | 8 +++++--- tasks/password.yml | 2 +- templates/etc/pam.d/common-password.j2 | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index 23fadaa2..7bd23a06 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -933,9 +933,9 @@ - ansible_os_family == "Debian" - name: Create yescrypt password hash - ansible.builtin.shell: |- + ansible.builtin.shell: | set -o pipefail - echo -n '{{ item }}' | mkpasswd --stdin --method=yescrypt --rounds=11 | tr -d '\n' + echo -n '{{ item }}' | mkpasswd --stdin --method=yescrypt --rounds=8 args: executable: /bin/bash register: mkpasswd @@ -949,7 +949,9 @@ - name: Set passwords ansible.builtin.set_fact: passwords: "{{ mkpasswd.results | map(attribute='stdout') | list }}" - when: mkpasswd.rc != 0 + when: + - password_algorithm == "yescrypt" + - mkpasswd.rc == 0 - name: Create sha512 password hash vars: diff --git a/tasks/password.yml b/tasks/password.yml index a6965c75..29d41e7e 100644 --- a/tasks/password.yml +++ b/tasks/password.yml @@ -117,7 +117,7 @@ ansible.builtin.replace: path: "{{ item }}" regexp: '(\s+{{ password_algorithm }}\s+(?!rounds=)\S*)(\s+rounds=[0-9]+)*' - replace: '\1 rounds={{ "65536" if (password_algorithm == "sha512") else "11" }}' + replace: '\1 rounds={{ "65536" if (password_algorithm == "sha512") else "8" }}' mode: "0644" owner: root group: root diff --git a/templates/etc/pam.d/common-password.j2 b/templates/etc/pam.d/common-password.j2 index 5f85189b..9d132033 100644 --- a/templates/etc/pam.d/common-password.j2 +++ b/templates/etc/pam.d/common-password.j2 @@ -2,6 +2,6 @@ # Generated by Ansible role {{ ansible_role_name }} password requisite pam_pwquality.so retry=3 -password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass {{ password_algorithm }} rounds={{ '65536' if (password_algorithm == 'sha512') else '11' }} remember={{ password_remember }} +password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass {{ password_algorithm }} rounds={{ '65536' if (password_algorithm == 'sha512') else '8' }} remember={{ password_remember }} password requisite pam_deny.so password required pam_permit.so From bc51546e9910a338a4a237198e9ab6f08e1233cd Mon Sep 17 00:00:00 2001 From: cleberb Date: Mon, 23 Oct 2023 10:48:06 -0300 Subject: [PATCH 4/6] Fix regex for set `rounds` and `remember` --- tasks/password.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/password.yml b/tasks/password.yml index 29d41e7e..bca71cda 100644 --- a/tasks/password.yml +++ b/tasks/password.yml @@ -116,7 +116,7 @@ - name: Set rounds ansible.builtin.replace: path: "{{ item }}" - regexp: '(\s+{{ password_algorithm }}\s+(?!rounds=)\S*)(\s+rounds=[0-9]+)*' + regexp: '(\s+{{ password_algorithm }}.*?(?=\s+rounds=\d+|$))(\s+rounds=\d+)*' replace: '\1 rounds={{ "65536" if (password_algorithm == "sha512") else "8" }}' mode: "0644" owner: root @@ -133,7 +133,7 @@ - name: Set remember ansible.builtin.replace: path: "{{ item }}" - regexp: '(\s+use_authtok\s+(?!remember=)\S*)(\s+remember=[0-9]+)*' + regexp: '(\s+use_authtok.*?(?=\s+remember=\d+|$))(\s+remember=\d+)*' replace: '\1 remember={{ password_remember }}' mode: "0644" owner: root From 005f2d0cde68ce2cba16b33d2377184040018782 Mon Sep 17 00:00:00 2001 From: cleberb Date: Tue, 24 Oct 2023 02:10:42 -0300 Subject: [PATCH 5/6] Fix `pwquality.dictpath` --- README.md | 2 +- defaults/main/password.yml | 2 +- molecule/default/verify.yml | 5 ++++- templates/etc/security/pwquality.conf.j2 | 6 ++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d2acb721..8926b5b0 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ password_remember: 5 pwquality: dcredit: -1 dictcheck: 1 - dictpath: 1 + dictpath: '' difok: 8 enforce_for_root: true enforcing: 1 diff --git a/defaults/main/password.yml b/defaults/main/password.yml index bf06e2c4..e2a907aa 100644 --- a/defaults/main/password.yml +++ b/defaults/main/password.yml @@ -23,7 +23,7 @@ password_remember: 5 pwquality: dcredit: -1 dictcheck: 1 - dictpath: 1 + dictpath: '' difok: 8 enforce_for_root: true enforcing: 1 diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index 7bd23a06..9e4fb377 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -531,7 +531,10 @@ pwquality_parameters: - dcredit = {{ pwquality.dcredit }} - dictcheck = {{ pwquality.dictcheck }} - - dictpath = {{ pwquality.dictpath }} + - >- + {%- if pwquality.dictpath != "" -%} + dictpath = {{ pwquality.dictpath }} + {%- endif -%} - difok = {{ pwquality.difok }} - >- {%- if (pwquality.enforce_for_root | bool) -%} diff --git a/templates/etc/security/pwquality.conf.j2 b/templates/etc/security/pwquality.conf.j2 index 0f6b549e..ec938866 100644 --- a/templates/etc/security/pwquality.conf.j2 +++ b/templates/etc/security/pwquality.conf.j2 @@ -7,17 +7,19 @@ dcredit = {{ pwquality.dcredit }} dictcheck = {{ pwquality.dictcheck }} +{% if pwquality.dictpath != "" %} dictpath = {{ pwquality.dictpath }} +{% endif %} difok = {{ pwquality.difok }} {% if (pwquality.enforce_for_root | bool) %} enforce_for_root -{% endif%} +{% endif %} enforcing = {{ pwquality.enforcing }} gecoscheck = {{ pwquality.gecoscheck }} lcredit = {{ pwquality.lcredit }} {% if (pwquality.local_users_only | bool) %} local_users_only -{% endif%} +{% endif %} maxclassrepeat = {{ pwquality.maxclassrepeat }} maxrepeat = {{ pwquality.maxrepeat }} minclass = {{ pwquality.minclass }} From 79d2369612054f3dabe34b9236403da1fe901f4f Mon Sep 17 00:00:00 2001 From: cleberb Date: Wed, 25 Oct 2023 11:44:30 -0300 Subject: [PATCH 6/6] Update testing --- molecule/default/verify.yml | 39 +++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index 9e4fb377..c75adae7 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -938,49 +938,46 @@ - name: Create yescrypt password hash ansible.builtin.shell: | set -o pipefail - echo -n '{{ item }}' | mkpasswd --stdin --method=yescrypt --rounds=8 + echo -n 'Ansible Role Test User' | mkpasswd --stdin --method=yescrypt --rounds=8 args: executable: /bin/bash register: mkpasswd changed_when: false failed_when: mkpasswd.rc != 0 - loop: - - 'Ansible Role Test User' - - 'Change Ansible Role Test User' when: password_algorithm == "yescrypt" - - name: Set passwords + - name: Set password ansible.builtin.set_fact: - passwords: "{{ mkpasswd.results | map(attribute='stdout') | list }}" - when: - - password_algorithm == "yescrypt" - - mkpasswd.rc == 0 + password: "{{ mkpasswd.stdout }}" + when: password_algorithm == "yescrypt" - name: Create sha512 password hash vars: salt: "{{ lookup('password', '/dev/null chars=ascii_lowercase,ascii_uppercase,digits length=16') }}" ansible.builtin.set_fact: - passwords: "{{ passwords | default([]) | union([item | password_hash('sha512', salt, rounds=65536)]) }}" - loop: - - 'Ansible Role Test User' - - 'Change Ansible Role Test User' + password: "{{ 'Ansible Role Test User' | password_hash('sha512', salt, rounds=656000) }}" when: password_algorithm == "sha512" + - name: Print password + ansible.builtin.debug: + msg: "{{ password }}" + when: password + - name: Create test user become: true ansible.builtin.user: name: roletestuser - password: "{{ passwords[0] }}" + password: "{{ password }}" state: present shell: /bin/bash - - name: Change test user password + - name: Get test user password become: true - ansible.builtin.user: - name: roletestuser - password: "{{ passwords[1] }}" - register: test_user_pass + ansible.builtin.command: + cmd: grep roletestuser /etc/shadow + register: roletestuser_pass + changed_when: false - - name: Debug test user password + - name: Print test user ansible.builtin.debug: - msg: "{{ test_user_pass }}" + msg: "{{ roletestuser_pass.stdout }}"