From f893a7484787d42064eebe5395f3db5c0787ca96 Mon Sep 17 00:00:00 2001 From: Ahmed Abdelhalim Date: Mon, 11 Aug 2025 00:32:05 +0200 Subject: Add settings role for setting locale/timezone/pass policy --- molecule/default/converge.yml | 4 + roles/settings/README.md | 42 ++++++++++ roles/settings/defaults/main.yml | 35 ++++++++ roles/settings/handlers/main.yml | 6 ++ roles/settings/meta/argument_specs.yml | 116 +++++++++++++++++++++++++++ roles/settings/meta/main.yml | 19 +++++ roles/settings/tasks/main.yml | 79 ++++++++++++++++++ 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 | 1 + 12 files changed, 400 insertions(+) create mode 100644 roles/settings/README.md create mode 100644 roles/settings/defaults/main.yml create mode 100644 roles/settings/handlers/main.yml create mode 100644 roles/settings/meta/argument_specs.yml create mode 100644 roles/settings/meta/main.yml create mode 100644 roles/settings/tasks/main.yml create mode 100644 roles/settings/templates/archlinux-pam-pw.j2 create mode 100644 roles/settings/templates/debian-pam-pw.j2 create mode 100644 roles/settings/vars/archlinux.yml create mode 100644 roles/settings/vars/debian.yml diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index f0d025e1..b0e73070 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -8,7 +8,11 @@ username: test_user user_groups: ["wheel", "test_group"] ssh_port: 1337 + settings_locales: + - "en_US.UTF-8 UTF-8" + settings_timezone: "GMT" roles: - role: network - role: user - role: ssh + - role: settings diff --git a/roles/settings/README.md b/roles/settings/README.md new file mode 100644 index 00000000..b88ee173 --- /dev/null +++ b/roles/settings/README.md @@ -0,0 +1,42 @@ +# Ansible Role: settings + +This role configure the machine settings (locales/timezone/password policy/etc.) + +## Role Variables + +- `settings_timezone` the timezone default `CET` + +- `settings_locales` the list of locales to be generated. (default: `de_DE`) +- `settings_locales_lang`: `LANG=` default `de_DE.UTF-8` +- `settings_locales_language`: `LANGUAGE=` default `de_DE.UTF-8:de:C` +- `settings_locales_lc_ctype`: `LC_CTYPE=` default `de_DE.UTF-8` +- `settings_locales_lc_numeric`: `LC_NUMERIC=` default `de_DE.UTF-8` +- `settings_locales_lc_time`: `LC_TIME=` default `de_DE.UTF-8` +- `settings_locales_lc_collate`: `LC_COLLATE=` default `de_DE.UTF-8` +- `settings_locales_lc_monetary`: `LC_MONETARY=` default `de_DE.UTF-8` +- `settings_locales_lc_messages`: `LC_MESSAGES=` default `de_DE.UTF-8` +- `settings_locales_lc_paper`: `LC_PAPER=` default `de_DE.UTF-8` +- `settings_locales_lc_name`: `LC_NAME=` default `de_DE.UTF-8` +- `settings_locales_lc_address`: `LC_ADDRESS=` default `de_DE.UTF-8` +- `settings_locales_lc_telephone`: `LC_TELEPHONE=` default `de_DE.UTF-8` +- `settings_locales_lc_measurement`: `LC_MEASUREMENT=` default `de_DE.UTF-8` +- `settings_locales_lc_identification`: `LC_IDENTIFICATION=` default `de_DE.UTF-8` + +- `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://manpages.debian.org/buster/manpages/locale.1.en.html +- https://manpages.debian.org/buster/locales/locale-gen.8.en.html +- https://manpages.debian.org/buster/manpages/localedef.1.en.html +- https://linux.die.net/man/8/pam_pwquality diff --git a/roles/settings/defaults/main.yml b/roles/settings/defaults/main.yml new file mode 100644 index 00000000..a8c2bfa3 --- /dev/null +++ b/roles/settings/defaults/main.yml @@ -0,0 +1,35 @@ +--- +# Locales +settings_locales: + - "de_DE.UTF-8 UTF-8" +settings_locales_lang: "de_DE.UTF-8" +settings_locales_language: "de_DE:de:C" +settings_locales_lc_ctype: "de_DE.UTF-8" +settings_locales_lc_numeric: "de_DE.UTF-8" +settings_locales_lc_time: "de_DE.UTF-8" +settings_locales_lc_collate: "de_DE.UTF-8" +settings_locales_lc_monetary: "de_DE.UTF-8" +settings_locales_lc_messages: "de_DE.UTF-8" +settings_locales_lc_paper: "de_DE.UTF-8" +settings_locales_lc_name: "de_DE.UTF-8" +settings_locales_lc_address: "de_DE.UTF-8" +settings_locales_lc_telephone: "de_DE.UTF-8" +settings_locales_lc_measurement: "de_DE.UTF-8" +settings_locales_lc_identification: "de_DE.UTF-8" + +# Timezone +settings_timezone: "CET" + +# 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/handlers/main.yml b/roles/settings/handlers/main.yml new file mode 100644 index 00000000..fce9c52a --- /dev/null +++ b/roles/settings/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: "Run locale-gen" + become: true + ansible.builtin.command: + cmd: "locale-gen" + changed_when: true diff --git a/roles/settings/meta/argument_specs.yml b/roles/settings/meta/argument_specs.yml new file mode 100644 index 00000000..0ff9459d --- /dev/null +++ b/roles/settings/meta/argument_specs.yml @@ -0,0 +1,116 @@ +--- +argument_specs: + main: + options: + settings_timezone: + type: "str" + description: "timezone configuration to use" + default: "UTC" + settings_locales: + type: "list" + default: ["de_DE.UTF-8 UTF-8"] + elements: "str" + description: "The default locales to generate" + settings_locales_lang: + type: "str" + description: "configuration of LANG=" + default: "de_DE.UTF-8" + settings_locales_language: + type: "str" + description: "configuration of LANGUAGE=" + default: "de_DE.UTF-8:de:C" + settings_locales_lc_ctype: + type: "str" + description: "configuration of LC_CTYPE=" + default: "de_DE.UTF-8" + settings_locales_lc_numeric: + type: "str" + description: "configuration of LC_NUMERIC=" + default: "de_DE.UTF-8" + settings_locales_lc_time: + type: "str" + description: "configuration of LC_TIME=" + default: "de_DE.UTF-8" + settings_locales_lc_collate: + type: "str" + description: "configuration of LC_COLLATE=" + default: "de_DE.UTF-8" + settings_locales_lc_monetary: + type: "str" + description: "configuration of LC_MONETARY=" + default: "de_DE.UTF-8" + settings_locales_lc_messages: + type: "str" + description: "configuration of LC_MESSAGES=" + default: "de_DE.UTF-8" + settings_locales_lc_paper: + type: "str" + description: "configuration of LC_PAPER=" + default: "de_DE.UTF-8" + settings_locales_lc_name: + type: "str" + description: "configuration of LC_NAME=" + default: "de_DE.UTF-8" + settings_locales_lc_address: + type: "str" + description: "configuration of LC_ADDRESS=" + default: "de_DE.UTF-8" + settings_locales_lc_telephone: + type: "str" + description: "configuration of LC_TELEPHONE=" + default: "de_DE.UTF-8" + settings_locales_lc_measurement: + type: "str" + description: "configuration of LC_MEASUREMENT=" + default: "de_DE.UTF-8" + settings_locales_lc_identification: + type: "str" + description: "configuration of LC_IDENTIFICATION=" + default: "de_DE.UTF-8" + 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 new file mode 100644 index 00000000..d8070386 --- /dev/null +++ b/roles/settings/meta/main.yml @@ -0,0 +1,19 @@ +--- +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 new file mode 100644 index 00000000..39fbf55c --- /dev/null +++ b/roles/settings/tasks/main.yml @@ -0,0 +1,79 @@ +--- +- 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 }}" + - "tzdata" + +- name: "Link localtime" + become: true + ansible.builtin.file: + src: "/usr/share/zoneinfo/{{ settings_timezone }}" + dest: "/etc/localtime" + state: "link" + +- name: "Update timezone" + become: true + community.general.timezone: + name: "{{ settings_timezone }}" + +- name: "Update locale.gen" + become: true + ansible.builtin.lineinfile: + dest: "/etc/locale.gen" + mode: "0644" + regexp: "^#{{ item }}" + line: "{{ item }}" + with_items: "{{ settings_locales }}" + notify: "Run locale-gen" + +- name: "Update locale.conf" + become: true + ansible.builtin.copy: + dest: "/etc/locale.conf" + mode: "0644" + content: | + LANG={{ settings_locales_lang }} + LANGUAGE={{ settings_locales_language }} + LC_CTYPE={{ settings_locales_lc_ctype }} + LC_NUMERIC={{ settings_locales_lc_numeric }} + LC_TIME={{ settings_locales_lc_time }} + LC_COLLATE={{ settings_locales_lc_collate }} + LC_MONETARY={{ settings_locales_lc_monetary }} + LC_MESSAGES={{ settings_locales_lc_messages }} + LC_PAPER={{ settings_locales_lc_paper }} + LC_NAME={{ settings_locales_lc_name }} + LC_ADDRESS={{ settings_locales_lc_address }} + LC_TELEPHONE={{ settings_locales_lc_telephone }} + LC_MEASUREMENT={{ settings_locales_lc_measurement }} + LC_IDENTIFICATION={{ settings_locales_lc_identification }} + +- 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 new file mode 100644 index 00000000..7be41ccb --- /dev/null +++ b/roles/settings/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 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 new file mode 100644 index 00000000..521b822a --- /dev/null +++ b/roles/settings/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 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 new file mode 100644 index 00000000..cbe08e80 --- /dev/null +++ b/roles/settings/vars/archlinux.yml @@ -0,0 +1,2 @@ +--- +settings_pw_quality: "libpwquality" diff --git a/roles/settings/vars/debian.yml b/roles/settings/vars/debian.yml new file mode 100644 index 00000000..6767ffd7 --- /dev/null +++ b/roles/settings/vars/debian.yml @@ -0,0 +1,2 @@ +--- +settings_pw_quality: "libpam-pwquality" diff --git a/site.yml b/site.yml index 84de0c4f..9b03e6df 100644 --- a/site.yml +++ b/site.yml @@ -11,6 +11,7 @@ tags: ["required_for_boot"] - role: "ssh" tags: ["required_for_boot"] + - role: "settings" pre_tasks: - name: "Update package cache" -- cgit v1.2.3