diff --git a/molecule/os_hardening/converge.yml b/molecule/os_hardening/converge.yml index c4dd195a..5747250f 100644 --- a/molecule/os_hardening/converge.yml +++ b/molecule/os_hardening/converge.yml @@ -28,6 +28,7 @@ os_security_suid_sgid_whitelist: ['/usr/bin/rlogin'] os_filesystem_whitelist: [] os_yum_repo_file_whitelist: ['foo.repo'] + os_users_without_password_ageing: ['pw_no_ageing'] os_netrc_enabled: false sysctl_config: net.ipv4.ip_forward: 0 diff --git a/molecule/os_hardening/prepare.yml b/molecule/os_hardening/prepare.yml index aab8b77f..0e0079d0 100644 --- a/molecule/os_hardening/prepare.yml +++ b/molecule/os_hardening/prepare.yml @@ -59,6 +59,8 @@ include_tasks: prepare_tasks/yum.yml when: ansible_facts.os_family == 'RedHat' - - name: include YUM prepare tasks + - name: include netrc prepare tasks include_tasks: prepare_tasks/netrc.yml + - name: include password ageing prepare tasks + include_tasks: prepare_tasks/pw_ageing.yml diff --git a/molecule/os_hardening/prepare_tasks/pw_ageing.yml b/molecule/os_hardening/prepare_tasks/pw_ageing.yml new file mode 100644 index 00000000..ce417bac --- /dev/null +++ b/molecule/os_hardening/prepare_tasks/pw_ageing.yml @@ -0,0 +1,9 @@ +- name: Create user those password ageing should not be changed + user: + name: "pw_no_ageing" + password: "$6$mysecretsalt$qJbapG68nyRab3gxvKWPUcs2g3t0oMHSHMnSKecYNpSi3CuZm.GbBqXO8BE6EI6P1JUefhA0qvD7b5LSh./PU1" + +- name: Create user those password ageing should be changed + user: + name: "pw_ageing" + password: "$6$mysecretsalt$qJbapG68nyRab3gxvKWPUcs2g3t0oMHSHMnSKecYNpSi3CuZm.GbBqXO8BE6EI6P1JUefhA0qvD7b5LSh./PU1" diff --git a/molecule/os_hardening/verify.yml b/molecule/os_hardening/verify.yml index 346c45f0..82016774 100644 --- a/molecule/os_hardening/verify.yml +++ b/molecule/os_hardening/verify.yml @@ -37,6 +37,9 @@ name: procps when: ansible_facts.os_family == 'Debian' + - name: include password ageing tests + include_tasks: verify_tasks/pw_ageing.yml + - name: include netrc tests include_tasks: verify_tasks/netrc.yml diff --git a/molecule/os_hardening/verify_tasks/pw_ageing.yml b/molecule/os_hardening/verify_tasks/pw_ageing.yml new file mode 100644 index 00000000..477c78b4 --- /dev/null +++ b/molecule/os_hardening/verify_tasks/pw_ageing.yml @@ -0,0 +1,22 @@ +--- +- name: Get Password Expiry date for use pw_no_ageing + ansible.builtin.shell: chage -l pw_no_ageing | grep "Password expires" | cut -d ":" -f 2 + changed_when: false + register: expiry_date + +- name: Check that the expiry date of pw_no_ageing is "never" + ansible.builtin.assert: + that: + - "expiry_date.stdout | trim == 'never'" + +- name: Get Password Expiry date for pw_ageing + ansible.builtin.shell: chage -l pw_ageing | grep "Password expires" | cut -d ":" -f 2 + changed_when: false + register: expiry_date + +- name: Check that the expiry date of pw_ageing is 30 days + ansible.builtin.assert: + # this uses the date from the expire_date variable and subtracts the current date. + # it should be bigger that the password_expire_min of the user "pw_no_ageing" + that: + - "{{ ( expiry_date.stdout | trim | to_datetime('%b %d, %Y') - ansible_date_time.date | to_datetime('%Y-%m-%d')).days }} == 60" diff --git a/roles/os_hardening/README.md b/roles/os_hardening/README.md index 6d483087..6d3d88c4 100644 --- a/roles/os_hardening/README.md +++ b/roles/os_hardening/README.md @@ -242,6 +242,12 @@ We know that this is the case on Raspberry Pi. - `os_remove_additional_root_users` - Default: `false` - Description: When enabled and there are multiple users with UID=0, only "root" will be kept. Others will be deleted. +- `os_user_pw_ageing` + - Default: `true` + - Description: Set to false to disable password age enforcement on existing users +- `os_users_without_password_ageing` + - Default: `[]` + - Description: List of users, where password ageing should not be enforced even if "os_user_pw_ageing" is enabled - `os_cron_enabled` - Default: `true` - Description: Set to false to disable installing and configuring cron. diff --git a/roles/os_hardening/defaults/main.yml b/roles/os_hardening/defaults/main.yml index 5cce2d5a..27fd4514 100644 --- a/roles/os_hardening/defaults/main.yml +++ b/roles/os_hardening/defaults/main.yml @@ -30,6 +30,12 @@ os_rootuser_pw_ageing: false # When enabled and there are multiple users with UID=0, only "root" will be kept. Others will be deleted. os_remove_additional_root_users: false +# Set to false to disable password age enforcement on existing users +os_user_pw_ageing: true + +# List of users, where password ageing should not be enforced even if "os_user_pw_ageing" is enabled +os_users_without_password_ageing: [] + # Specify system accounts whose login should not be disabled and password not changed os_ignore_users: ['vagrant', 'kitchen'] os_security_kernel_enable_module_loading: true diff --git a/roles/os_hardening/tasks/user_accounts.yml b/roles/os_hardening/tasks/user_accounts.yml index 4c41d6b5..3cdb5ed7 100644 --- a/roles/os_hardening/tasks/user_accounts.yml +++ b/roles/os_hardening/tasks/user_accounts.yml @@ -3,7 +3,34 @@ getent: database: passwd # creates a dict for each user containing UID/HOMEDIR etc... - when: getent_passwd is undefined # skip this task if "getent" has run before + when: getent_passwd is undefined # skip this task if "getent" has run before + +- name: Read local linux shadow database + getent: + database: shadow + +- name: Extract regular (non-system, non-root) accounts from local user database + loop: "{{ getent_passwd.keys() | list }}" + when: + - getent_passwd[item][1]|int >= os_auth_uid_min|int + - getent_passwd[item][1]|int <= os_auth_uid_max|int + - item is not in os_always_ignore_users # skip users from "os_always_ignore_users" list (taken from role "vars") + - item is not in os_ignore_users # skip users from "os_ignore_users" list (taken from role "defaults") + set_fact: + regular_users: "{{ regular_users | default([]) + [item] }}" + +- name: Set password ageing for existing regular (non-system, non-root) accounts + user: + name: "{{ item }}" + password_expire_min: "{{ os_auth_pw_min_age }}" + password_expire_max: "{{ os_auth_pw_max_age }}" + loop: "{{ regular_users }}" + when: + - os_user_pw_ageing + - regular_users is defined and (regular_users | length > 0) + - item not in os_users_without_password_ageing + - getent_shadow[item][0] is not match("\*") # password hashes containing illegal characters like "*" are unusable (locked) and don't need to age + - getent_shadow[item][0] is not match("\!") # password hashes containing illegal characters like "!" are unusable (locked) and don't need to age - name: Extract root account(s) from local user database loop: "{{ getent_passwd.keys() | list }}" @@ -28,7 +55,10 @@ password_expire_min: "{{ os_auth_pw_min_age }}" password_expire_max: "{{ os_auth_pw_max_age }}" loop: "{{ root_users }}" - when: os_rootuser_pw_ageing|bool + when: + - os_rootuser_pw_ageing | bool + - getent_shadow[item][0] is not match("\*") # password hashes containing illegal characters like "*" are unusable (locked) and don't need to age + - getent_shadow[item][0] is not match("\!") # password hashes containing illegal characters like "!" are unusable (locked) and don't need to age - name: Remove additional users with UID=0 ("root" user is not touched) user: