summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmed Abdelhalim <[email protected]>2025-08-11 00:32:05 +0200
committerAhmed Abdelhalim <[email protected]>2025-08-11 00:32:05 +0200
commitf893a7484787d42064eebe5395f3db5c0787ca96 (patch)
treedad8e7cee566c7c34742d0354764229f4098039a
parente0ffa652e8d2950cca4e71fc779280765bb27349 (diff)
Add settings role for setting locale/timezone/pass policy
-rw-r--r--molecule/default/converge.yml4
-rw-r--r--roles/settings/README.md42
-rw-r--r--roles/settings/defaults/main.yml35
-rw-r--r--roles/settings/handlers/main.yml6
-rw-r--r--roles/settings/meta/argument_specs.yml116
-rw-r--r--roles/settings/meta/main.yml19
-rw-r--r--roles/settings/tasks/main.yml79
-rw-r--r--roles/settings/templates/archlinux-pam-pw.j245
-rw-r--r--roles/settings/templates/debian-pam-pw.j249
-rw-r--r--roles/settings/vars/archlinux.yml2
-rw-r--r--roles/settings/vars/debian.yml2
-rw-r--r--site.yml1
12 files changed, 400 insertions, 0 deletions
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"