From 14e6bf12fdbaa2fd056227466c13b69ac3c0fc00 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 13:34:32 +0400 Subject: [PATCH 1/9] Migrate the `users` role --- roles/users/README.md | 55 +++++++++ roles/users/defaults/main.yml | 2 + roles/users/handlers/main.yml | 1 + roles/users/meta/main.yml | 16 +++ roles/users/molecule/default/converge.yml | 10 ++ roles/users/molecule/default/molecule.yml | 27 +++++ roles/users/molecule/default/user0.keys | 1 + roles/users/molecule/default/users.yml | 22 ++++ roles/users/molecule/default/verify.yml | 64 +++++++++++ .../molecule/users_from_var/converge.yml | 31 +++++ .../molecule/users_from_var/molecule.yml | 27 +++++ .../users/molecule/users_from_var/user0.keys | 1 + .../users/molecule/users_from_var/verify.yml | 64 +++++++++++ roles/users/tasks/create.yml | 106 ++++++++++++++++++ roles/users/tasks/main.yml | 14 +++ roles/users/tasks/openssh.yml | 14 +++ roles/users/templates/slice.j2 | 12 ++ roles/users/vars/main.yml | 4 + 18 files changed, 471 insertions(+) create mode 100644 roles/users/README.md create mode 100644 roles/users/defaults/main.yml create mode 100644 roles/users/handlers/main.yml create mode 100644 roles/users/meta/main.yml create mode 100644 roles/users/molecule/default/converge.yml create mode 100644 roles/users/molecule/default/molecule.yml create mode 100644 roles/users/molecule/default/user0.keys create mode 100644 roles/users/molecule/default/users.yml create mode 100644 roles/users/molecule/default/verify.yml create mode 100644 roles/users/molecule/users_from_var/converge.yml create mode 100644 roles/users/molecule/users_from_var/molecule.yml create mode 100644 roles/users/molecule/users_from_var/user0.keys create mode 100644 roles/users/molecule/users_from_var/verify.yml create mode 100644 roles/users/tasks/create.yml create mode 100644 roles/users/tasks/main.yml create mode 100644 roles/users/tasks/openssh.yml create mode 100644 roles/users/templates/slice.j2 create mode 100644 roles/users/vars/main.yml diff --git a/roles/users/README.md b/roles/users/README.md new file mode 100644 index 0000000..90f8563 --- /dev/null +++ b/roles/users/README.md @@ -0,0 +1,55 @@ +ansible-users +========= + +Create user accounts according to a YAML manifest. + +Controls: + - username and UID + - groupname and GID + - homedir creation + - GECOS field (typically full user name) + - shell + - SSH public key deployment to `.ssh/authorized_keys` + - account expiration + - memory, swap and CPU allocation limits via user slices + +Check out [corvus-migratorius/ansible-disk-quotas](https://github.com/corvus-migratorius/ansible-disk-quotas) for controlling non-root filesystem quotas. + +Requirements +------------ + +- systemd +- openssh + +Role Variables +-------------- + +`manifest_path`: a YAML file containing user definitions (see `molecule/default/users.yml` for an example) + +`users`: a list of objects mirroring the YAML structure expected by `manifest_path`; takes precedence over it + +`common_memory_max`: e.g. `"500M"`, optional + +`common_swap_max`: e.g. `"2G"`, optional + +`common_cpu_quota`: e.g. `"100%"`, optional + +Dependencies +------------ + +Example Playbook +---------------- + +- Configuring users in a YAML manifest: [converge.yml](molecule/default/converge.yml) +- Configuring user list as a variable: [converge.yml](molecule/users_from_var/converge.yml) + +License +------- + +BSD + +Author Information +------------------ + +corvus-migratorius@proton.me +masayganova@gmail.com diff --git a/roles/users/defaults/main.yml b/roles/users/defaults/main.yml new file mode 100644 index 0000000..cce8b27 --- /dev/null +++ b/roles/users/defaults/main.yml @@ -0,0 +1,2 @@ +--- +default_home_root: /home diff --git a/roles/users/handlers/main.yml b/roles/users/handlers/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/users/handlers/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/users/meta/main.yml b/roles/users/meta/main.yml new file mode 100644 index 0000000..06fac82 --- /dev/null +++ b/roles/users/meta/main.yml @@ -0,0 +1,16 @@ +galaxy_info: + role_name: users + namespace: genlab + author: Alexander Gorelyshev + company: Genlab, LLC + description: Deploy user accounts + license: GPL-2.0-or-later + min_ansible_version: "2.1" + + platforms: + - name: Ubuntu + versions: [ "focal", "jammy", "noble" ] + + galaxy_tags: [] + +dependencies: [] diff --git a/roles/users/molecule/default/converge.yml b/roles/users/molecule/default/converge.yml new file mode 100644 index 0000000..38fd392 --- /dev/null +++ b/roles/users/molecule/default/converge.yml @@ -0,0 +1,10 @@ +--- +- name: Converge + hosts: all + + roles: + - role: genlab.users + manifest_path: "users.yml" + common_memory_max: "500M" + common_swap_max: "2G" + common_cpu_quota: "100%" diff --git a/roles/users/molecule/default/molecule.yml b/roles/users/molecule/default/molecule.yml new file mode 100644 index 0000000..d82158e --- /dev/null +++ b/roles/users/molecule/default/molecule.yml @@ -0,0 +1,27 @@ +--- +dependency: + name: galaxy + +driver: + name: docker + +platforms: + - name: ubuntu + image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest + pre_build_image: true + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + +provisioner: + name: ansible + +verifier: + name: ansible + +lint: | + set -e + yamllint . + ansible-lint . diff --git a/roles/users/molecule/default/user0.keys b/roles/users/molecule/default/user0.keys new file mode 100644 index 0000000..5a478b1 --- /dev/null +++ b/roles/users/molecule/default/user0.keys @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMh9Y+wR4LH8lWJjJXqHn76kSoTRujkab+PYwD3IReFh user0@hostname diff --git a/roles/users/molecule/default/users.yml b/roles/users/molecule/default/users.yml new file mode 100644 index 0000000..39687a1 --- /dev/null +++ b/roles/users/molecule/default/users.yml @@ -0,0 +1,22 @@ +--- +users: + - name: "user0" + uid: 1001 + gid: 1004 + full_name: "User Zero" + state: "present" + groups: [sudo] + create_home: true + home_root: "/tmp/someplace" + shell: "/bin/bash" + pubkeys_file: "user0.keys" + + - name: "user1" + uid: 1002 + full_name: "User One" + state: "present" + groups: [] + create_home: true + shell: "/bin/sh" + expires: "2024-07-23 12:00:00" + generate_ssh_key: true diff --git a/roles/users/molecule/default/verify.yml b/roles/users/molecule/default/verify.yml new file mode 100644 index 0000000..a911e9d --- /dev/null +++ b/roles/users/molecule/default/verify.yml @@ -0,0 +1,64 @@ +--- +- name: Verify + hosts: all + gather_facts: true + any_errors_fatal: true + + tasks: + - name: "Get user info from /etc/passwd" + register: etc_passwd + changed_when: false + ansible.builtin.shell: + cmd: set -o pipefail; cat /etc/passwd | grep user + executable: /bin/bash + + - name: "Get user info from /etc/shadow" + register: etc_shadow + changed_when: false + ansible.builtin.shell: + cmd: set -o pipefail; cat /etc/shadow | grep user + executable: /bin/bash + + # using a hack since here the date of last password change == date of account creation + - name: "Verify expected account configuration" + vars: + expected_shadow: + - "user0:!:{{ (ansible_date_time.epoch | int) // 86400 }}:0:99999:7:::" + - "user1:!:{{ (ansible_date_time.epoch | int) // 86400 }}:0:99999:7::19927:" + expected_passwd: + - "user0:x:1001:1004:User Zero:/tmp/someplace/user0:/bin/bash" + - "user1:x:1002:1002:User One:/home/user1:/bin/sh" + ansible.builtin.assert: + that: + - etc_passwd.stdout_lines == expected_passwd + - etc_shadow.stdout_lines == expected_shadow + + - name: "Get SSH key contents for user1" + register: user1_ssh_key + changed_when: false + ansible.builtin.command: + cmd: cat /home/user1/.ssh/id_ed25519.pub + + - name: "Verify the SSH key contents for user1" + ansible.builtin.assert: + that: "'user1@ubuntu' in user1_ssh_key.stdout" + + - name: "Get the user slice drop-in for user0" + register: user0_slice_dropin + changed_when: false + ansible.builtin.command: + cmd: cat /etc/systemd/system/user-1001.slice.d/50-limits.conf + + - name: "Verify drop-in contens" + vars: + expected: + - "[Slice]" + - "MemoryAccounting=1" + - "MemoryMax=500M" + - "MemorySwapMax=2G" + - "CPUAccounting=1" + - "CPUQuota=100%" + ansible.builtin.assert: + that: "user0_slice_dropin.stdout_lines == expected" + success_msg: "Got the expected limits slice drop-in" + fail_msg: "Unexpected limits slice drop-in contents" diff --git a/roles/users/molecule/users_from_var/converge.yml b/roles/users/molecule/users_from_var/converge.yml new file mode 100644 index 0000000..a6e93e9 --- /dev/null +++ b/roles/users/molecule/users_from_var/converge.yml @@ -0,0 +1,31 @@ +--- +- name: Converge + hosts: all + + roles: + - role: genlab.users + manifest_path: "some-none-existent-file" # the 'users' variable is supposed to take precedence + common_memory_max: "500M" + common_swap_max: "2G" + common_cpu_quota: "100%" + users: + - name: "user0" + uid: 1001 + gid: 1004 + full_name: "User Zero" + state: "present" + groups: [sudo] + create_home: true + home_root: "/tmp/someplace" + shell: "/bin/bash" + pubkeys_file: "user0.keys" + + - name: "user1" + uid: 1002 + full_name: "User One" + state: "present" + groups: [] + create_home: true + shell: "/bin/sh" + expires: "2024-07-23 12:00:00" + generate_ssh_key: true diff --git a/roles/users/molecule/users_from_var/molecule.yml b/roles/users/molecule/users_from_var/molecule.yml new file mode 100644 index 0000000..d82158e --- /dev/null +++ b/roles/users/molecule/users_from_var/molecule.yml @@ -0,0 +1,27 @@ +--- +dependency: + name: galaxy + +driver: + name: docker + +platforms: + - name: ubuntu + image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest + pre_build_image: true + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + +provisioner: + name: ansible + +verifier: + name: ansible + +lint: | + set -e + yamllint . + ansible-lint . diff --git a/roles/users/molecule/users_from_var/user0.keys b/roles/users/molecule/users_from_var/user0.keys new file mode 100644 index 0000000..5a478b1 --- /dev/null +++ b/roles/users/molecule/users_from_var/user0.keys @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMh9Y+wR4LH8lWJjJXqHn76kSoTRujkab+PYwD3IReFh user0@hostname diff --git a/roles/users/molecule/users_from_var/verify.yml b/roles/users/molecule/users_from_var/verify.yml new file mode 100644 index 0000000..a911e9d --- /dev/null +++ b/roles/users/molecule/users_from_var/verify.yml @@ -0,0 +1,64 @@ +--- +- name: Verify + hosts: all + gather_facts: true + any_errors_fatal: true + + tasks: + - name: "Get user info from /etc/passwd" + register: etc_passwd + changed_when: false + ansible.builtin.shell: + cmd: set -o pipefail; cat /etc/passwd | grep user + executable: /bin/bash + + - name: "Get user info from /etc/shadow" + register: etc_shadow + changed_when: false + ansible.builtin.shell: + cmd: set -o pipefail; cat /etc/shadow | grep user + executable: /bin/bash + + # using a hack since here the date of last password change == date of account creation + - name: "Verify expected account configuration" + vars: + expected_shadow: + - "user0:!:{{ (ansible_date_time.epoch | int) // 86400 }}:0:99999:7:::" + - "user1:!:{{ (ansible_date_time.epoch | int) // 86400 }}:0:99999:7::19927:" + expected_passwd: + - "user0:x:1001:1004:User Zero:/tmp/someplace/user0:/bin/bash" + - "user1:x:1002:1002:User One:/home/user1:/bin/sh" + ansible.builtin.assert: + that: + - etc_passwd.stdout_lines == expected_passwd + - etc_shadow.stdout_lines == expected_shadow + + - name: "Get SSH key contents for user1" + register: user1_ssh_key + changed_when: false + ansible.builtin.command: + cmd: cat /home/user1/.ssh/id_ed25519.pub + + - name: "Verify the SSH key contents for user1" + ansible.builtin.assert: + that: "'user1@ubuntu' in user1_ssh_key.stdout" + + - name: "Get the user slice drop-in for user0" + register: user0_slice_dropin + changed_when: false + ansible.builtin.command: + cmd: cat /etc/systemd/system/user-1001.slice.d/50-limits.conf + + - name: "Verify drop-in contens" + vars: + expected: + - "[Slice]" + - "MemoryAccounting=1" + - "MemoryMax=500M" + - "MemorySwapMax=2G" + - "CPUAccounting=1" + - "CPUQuota=100%" + ansible.builtin.assert: + that: "user0_slice_dropin.stdout_lines == expected" + success_msg: "Got the expected limits slice drop-in" + fail_msg: "Unexpected limits slice drop-in contents" diff --git a/roles/users/tasks/create.yml b/roles/users/tasks/create.yml new file mode 100644 index 0000000..309a9c5 --- /dev/null +++ b/roles/users/tasks/create.yml @@ -0,0 +1,106 @@ +--- +- name: "Create | Get current user's group entity info ({{ user.name }})" + failed_when: false + ansible.builtin.getent: + database: group + key: "{{ user.name }}" + split: ":" + +- name: "Create | Create user group ('{{ user.name }}')" # to handle cases where GID!=UID + when: + - getent_group[user.name] is not defined + ansible.builtin.group: + name: "{{ user.name }}" + gid: "{{ user.gid is defined | ternary(user.gid, user.uid) }}" + state: "{{ user.state }}" + +- name: "Create | Create user account ('{{ user.name }}')" + vars: + home_root: "{{ user.home_root is defined | ternary(user.home_root, default_home_root) }}" + ansible.builtin.user: + name: "{{ user.name }}" + state: "{{ user.state }}" + uid: "{{ user.uid }}" + group: "{{ user.name }}" + create_home: "{{ user.create_home | default(true) }}" + home: "{{ home_root }}/{{ user.name }}" + comment: "{{ user.full_name }}" + shell: "{{ user.shell | default('/bin/bash') }}" + groups: "{{ user.groups | default([]) }}" + append: false + generate_ssh_key: "{{ user.generate_ssh_key | default(false) }}" + ssh_key_type: "{{ user.ssh_key_type | default('ed25519') }}" + ssh_key_comment: "{{ user.name }}@{{ ansible_nodename }}" + # password_lock: "{{ user.password_lock | default('false') }}" + +- name: "Create | Set user account expiration date where defined ('{{ user.name }}')" + when: user.expires is defined + ansible.builtin.user: + name: "{{ user.name }}" + state: "{{ user.state }}" + expires: "{{ (user.expires | to_datetime).strftime('%s') }}" + +- name: "Create | Ensure no user account expiration date where undefined ('{{ user.name }}')" + when: user.expires is undefined + ansible.builtin.user: + name: "{{ user.name }}" + state: "{{ user.state }}" + expires: "-1" + +- name: "Create | Deploy SSH public key to 'authorized_keys' files ('{{ user.name }}')" + when: (user.pubkeys_file is defined) and (user.pubkeys_file != "") + ansible.posix.authorized_key: + user: "{{ user.name }}" + key: "{{ lookup('file', user.pubkeys_file) }}" + key_options: "{{ user.options | default('') }}" + exclusive: true + +- name: "Create | Create a systemd slice directory ('{{ user.name }}')" + ansible.builtin.file: + path: /etc/systemd/system/user-{{ user.uid }}.slice.d + state: directory + owner: root + group: root + mode: '0750' + +# Set user's maximum memory limit to 'memory_limit' defined in the user manifest_path. +# If 'memory_limit' is undefined there, use 'common_memory_max' as fallback. +# If 'common_memory_max' is also undefined, set to "", which should be ignored by the template. +# - 'memory_max:' may use postfix like K, M, G. +# - 'cpu_quota': "100%" for 1 full core. +# REF https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html +# REF https://serverfault.com/a/1092803 +- name: "Create | Create/update a systemd slice limits config ('{{ user.name }}')" + when: user.uid is defined + register: users_limit_state + vars: + memory_max: "{{ (user.memory_max is defined and user.memory_max != '') | ternary(user.memory_max, common_memory_max) | default('') }}" + swap_max: "{{ (user.swap_max is defined and user.swap_max != '') | ternary(user.swap_max, common_swap_max) | default('') }}" + cpu_quota: "{{ (user.cpu_quota is defined and user.cpu_quota != '') | ternary(user.cpu_quota, common_cpu_quota) | default('') }}" + ansible.builtin.template: + src: slice.j2 + dest: "/etc/systemd/system/user-{{ user.uid }}.slice.d/50-limits.conf" + owner: root + group: root + mode: '0750' + +- name: "Create | Enable user systemd service ('{{ user.name }}')" # noqa: no-handler + when: users_limit_state.changed + ansible.builtin.systemd_service: + name: user@{{ user.uid }}.service + enabled: true + +# User service cannot be restarted if: +# - the account is expired (we check for that) +# - password change was enforced +# Sometimes restarting fails with obscure 'status=219/CGROUP', but works fine after a retry +# Didn't debug this yet, sorry ;( +- name: "Restart user service (systemd limits) ('{{ user.name }}')" + when: + - users_limit_state.changed + - user.expires is undefined or (user.expires | to_datetime).strftime('%s') > now(fmt='%s') + retries: 3 + ansible.builtin.systemd_service: + name: user@{{ user.uid }}.service + state: restarted + daemon_reload: true diff --git a/roles/users/tasks/main.yml b/roles/users/tasks/main.yml new file mode 100644 index 0000000..6d3f166 --- /dev/null +++ b/roles/users/tasks/main.yml @@ -0,0 +1,14 @@ +--- +- name: "Include OpenSSH client installation tasks" + ansible.builtin.include_tasks: openssh.yml + +- name: "Include user definition vars" + when: (manifest_path is defined) and (manifest_path != "") and users is not defined + ansible.builtin.include_vars: + file: "{{ manifest_path }}" + +- name: "Create user accounts" + loop: "{{ users }}" + loop_control: + loop_var: user + ansible.builtin.include_tasks: create.yml diff --git a/roles/users/tasks/openssh.yml b/roles/users/tasks/openssh.yml new file mode 100644 index 0000000..753bf52 --- /dev/null +++ b/roles/users/tasks/openssh.yml @@ -0,0 +1,14 @@ +--- +- name: "OpenSSH | Ensure openssh client tools are installed" + when: ansible_os_family == "Debian" + ansible.builtin.apt: + name: openssh-client + state: present + cache_valid_time: 3000 + +- name: "OpenSSH | Ensure openssh client tools are installed" + when: ansible_os_family == "RedHat" + ansible.builtin.dnf: + name: openssh-clients + state: present + update_cache: true diff --git a/roles/users/templates/slice.j2 b/roles/users/templates/slice.j2 new file mode 100644 index 0000000..5a9b1f5 --- /dev/null +++ b/roles/users/templates/slice.j2 @@ -0,0 +1,12 @@ +[Slice] +{% if memory_max is defined and memory_max != "" %} +MemoryAccounting=1 +MemoryMax={{ memory_max }} +MemorySwapMax={{ swap_max }} +{% else %} +{% endif %} +{% if cpu_quota is defined and cpu_quota != "" %} +CPUAccounting=1 +CPUQuota={{ cpu_quota }} +{% else %} +{% endif %} diff --git a/roles/users/vars/main.yml b/roles/users/vars/main.yml new file mode 100644 index 0000000..4e004e9 --- /dev/null +++ b/roles/users/vars/main.yml @@ -0,0 +1,4 @@ +--- +# vars file for user +inv_groupname: "{{ hostvars[inventory_hostname].group_names[0] }}" +inv_hostname: "{{ inventory_hostname }}" From 7056bfe278640f7dafe2a49fbacf325c8ec4de0b Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 13:39:05 +0400 Subject: [PATCH 2/9] Fix role name --- roles/users/molecule/default/converge.yml | 2 +- roles/users/molecule/users_from_var/converge.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/users/molecule/default/converge.yml b/roles/users/molecule/default/converge.yml index 38fd392..7cf7ea1 100644 --- a/roles/users/molecule/default/converge.yml +++ b/roles/users/molecule/default/converge.yml @@ -3,7 +3,7 @@ hosts: all roles: - - role: genlab.users + - role: genlab.common.users manifest_path: "users.yml" common_memory_max: "500M" common_swap_max: "2G" diff --git a/roles/users/molecule/users_from_var/converge.yml b/roles/users/molecule/users_from_var/converge.yml index a6e93e9..4993887 100644 --- a/roles/users/molecule/users_from_var/converge.yml +++ b/roles/users/molecule/users_from_var/converge.yml @@ -3,7 +3,7 @@ hosts: all roles: - - role: genlab.users + - role: genlab.common.users manifest_path: "some-none-existent-file" # the 'users' variable is supposed to take precedence common_memory_max: "500M" common_swap_max: "2G" From 1a9cb69d58c3d1c97e501cce8b7bebcdf7ef84f8 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 13:39:17 +0400 Subject: [PATCH 3/9] Update metadata --- roles/users/meta/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/users/meta/main.yml b/roles/users/meta/main.yml index 06fac82..03efaab 100644 --- a/roles/users/meta/main.yml +++ b/roles/users/meta/main.yml @@ -3,13 +3,13 @@ galaxy_info: namespace: genlab author: Alexander Gorelyshev company: Genlab, LLC - description: Deploy user accounts + description: Configure user accounts license: GPL-2.0-or-later min_ansible_version: "2.1" platforms: - name: Ubuntu - versions: [ "focal", "jammy", "noble" ] + versions: ["jammy", "noble"] galaxy_tags: [] From 0c46455a638e0b6669e0a34ff12cbef0d36f8a9f Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 13:44:02 +0400 Subject: [PATCH 4/9] Fix linting issues --- roles/users/defaults/main.yml | 2 +- roles/users/molecule/default/verify.yml | 16 ++++++++-------- roles/users/molecule/users_from_var/verify.yml | 16 ++++++++-------- roles/users/tasks/create.yml | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/roles/users/defaults/main.yml b/roles/users/defaults/main.yml index cce8b27..755fb15 100644 --- a/roles/users/defaults/main.yml +++ b/roles/users/defaults/main.yml @@ -1,2 +1,2 @@ --- -default_home_root: /home +users_default_home_root: /home diff --git a/roles/users/molecule/default/verify.yml b/roles/users/molecule/default/verify.yml index a911e9d..da1af66 100644 --- a/roles/users/molecule/default/verify.yml +++ b/roles/users/molecule/default/verify.yml @@ -6,14 +6,14 @@ tasks: - name: "Get user info from /etc/passwd" - register: etc_passwd + register: users_etc_passwd changed_when: false ansible.builtin.shell: cmd: set -o pipefail; cat /etc/passwd | grep user executable: /bin/bash - name: "Get user info from /etc/shadow" - register: etc_shadow + register: users_etc_shadow changed_when: false ansible.builtin.shell: cmd: set -o pipefail; cat /etc/shadow | grep user @@ -30,21 +30,21 @@ - "user1:x:1002:1002:User One:/home/user1:/bin/sh" ansible.builtin.assert: that: - - etc_passwd.stdout_lines == expected_passwd - - etc_shadow.stdout_lines == expected_shadow + - users_etc_passwd.stdout_lines == expected_passwd + - users_etc_shadow.stdout_lines == expected_shadow - name: "Get SSH key contents for user1" - register: user1_ssh_key + register: users_user1_ssh_key changed_when: false ansible.builtin.command: cmd: cat /home/user1/.ssh/id_ed25519.pub - name: "Verify the SSH key contents for user1" ansible.builtin.assert: - that: "'user1@ubuntu' in user1_ssh_key.stdout" + that: "'user1@ubuntu' in users_user1_ssh_key.stdout" - name: "Get the user slice drop-in for user0" - register: user0_slice_dropin + register: users_user0_slice_dropin changed_when: false ansible.builtin.command: cmd: cat /etc/systemd/system/user-1001.slice.d/50-limits.conf @@ -59,6 +59,6 @@ - "CPUAccounting=1" - "CPUQuota=100%" ansible.builtin.assert: - that: "user0_slice_dropin.stdout_lines == expected" + that: "users_user0_slice_dropin.stdout_lines == expected" success_msg: "Got the expected limits slice drop-in" fail_msg: "Unexpected limits slice drop-in contents" diff --git a/roles/users/molecule/users_from_var/verify.yml b/roles/users/molecule/users_from_var/verify.yml index a911e9d..da1af66 100644 --- a/roles/users/molecule/users_from_var/verify.yml +++ b/roles/users/molecule/users_from_var/verify.yml @@ -6,14 +6,14 @@ tasks: - name: "Get user info from /etc/passwd" - register: etc_passwd + register: users_etc_passwd changed_when: false ansible.builtin.shell: cmd: set -o pipefail; cat /etc/passwd | grep user executable: /bin/bash - name: "Get user info from /etc/shadow" - register: etc_shadow + register: users_etc_shadow changed_when: false ansible.builtin.shell: cmd: set -o pipefail; cat /etc/shadow | grep user @@ -30,21 +30,21 @@ - "user1:x:1002:1002:User One:/home/user1:/bin/sh" ansible.builtin.assert: that: - - etc_passwd.stdout_lines == expected_passwd - - etc_shadow.stdout_lines == expected_shadow + - users_etc_passwd.stdout_lines == expected_passwd + - users_etc_shadow.stdout_lines == expected_shadow - name: "Get SSH key contents for user1" - register: user1_ssh_key + register: users_user1_ssh_key changed_when: false ansible.builtin.command: cmd: cat /home/user1/.ssh/id_ed25519.pub - name: "Verify the SSH key contents for user1" ansible.builtin.assert: - that: "'user1@ubuntu' in user1_ssh_key.stdout" + that: "'user1@ubuntu' in users_user1_ssh_key.stdout" - name: "Get the user slice drop-in for user0" - register: user0_slice_dropin + register: users_user0_slice_dropin changed_when: false ansible.builtin.command: cmd: cat /etc/systemd/system/user-1001.slice.d/50-limits.conf @@ -59,6 +59,6 @@ - "CPUAccounting=1" - "CPUQuota=100%" ansible.builtin.assert: - that: "user0_slice_dropin.stdout_lines == expected" + that: "users_user0_slice_dropin.stdout_lines == expected" success_msg: "Got the expected limits slice drop-in" fail_msg: "Unexpected limits slice drop-in contents" diff --git a/roles/users/tasks/create.yml b/roles/users/tasks/create.yml index 309a9c5..f952d08 100644 --- a/roles/users/tasks/create.yml +++ b/roles/users/tasks/create.yml @@ -16,7 +16,7 @@ - name: "Create | Create user account ('{{ user.name }}')" vars: - home_root: "{{ user.home_root is defined | ternary(user.home_root, default_home_root) }}" + home_root: "{{ user.home_root is defined | ternary(user.home_root, users_default_home_root) }}" ansible.builtin.user: name: "{{ user.name }}" state: "{{ user.state }}" From b0fbb7f17c4c44072581be9fc2f798816d865344 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 13:44:17 +0400 Subject: [PATCH 5/9] Drop unused variables --- roles/users/vars/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/roles/users/vars/main.yml b/roles/users/vars/main.yml index 4e004e9..ed97d53 100644 --- a/roles/users/vars/main.yml +++ b/roles/users/vars/main.yml @@ -1,4 +1 @@ --- -# vars file for user -inv_groupname: "{{ hostvars[inventory_hostname].group_names[0] }}" -inv_hostname: "{{ inventory_hostname }}" From 4a167c47985cf3265a14ec6d2c8b22cd0775e603 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 13:48:41 +0400 Subject: [PATCH 6/9] Move the user manifest into a subfolder to work around `ansible-lint` --- roles/users/molecule/default/{ => configuration}/users.yml | 0 roles/users/molecule/default/converge.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename roles/users/molecule/default/{ => configuration}/users.yml (100%) diff --git a/roles/users/molecule/default/users.yml b/roles/users/molecule/default/configuration/users.yml similarity index 100% rename from roles/users/molecule/default/users.yml rename to roles/users/molecule/default/configuration/users.yml diff --git a/roles/users/molecule/default/converge.yml b/roles/users/molecule/default/converge.yml index 7cf7ea1..0327060 100644 --- a/roles/users/molecule/default/converge.yml +++ b/roles/users/molecule/default/converge.yml @@ -4,7 +4,7 @@ roles: - role: genlab.common.users - manifest_path: "users.yml" + manifest_path: "configuration/users.yml" common_memory_max: "500M" common_swap_max: "2G" common_cpu_quota: "100%" From 794d0e1dc499e666e272e5e45bf2f4ada8e2ff0f Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 13:56:10 +0400 Subject: [PATCH 7/9] Bump Galaxy collection version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index c26b9f9..8c51504 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: genlab name: common -version: 0.22.0 +version: 0.23.0 readme: README.md authors: - Alexander Gorelyshev (corvus-migratorius@proton.me) From 4beafb13f137b54693df22d7c0be88ce09cedcdd Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 13:56:23 +0400 Subject: [PATCH 8/9] Add a link to the role's README file --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bd37b07..5d75026 100644 --- a/README.md +++ b/README.md @@ -25,5 +25,6 @@ - [smartctl_exporter](roles/smartctl_exporter/README.md) - [swapfile](roles/swapfile/README.md) - [ufw](roles/ufw/README.md) +- [users](roles/users/README.md) - [wg_hub](roles/wg_hub/README.md) -- [wg_spoke](roles/wg_spoke/README.md) \ No newline at end of file +- [wg_spoke](roles/wg_spoke/README.md) From 02178246c9bef8390da5f7e54a6894f32752dcb6 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Thu, 18 Dec 2025 14:35:42 +0400 Subject: [PATCH 9/9] Add `passlib` - a missing `users` role control node dependency --- ci/requirements.txt | 1 + roles/users/README.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/ci/requirements.txt b/ci/requirements.txt index 456b2b7..fcd67d5 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -1 +1,2 @@ ansible-core==2.18.11 +passlib==1.7.4 diff --git a/roles/users/README.md b/roles/users/README.md index 90f8563..30fd61c 100644 --- a/roles/users/README.md +++ b/roles/users/README.md @@ -18,9 +18,15 @@ Check out [corvus-migratorius/ansible-disk-quotas](https://github.com/corvus-mig Requirements ------------ +Target node: + - systemd - openssh +Controller: + +- `passlib` (for working with user passwords) + Role Variables --------------