From 652734b570bc728f02df99a7c570b119ddc5c4be Mon Sep 17 00:00:00 2001 From: Ahmed Abdelhalim Date: Mon, 11 Aug 2025 00:32:07 +0200 Subject: Refactor: move password policy to its own role --- molecule/default/converge.yml | 2 +- roles/password_policy/README.md | 21 +++++++++ roles/password_policy/defaults/main.yml | 14 ++++++ roles/password_policy/meta/argument_specs.yml | 51 ++++++++++++++++++++ roles/password_policy/meta/main.yml | 19 ++++++++ roles/password_policy/tasks/main.yml | 25 ++++++++++ .../password_policy/templates/archlinux-pam-pw.j2 | 45 ++++++++++++++++++ roles/password_policy/templates/debian-pam-pw.j2 | 49 +++++++++++++++++++ roles/password_policy/vars/archlinux.yml | 2 + roles/password_policy/vars/debian.yml | 2 + roles/settings/README.md | 23 --------- roles/settings/defaults/main.yml | 14 ------ roles/settings/meta/argument_specs.yml | 55 ---------------------- roles/settings/meta/main.yml | 19 -------- roles/settings/tasks/main.yml | 35 -------------- roles/settings/templates/archlinux-pam-pw.j2 | 45 ------------------ roles/settings/templates/debian-pam-pw.j2 | 49 ------------------- roles/settings/vars/archlinux.yml | 2 - roles/settings/vars/debian.yml | 2 - site.yml | 6 ++- 20 files changed, 234 insertions(+), 246 deletions(-) create mode 100644 roles/password_policy/README.md create mode 100644 roles/password_policy/defaults/main.yml create mode 100644 roles/password_policy/meta/argument_specs.yml create mode 100644 roles/password_policy/meta/main.yml create mode 100644 roles/password_policy/tasks/main.yml create mode 100644 roles/password_policy/templates/archlinux-pam-pw.j2 create mode 100644 roles/password_policy/templates/debian-pam-pw.j2 create mode 100644 roles/password_policy/vars/archlinux.yml create mode 100644 roles/password_policy/vars/debian.yml delete mode 100644 roles/settings/README.md delete mode 100644 roles/settings/defaults/main.yml delete mode 100644 roles/settings/meta/argument_specs.yml delete mode 100644 roles/settings/meta/main.yml delete mode 100644 roles/settings/tasks/main.yml delete mode 100644 roles/settings/templates/archlinux-pam-pw.j2 delete mode 100644 roles/settings/templates/debian-pam-pw.j2 delete mode 100644 roles/settings/vars/archlinux.yml delete mode 100644 roles/settings/vars/debian.yml diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index d04d8628..eacb6559 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -15,6 +15,6 @@ - role: network - role: user - role: ssh + - role: password_policy - role: locales - role: timezone - - role: settings diff --git a/roles/password_policy/README.md b/roles/password_policy/README.md new file mode 100644 index 00000000..c499dcdc --- /dev/null +++ b/roles/password_policy/README.md @@ -0,0 +1,21 @@ +# Ansible Role: password_policy + +This role configure the machine password quality policy + +## Role Variables + +- `password_policy_enabled` boolean to enable or disable the enforcement of password policy +- `password_policy_difok` the number of differences between new and old password (default 0 - disabled) +- `password_policy_minlen` min password length (default 8) +- `password_policy_dcredit` required digits in password (default 0) +- `password_policy_ucredit` required uppercase chars in password (default 0) +- `password_policy_ocredit` required other chars in password (default 0) +- `password_policy_lcredit` required lowercase chars in password (default 0) +- `password_policy_minclass` required minimum classes in password upper/lower/digit/other (default 0 - disabled) +- `password_policy_maxrepeat` allowed maximum repeats in password (default 0 - disabled) +- `password_policy_maxclassrepeat` maximum allowed consecutive class repeats (default 0 -disabled) +- `password_policy_gecoscheck` (default 0 - disabled) + +## Docs + +- https://linux.die.net/man/8/pam_pwquality diff --git a/roles/password_policy/defaults/main.yml b/roles/password_policy/defaults/main.yml new file mode 100644 index 00000000..1c5f7254 --- /dev/null +++ b/roles/password_policy/defaults/main.yml @@ -0,0 +1,14 @@ +--- +# Password policy +# https://linux.die.net/man/8/pam_pwquality +password_policy_enabled: true +password_policy_difok: 0 +password_policy_minlen: 8 +password_policy_dcredit: 0 +password_policy_ucredit: 0 +password_policy_ocredit: 0 +password_policy_lcredit: 0 +password_policy_minclass: 0 +password_policy_maxrepeat: 0 +password_policy_maxclassrepeat: 0 +password_policy_gecoscheck: 0 diff --git a/roles/password_policy/meta/argument_specs.yml b/roles/password_policy/meta/argument_specs.yml new file mode 100644 index 00000000..8c312a8c --- /dev/null +++ b/roles/password_policy/meta/argument_specs.yml @@ -0,0 +1,51 @@ +--- +argument_specs: + main: + options: + password_policy_enabled: + type: "bool" + description: "Enable password policy" + default: true + password_policy_difok: + type: "int" + description: "The number of differences between new and old password" + default: 0 + password_policy_minlen: + type: "int" + description: "Min password length" + default: 8 + password_policy_dcredit: + type: "int" + description: "Num of digits required in password" + default: 0 + password_policy_ucredit: + type: "int" + description: "Num of uppercase chars required in password" + default: 0 + password_policy_ocredit: + type: "int" + description: "Num of special chars required in password" + default: 0 + password_policy_lcredit: + type: "int" + description: "Num of lowercase chars required in password" + default: 0 + password_policy_minclass: + type: "int" + description: "Num of required classes (upper/lower/digit/special) chars required in password" + default: 0 + password_policy_maxrepeat: + type: "int" + description: "Num of allowed repeats in password" + default: 0 + password_policy_maxclassrepeat: + type: "int" + description: "Maximum number of consecutive class repeats in password" + default: 0 + password_policy_gecoscheck: + type: "int" + description: | + If nonzero, check whether the individual words longer than 3 characters from the + passwd GECOS field of the user are contained in the new password. + The default is 0 which means that this check is disabled. + default: 0 diff --git a/roles/password_policy/meta/main.yml b/roles/password_policy/meta/main.yml new file mode 100644 index 00000000..fdcf75a1 --- /dev/null +++ b/roles/password_policy/meta/main.yml @@ -0,0 +1,19 @@ +--- +dependencies: [] +galaxy_info: + role_name: password_policy + author: a14m + description: "Configure the linux distro password policy" + company: "kartoffeln.work GmbH." + license: "license MIT" + min_ansible_version: "2.18" + platforms: + - name: ArchLinux + versions: + - all + - name: Ubuntu + versions: + - noble + - name: Debian + versions: + - bookworm diff --git a/roles/password_policy/tasks/main.yml b/roles/password_policy/tasks/main.yml new file mode 100644 index 00000000..10aa3837 --- /dev/null +++ b/roles/password_policy/tasks/main.yml @@ -0,0 +1,25 @@ +--- +- name: "Include OS-specific variables" + ansible.builtin.include_vars: "{{ ansible_os_family | lower }}.yml" + +- name: "Ensure password_policy package is installed" + become: true + ansible.builtin.package: + name: "{{ password_policy_pkg }}" + state: "present" + +- name: "Configure archlinux password policy" + become: true + ansible.builtin.template: + src: "archlinux-pam-pw.j2" + dest: "/etc/pam.d/system-auth" + mode: "0644" + when: ansible_os_family == "Archlinux" + +- name: "Configure debian password policy" + become: true + ansible.builtin.template: + src: "debian-pam-pw.j2" + dest: "/etc/pam.d/common-password" + mode: "0644" + when: ansible_os_family == "Debian" diff --git a/roles/password_policy/templates/archlinux-pam-pw.j2 b/roles/password_policy/templates/archlinux-pam-pw.j2 new file mode 100644 index 00000000..b5e3914e --- /dev/null +++ b/roles/password_policy/templates/archlinux-pam-pw.j2 @@ -0,0 +1,45 @@ +#%PAM-1.0 + +auth required pam_faillock.so preauth +# Optionally use requisite above if you do not want to prompt for the password +# on locked accounts. +-auth [success=2 default=ignore] pam_systemd_home.so +auth [success=1 default=bad] pam_unix.so try_first_pass nullok +auth [default=die] pam_faillock.so authfail +auth optional pam_permit.so +auth required pam_env.so +auth required pam_faillock.so authsucc +# If you drop the above call to pam_faillock.so the lock will be done also +# on non-consecutive authentication failures. + +-account [success=1 default=ignore] pam_systemd_home.so +account required pam_unix.so +account optional pam_permit.so +account required pam_time.so + +{% if password_policy_enabled %} +-password [success=2 default=ignore] pam_systemd_home.so +password requisite pam_pwquality.so retry=3 \ + difok={{ password_policy_difok }} \ + minlen={{ password_policy_minlen }} \ + dcredit={{ password_policy_dcredit }} \ + ucredit={{ password_policy_ucredit }} \ + ocredit={{ password_policy_ocredit }} \ + lcredit={{ password_policy_lcredit }} \ + minclass={{ password_policy_minclass }} \ + maxrepeat={{ password_policy_maxrepeat }} \ + maxclassrepeat={{ password_policy_maxclassrepeat }} \ + gecoscheck={{ password_policy_gecoscheck }} +{% else %} +-password [success=1 default=ignore] pam_systemd_home.so +{% endif %} +password required pam_unix.so try_first_pass \ + nullok \ + shadow \ + minlen={{ password_policy_minlen }} +password optional pam_permit.so + +-session optional pam_systemd_home.so +session required pam_limits.so +session required pam_unix.so +session optional pam_permit.so diff --git a/roles/password_policy/templates/debian-pam-pw.j2 b/roles/password_policy/templates/debian-pam-pw.j2 new file mode 100644 index 00000000..dceedf97 --- /dev/null +++ b/roles/password_policy/templates/debian-pam-pw.j2 @@ -0,0 +1,49 @@ +# +# /etc/pam.d/common-password - password-related modules common to all services +# +# This file is included from other service-specific PAM config files, +# and should contain a list of modules that define the services to be +# used to change user passwords. The default is pam_unix. + +# Explanation of pam_unix options: +# The "yescrypt" option enables +#hashed passwords using the yescrypt algorithm, introduced in Debian +#11. Without this option, the default is Unix crypt. Prior releases +#used the option "sha512"; if a shadow password hash will be shared +#between Debian 11 and older releases replace "yescrypt" with "sha512" +#for compatibility . The "obscure" option replaces the old +#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage +#for other options. + +# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. +# To take advantage of this, it is recommended that you configure any +# local modules either before or after the default block, and use +# pam-auth-update to manage selection of other modules. See +# pam-auth-update(8) for details. + +# here are the per-package modules (the "Primary" block) +{% if password_policy_enabled %} +password requisite pam_pwquality.so retry=3 \ + difok={{ password_policy_difok }} \ + minlen={{ password_policy_minlen }} \ + dcredit={{ password_policy_dcredit }} \ + ucredit={{ password_policy_ucredit }} \ + ocredit={{ password_policy_ocredit }} \ + lcredit={{ password_policy_lcredit }} \ + minclass={{ password_policy_minclass }} \ + maxrepeat={{ password_policy_maxrepeat }} \ + maxclassrepeat={{ password_policy_maxclassrepeat }} \ + gecoscheck={{ password_policy_gecoscheck }} +password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass yescrypt +{% else %} +password [success=1 default=ignore] pam_unix.so obsecure sha512 minlen={{ password_policy_minlen }} +{% endif %} +# here's the fallback if no module succeeds +password requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +password required pam_permit.so +# and here are more per-package modules (the "Additional" block) +password optional pam_gnome_keyring.so +# end of pam-auth-update config diff --git a/roles/password_policy/vars/archlinux.yml b/roles/password_policy/vars/archlinux.yml new file mode 100644 index 00000000..59d439d6 --- /dev/null +++ b/roles/password_policy/vars/archlinux.yml @@ -0,0 +1,2 @@ +--- +password_policy_pkg: "libpwquality" diff --git a/roles/password_policy/vars/debian.yml b/roles/password_policy/vars/debian.yml new file mode 100644 index 00000000..cb52b37d --- /dev/null +++ b/roles/password_policy/vars/debian.yml @@ -0,0 +1,2 @@ +--- +password_policy_pkg: "libpam-pwquality" diff --git a/roles/settings/README.md b/roles/settings/README.md deleted file mode 100644 index 9f6c7bb9..00000000 --- a/roles/settings/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Ansible Role: settings - -This role configure the machine settings (locales/timezone/password policy/etc.) - -## Role Variables - -- `settings_timezone` the timezone default `CET` - -- `settings_pw_policy_enabled` boolean to enable or disable the enforcement of password policy -- `settings_pw_policy_difok` the number of differences between new and old password (default 0 - disabled) -- `settings_pw_policy_minlen` min password length (default 8) -- `settings_pw_policy_dcredit` required digits in password (default 0) -- `settings_pw_policy_ucredit` required uppercase chars in password (default 0) -- `settings_pw_policy_ocredit` required other chars in password (default 0) -- `settings_pw_policy_lcredit` required lowercase chars in password (default 0) -- `settings_pw_policy_minclass` required minimum classes in password upper/lower/digit/other (default 0 - disabled) -- `settings_pw_policy_maxrepeat` allowed maximum repeats in password (default 0 - disabled) -- `settings_pw_policy_maxclassrepeat` maximum allowed consecutive class repeats (default 0 -disabled) -- `settings_pw_policy_gecoscheck` (default 0 - disabled) - -## Docs - -- https://linux.die.net/man/8/pam_pwquality diff --git a/roles/settings/defaults/main.yml b/roles/settings/defaults/main.yml deleted file mode 100644 index a7001ab0..00000000 --- a/roles/settings/defaults/main.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -# Password policy -# https://linux.die.net/man/8/pam_pwquality -settings_pw_policy_enabled: true -settings_pw_policy_difok: 0 -settings_pw_policy_minlen: 8 -settings_pw_policy_dcredit: 0 -settings_pw_policy_ucredit: 0 -settings_pw_policy_ocredit: 0 -settings_pw_policy_lcredit: 0 -settings_pw_policy_minclass: 0 -settings_pw_policy_maxrepeat: 0 -settings_pw_policy_maxclassrepeat: 0 -settings_pw_policy_gecoscheck: 0 diff --git a/roles/settings/meta/argument_specs.yml b/roles/settings/meta/argument_specs.yml deleted file mode 100644 index 0507db86..00000000 --- a/roles/settings/meta/argument_specs.yml +++ /dev/null @@ -1,55 +0,0 @@ ---- -argument_specs: - main: - options: - settings_timezone: - type: "str" - description: "timezone configuration to use" - default: "UTC" - settings_pw_policy_enabled: - type: "bool" - description: "Enable password policy" - default: true - settings_pw_policy_difok: - type: "int" - description: "The number of differences between new and old password" - default: 0 - settings_pw_policy_minlen: - type: "int" - description: "Min password length" - default: 8 - settings_pw_policy_dcredit: - type: "int" - description: "Num of digits required in password" - default: 0 - settings_pw_policy_ucredit: - type: "int" - description: "Num of uppercase chars required in password" - default: 0 - settings_pw_policy_ocredit: - type: "int" - description: "Num of special chars required in password" - default: 0 - settings_pw_policy_lcredit: - type: "int" - description: "Num of lowercase chars required in password" - default: 0 - settings_pw_policy_minclass: - type: "int" - description: "Num of required classes (upper/lower/digit/special) chars required in password" - default: 0 - settings_pw_policy_maxrepeat: - type: "int" - description: "Num of allowed repeats in password" - default: 0 - settings_pw_policy_maxclassrepeat: - type: "int" - description: "Maximum number of consecutive class repeats in password" - default: 0 - settings_pw_policy_gecoscheck: - type: "int" - description: | - If nonzero, check whether the individual words longer than 3 characters from the - passwd GECOS field of the user are contained in the new password. - The default is 0 which means that this check is disabled. - default: 0 diff --git a/roles/settings/meta/main.yml b/roles/settings/meta/main.yml deleted file mode 100644 index d8070386..00000000 --- a/roles/settings/meta/main.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -dependencies: [] -galaxy_info: - role_name: settings - author: a14m - description: "Configure the linux distro settings" - company: "kartoffeln.work GmbH." - license: "license MIT" - min_ansible_version: "2.18" - platforms: - - name: ArchLinux - versions: - - all - - name: Ubuntu - versions: - - noble - - name: Debian - versions: - - bookworm diff --git a/roles/settings/tasks/main.yml b/roles/settings/tasks/main.yml deleted file mode 100644 index 32fe9178..00000000 --- a/roles/settings/tasks/main.yml +++ /dev/null @@ -1,35 +0,0 @@ ---- -- name: "Include OS-specific variables" - ansible.builtin.include_vars: "{{ ansible_os_family | lower }}.yml" - -- name: "Ensure settings packages are installed" - become: true - ansible.builtin.package: - name: "{{ item }}" - state: "present" - with_items: - - "{{ settings_pw_quality }}" - -- name: "Configure archlinux password policy" - become: true - ansible.builtin.template: - src: "archlinux-pam-pw.j2" - dest: "/etc/pam.d/system-auth" - mode: "0644" - when: ansible_os_family == "Archlinux" - # Password policy is technically not required for boot, - # But it's quite nice to configure it for boot and therefore - # enforce the required policies/configurations wanted for the first passwrod change - tags: ["required_for_boot"] - -- name: "Configure debian password policy" - become: true - ansible.builtin.template: - src: "debian-pam-pw.j2" - dest: "/etc/pam.d/common-password" - mode: "0644" - when: ansible_os_family == "Debian" - # Password policy is technically not required for boot, - # But it's quite nice to configure it for boot and therefore - # enforce the required policies/configurations wanted for the first passwrod change - tags: ["required_for_boot"] diff --git a/roles/settings/templates/archlinux-pam-pw.j2 b/roles/settings/templates/archlinux-pam-pw.j2 deleted file mode 100644 index 7be41ccb..00000000 --- a/roles/settings/templates/archlinux-pam-pw.j2 +++ /dev/null @@ -1,45 +0,0 @@ -#%PAM-1.0 - -auth required pam_faillock.so preauth -# Optionally use requisite above if you do not want to prompt for the password -# on locked accounts. --auth [success=2 default=ignore] pam_systemd_home.so -auth [success=1 default=bad] pam_unix.so try_first_pass nullok -auth [default=die] pam_faillock.so authfail -auth optional pam_permit.so -auth required pam_env.so -auth required pam_faillock.so authsucc -# If you drop the above call to pam_faillock.so the lock will be done also -# on non-consecutive authentication failures. - --account [success=1 default=ignore] pam_systemd_home.so -account required pam_unix.so -account optional pam_permit.so -account required pam_time.so - -{% if settings_pw_policy_enabled %} --password [success=2 default=ignore] pam_systemd_home.so -password requisite pam_pwquality.so retry=3 \ - difok={{ settings_pw_policy_difok }} \ - minlen={{ settings_pw_policy_minlen }} \ - dcredit={{ settings_pw_policy_dcredit }} \ - ucredit={{ settings_pw_policy_ucredit }} \ - ocredit={{ settings_pw_policy_ocredit }} \ - lcredit={{ settings_pw_policy_lcredit }} \ - minclass={{ settings_pw_policy_minclass }} \ - maxrepeat={{ settings_pw_policy_maxrepeat }} \ - maxclassrepeat={{ settings_pw_policy_maxclassrepeat }} \ - gecoscheck={{ settings_pw_policy_gecoscheck }} -{% else %} --password [success=1 default=ignore] pam_systemd_home.so -{% endif %} -password required pam_unix.so try_first_pass \ - nullok \ - shadow \ - minlen={{ settings_pw_policy_minlen }} -password optional pam_permit.so - --session optional pam_systemd_home.so -session required pam_limits.so -session required pam_unix.so -session optional pam_permit.so diff --git a/roles/settings/templates/debian-pam-pw.j2 b/roles/settings/templates/debian-pam-pw.j2 deleted file mode 100644 index 521b822a..00000000 --- a/roles/settings/templates/debian-pam-pw.j2 +++ /dev/null @@ -1,49 +0,0 @@ -# -# /etc/pam.d/common-password - password-related modules common to all services -# -# This file is included from other service-specific PAM config files, -# and should contain a list of modules that define the services to be -# used to change user passwords. The default is pam_unix. - -# Explanation of pam_unix options: -# The "yescrypt" option enables -#hashed passwords using the yescrypt algorithm, introduced in Debian -#11. Without this option, the default is Unix crypt. Prior releases -#used the option "sha512"; if a shadow password hash will be shared -#between Debian 11 and older releases replace "yescrypt" with "sha512" -#for compatibility . The "obscure" option replaces the old -#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage -#for other options. - -# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. -# To take advantage of this, it is recommended that you configure any -# local modules either before or after the default block, and use -# pam-auth-update to manage selection of other modules. See -# pam-auth-update(8) for details. - -# here are the per-package modules (the "Primary" block) -{% if settings_pw_policy_enabled %} -password requisite pam_pwquality.so retry=3 \ - difok={{ settings_pw_policy_difok }} \ - minlen={{ settings_pw_policy_minlen }} \ - dcredit={{ settings_pw_policy_dcredit }} \ - ucredit={{ settings_pw_policy_ucredit }} \ - ocredit={{ settings_pw_policy_ocredit }} \ - lcredit={{ settings_pw_policy_lcredit }} \ - minclass={{ settings_pw_policy_minclass }} \ - maxrepeat={{ settings_pw_policy_maxrepeat }} \ - maxclassrepeat={{ settings_pw_policy_maxclassrepeat }} \ - gecoscheck={{ settings_pw_policy_gecoscheck }} -password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass yescrypt -{% else %} -password [success=1 default=ignore] pam_unix.so obsecure sha512 minlen={{ settings_pw_policy_minlen }} -{% endif %} -# here's the fallback if no module succeeds -password requisite pam_deny.so -# prime the stack with a positive return value if there isn't one already; -# this avoids us returning an error just because nothing sets a success code -# since the modules above will each just jump around -password required pam_permit.so -# and here are more per-package modules (the "Additional" block) -password optional pam_gnome_keyring.so -# end of pam-auth-update config diff --git a/roles/settings/vars/archlinux.yml b/roles/settings/vars/archlinux.yml deleted file mode 100644 index cbe08e80..00000000 --- a/roles/settings/vars/archlinux.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -settings_pw_quality: "libpwquality" diff --git a/roles/settings/vars/debian.yml b/roles/settings/vars/debian.yml deleted file mode 100644 index 6767ffd7..00000000 --- a/roles/settings/vars/debian.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -settings_pw_quality: "libpam-pwquality" diff --git a/site.yml b/site.yml index 5f48b6fb..b362ac0e 100644 --- a/site.yml +++ b/site.yml @@ -11,9 +11,13 @@ tags: ["required_for_boot"] - role: "ssh" tags: ["required_for_boot"] + - role: "password_policy" + # Password policy is technically not required for boot, + # But it's quite nice to configure it for boot and therefore + # enforce the required policies/configurations wanted for the first passwrod change + tags: ["required_for_boot"] - role: "locales" - role: "timezone" - - role: "settings" pre_tasks: - name: "Update package cache" -- cgit v1.2.3