infra/ansible/roles/base/tasks/users.yml
2026-07-12 15:35:31 -05:00

184 lines
6.5 KiB
YAML

# User and group management tasks
- name: Ensure admin group exists when defined
ansible.builtin.group:
name: "{{ admin_group }}"
when: admin_group | default('') | length > 0
- name: Ensure managed groups exist
ansible.builtin.group:
name: "{{ item.name }}"
gid: "{{ item.gid | default(omit) }}"
state: "{{ item.state | default('present') }}"
loop: "{{ managed_groups | default([]) }}"
loop_control:
label: "{{ item.name }}"
- name: Ensure primary groups for managed users exist
ansible.builtin.group:
name: "{{ item.primary_group }}"
gid: "{{ item.primary_gid | default(omit) }}"
state: present
loop: "{{ managed_users | default([]) }}"
loop_control:
label: "{{ item.name }}"
when: item.primary_group is defined
- name: Ensure managed users are present
ansible.builtin.user:
name: "{{ item.name }}"
uid: "{{ item.uid | default(omit) }}"
shell: "{{ item.shell | default('/bin/bash') }}"
home: "{{ item.home | default(omit) }}"
group: "{{ item.primary_group | default(omit) }}"
groups: "{{ (user_groups | join(',')) if user_groups else omit }}"
append: "{{ true if user_groups else omit }}"
create_home: "{{ item.create_home | default(true) }}"
state: "{{ item.state | default('present') }}"
vars:
user_groups: >-
{{ ((item.groups | default([])) +
([admin_group] if item.admin | default(false) and admin_group | default('') | length > 0 else []))
| unique }}
loop: "{{ managed_users | default([]) }}"
loop_control:
label: "{{ item.name }}"
when: item.state | default('present') != 'absent'
register: user_result
failed_when:
- user_result is failed
- '"currently used by process" not in user_result.msg'
- '"does not exist in /etc/passwd" not in user_result.msg'
- name: Remove managed users marked absent
ansible.builtin.user:
name: "{{ item.name }}"
state: absent
remove: "{{ item.remove | default(false) }}"
loop: "{{ managed_users | default([]) }}"
loop_control:
label: "{{ item.name }}"
when: item.state | default('present') == 'absent'
- name: Collect unique GitHub usernames for key fetch
ansible.builtin.set_fact:
_gh_usernames: >-
{{ managed_users | default([])
| map(attribute='authorized_keys', default=[])
| flatten
| selectattr('type', 'defined')
| selectattr('type', 'equalto', 'github')
| map(attribute='value')
| unique
| list }}
when: managed_users | default([]) | length > 0
- name: Fetch GitHub SSH keys
ansible.builtin.uri:
url: "https://github.com/{{ item }}.keys"
return_content: true
loop: "{{ _gh_usernames | default([]) }}"
register: _gh_fetched
delegate_to: localhost
run_once: true
become: false
when: _gh_usernames | default([]) | length > 0
- name: Build GitHub keys lookup dict
ansible.builtin.set_fact:
_gh_keys: "{{ _gh_keys | default({}) | combine({item.item: item.content}) }}"
loop: "{{ _gh_fetched.results | default([]) }}"
when: _gh_fetched.results | default([]) | length > 0
no_log: true
- name: Install authorized keys from GitHub
ansible.posix.authorized_key:
user: "{{ item.0.name }}"
state: "{{ item.1.state | default('present') }}"
key: "{{ _gh_keys[item.1.value] }}"
manage_dir: "{{ item.1.manage_dir | default(true) }}"
exclusive: "{{ item.1.exclusive | default(false) }}"
loop: "{{ managed_users | default([]) | subelements('authorized_keys', skip_missing=True) }}"
loop_control:
label: "{{ item.0.name }} (github: {{ item.1.value }})"
when:
- item.1.type | default('github') == 'github'
- item.0.state | default('present') != 'absent'
- name: Install authorized keys from files
ansible.posix.authorized_key:
user: "{{ item.0.name }}"
state: "{{ item.1.state | default('present') }}"
key: "{{ lookup('file', item.1.value) }}"
manage_dir: "{{ item.1.manage_dir | default(true) }}"
exclusive: "{{ item.1.exclusive | default(false) }}"
loop: "{{ managed_users | default([]) | subelements('authorized_keys', skip_missing=True) }}"
loop_control:
label: "{{ item.0.name }} (file: {{ item.1.value }})"
when:
- item.1.type | default('github') == 'file'
- item.0.state | default('present') != 'absent'
- name: Install authorized keys from inline strings
ansible.posix.authorized_key:
user: "{{ item.0.name }}"
state: "{{ item.1.state | default('present') }}"
key: "{{ item.1.value }}"
manage_dir: "{{ item.1.manage_dir | default(true) }}"
exclusive: "{{ item.1.exclusive | default(false) }}"
loop: "{{ managed_users | default([]) | subelements('authorized_keys', skip_missing=True) }}"
loop_control:
label: "{{ item.0.name }} (string key)"
when:
- item.1.type | default('github') == 'string'
- item.0.state | default('present') != 'absent'
- name: Configure sudo for ansible user
block:
- name: Render sudoers content for ansible
ansible.builtin.set_fact:
_sudoers_ansible_content: "{{ lookup('template', 'sudoers_ansible.j2') }}"
- name: Check current sudoers for ansible
ansible.builtin.slurp:
path: "{{ sudoers_dir }}/ansible"
register: _sudoers_ansible_current
failed_when: false
- name: Write and validate sudoers for ansible
ansible.builtin.copy:
content: "{{ _sudoers_ansible_content }}"
dest: "{{ sudoers_dir }}/ansible"
mode: '0440'
validate: "{{ visudo_path }} -cf %s"
when:
- _sudoers_ansible_current.content | default('') | b64decode != _sudoers_ansible_content
- name: Configure sudo for graham user
block:
- name: Render sudoers content for graham
ansible.builtin.set_fact:
_sudoers_graham_content: "{{ lookup('template', 'sudoers_graham.j2') }}"
- name: Check current sudoers for graham
ansible.builtin.slurp:
path: "{{ sudoers_dir }}/graham"
register: _sudoers_graham_current
failed_when: false
- name: Write and validate sudoers for graham
ansible.builtin.copy:
content: "{{ _sudoers_graham_content }}"
dest: "{{ sudoers_dir }}/graham"
mode: '0440'
validate: "{{ visudo_path }} -cf %s"
when:
- _sudoers_graham_current.content | default('') | b64decode != _sudoers_graham_content
- name: Enable passwordless sudo for admin group
ansible.builtin.lineinfile:
path: "{{ sudoers_path }}"
state: present
regexp: '^%{{ admin_group }}'
line: '{{ admin_group }} ALL=(ALL) NOPASSWD: ALL'
validate: "{{ visudo_path }} -cf %s"