diff options
Diffstat (limited to 'roles/unix_users')
-rw-r--r-- | roles/unix_users/defaults/main.yml | 23 | ||||
-rw-r--r-- | roles/unix_users/subplot.md | 62 | ||||
-rw-r--r-- | roles/unix_users/subplot.py | 59 | ||||
-rw-r--r-- | roles/unix_users/subplot.yaml | 29 | ||||
-rw-r--r-- | roles/unix_users/tasks/main.yml | 21 |
5 files changed, 169 insertions, 25 deletions
diff --git a/roles/unix_users/defaults/main.yml b/roles/unix_users/defaults/main.yml index ea33fc9..cfcf754 100644 --- a/roles/unix_users/defaults/main.yml +++ b/roles/unix_users/defaults/main.yml @@ -1,3 +1,9 @@ +# The user of the role MUST define the version they want to use. If +# it's not what the version of unix_users being used actually +# provides, the role will fail. +unix_users_version: null + + # List of system users to create. Value a list of dicts with keys: # # username -- the username of the new user @@ -7,7 +13,6 @@ # sudo -- yes/no, should user have sudo access? (without password) # ssh_key -- install this as ~/.ssh/id_rsa # ssh_key_pub -- install this as ~/.ssh/id_rsa.pub -# ssh_key_pub -- install this as ~/.ssh/id_rsa.pub # authorized_keys -- install this as ~/.ssh/authorized_keys # password -- encrypted password # @@ -16,19 +21,3 @@ # unix_users: [] - - -# Specify directory where per-user authorized_keys files are stored. -# Each user has their own file in the directory, named after their -# username. You MUST specify this variable. You may put more than one -# key in each user's file. -# -# You MUST create a file for each user in unix_users. An empty file -# will do. -# -# THIS IS NOW DEPRECATED. DO NOT USE. If you leave this empty, the old, -# deprecated way of installing authorized_keys files is skipped. If you -# still use that, then set it in your own vars. But switch to the new -# way asap: set authorized_keys field for the user, see above. - -authkeys_dir: diff --git a/roles/unix_users/subplot.md b/roles/unix_users/subplot.md new file mode 100644 index 0000000..c7929e9 --- /dev/null +++ b/roles/unix_users/subplot.md @@ -0,0 +1,62 @@ +# Role `unix_users` – manage Unix users + +This role creates or updates Unix users. + +## Configuration + +This role makes use of the following variables: + +* `unix_users_version` – MANDATORY: The playbook should set this + to the version of the role it expects to use. + +* `unix_users` – OPTIONAL: A list of Unix accounts to create. + Defaults to the empty list. Each item in the list is a dict with the + following keys: + + * `username` – MANDATORY: the username of the account + * `comment` – OPTIONAL: the real name (or GECOS field) of the + new account + * `shell` – OPTIONAL: the login shell + * `system` – OPTIONAL: boolean, is this a system user? + * `sudo` – OPTIONAL: boolean, should the account have password-less sudo? + * `ssh_key` – OPTIONAL: text of key to install as `~/.ssh/id_rsa` + * `ssh_key_pub` – OPTIONAL: text of key to install as `~/.ssh/id_rsa.pub` + * `authorized_keys` – OPTIONAL: text of contents of + `~/.ssh/authorized_keys` + * `password` – OPTIONAL: encrypted password + * `groups` – OPTIONAL: list of additional groups to which user + should be added + +Create the encrypted password with something like: + +~~~yaml +password: "{{ lookup('pipe', 'pass show foo | mkpasswd --method=sha-512 --stdin') }}" +~~~ + +## Create normal user with unix_users + +~~~scenario +given a host running Debian +then the host has no user foo +when I use role unix_users +and I use variables from foo.yml +and I run the playbook +then the host has user foo +and the user foo on host has encrypted password foopass +and the user foo on host has shell /bin/true +and the user foo on host has authorized_keys containing "ssh-rsa" +and the user foo on host is in group operator +~~~ + +~~~{#foo.yml .file .yaml} +unix_users_version: 2 + +unix_users: +- username: foo + comment: Foo Bar + shell: /bin/true + password: foopass + authorized_keys: | + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKVaQfxzzwpwk763IcPBs308TpYYp6+NTOMvYaj3j3ewz8feYQg3lOlKo/5xaPug2ZywG6v6tpn/p0drovT5YAIPJitP7yJAfEzJe/gO7c9uwx0uIpe6cc8bwRG0XFdUVK0EneB6LpIec+3juj4zitGBm0ffIoLDhJ7J0daTzQN62rZaw/2SjSvgbfnu3a2BYRPz1NGiXdvOCbytVSLlUAR6SxNPrFdh/BJnS4umyDaBL/1j2yaw/WlkfZPn5Ni3USZLRcbHnBUUbo64iwBwJabhdpeh0xLGTqDkaeudUgZjlrRHFyCbwJTPtDzJsPLb5HKGGzdXPHP7Lk6PM2CIOz liw@exolobe1 + groups: [operator] +~~~ diff --git a/roles/unix_users/subplot.py b/roles/unix_users/subplot.py new file mode 100644 index 0000000..05330fd --- /dev/null +++ b/roles/unix_users/subplot.py @@ -0,0 +1,59 @@ +import logging + + +def host_does_not_have_user(ctx, username=None): + assert_ne = globals()["assert_ne"] + qemu = ctx["qemu"] + output, exit = qemu.ssh(["getent", "passwd", username]) + assert_ne(exit, 0) + + +def host_has_user(ctx, username=None): + assert_eq = globals()["assert_eq"] + qemu = ctx["qemu"] + output, exit = qemu.ssh(["getent", "passwd", username]) + assert_eq(exit, 0) + output = output.decode("UTF8") + assert f"{username}:" in output + + +def host_user_has_shell(ctx, username=None, shell=None): + assert_eq = globals()["assert_eq"] + qemu = ctx["qemu"] + output, exit = qemu.ssh(["getent", "passwd", username]) + assert_eq(exit, 0) + for line in output.decode("UTF8").splitlines(): + if line.startswith(f"{username}:"): + logging.debug(f"host_user_has_shell: line={line!r}") + assert line.endswith(f":{shell}") + + +def host_user_has_password(ctx, username=None, password=None): + assert_eq = globals()["assert_eq"] + qemu = ctx["qemu"] + output, exit = qemu.ssh(["sudo", "grep", f"^{username}:", "/etc/shadow"]) + assert_eq(exit, 0) + for line in output.decode("UTF8").splitlines(): + if line.startswith(f"{username}:"): + parts = line.split(":") + assert_eq(parts[1], password) + + +def host_user_has_authorized_keys_containing(ctx, username=None, substring=None): + assert_eq = globals()["assert_eq"] + qemu = ctx["qemu"] + output, exit = qemu.ssh(["sudo", "cat", f"/home/{username}/.ssh/authorized_keys"]) + assert_eq(exit, 0) + output = output.decode("UTF8") + assert substring in output + + +def host_user_is_in_group(ctx, username=None, group=None): + assert_eq = globals()["assert_eq"] + qemu = ctx["qemu"] + output, exit = qemu.ssh(["sudo", "-u", username, "groups"]) + assert_eq(exit, 0) + output = output.decode("UTF8") + groups = output.split() + logging.debug(f"host_user_is_in_group: groups={groups}") + assert group in groups diff --git a/roles/unix_users/subplot.yaml b/roles/unix_users/subplot.yaml new file mode 100644 index 0000000..e495602 --- /dev/null +++ b/roles/unix_users/subplot.yaml @@ -0,0 +1,29 @@ +- then: the host has no user {username} + impl: + python: + function: host_does_not_have_user + +- then: the host has user {username} + impl: + python: + function: host_has_user + +- then: the user {username} on host has encrypted password {password} + impl: + python: + function: host_user_has_password + +- then: the user {username} on host has shell {shell} + impl: + python: + function: host_user_has_shell + +- then: the user {username} on host has authorized_keys containing "{substring}" + impl: + python: + function: host_user_has_authorized_keys_containing + +- then: the user {username} on host is in group {group} + impl: + python: + function: host_user_is_in_group diff --git a/roles/unix_users/tasks/main.yml b/roles/unix_users/tasks/main.yml index 00c49fd..e181054 100644 --- a/roles/unix_users/tasks/main.yml +++ b/roles/unix_users/tasks/main.yml @@ -1,3 +1,8 @@ +- name: "check unix_users_version" + shell: | + [ "{{ unix_users_version }}" = "2" ] || \ + (echo "Unexpected version {{ unix_users_version }}" 1>&2; exit 1) + - name: create system users with_items: "{{ unix_users }}" user: @@ -5,6 +10,13 @@ comment: "{{ item.comment|default('unnamed user') }}" shell: "{{ item.shell|default('/bin/bash') }}" system: "{{ item.system|default('no') }}" + +- name: add users to additional groups + with_items: "{{ unix_users }}" + when: item.groups is defined + user: + name: "{{ item.username }}" + groups: "{{ item.groups }}" - name: set password for users with_items: "{{ unix_users }}" @@ -43,14 +55,7 @@ group: "{{ item.username }}" mode: 0600 -- name: add keys to authorized_keys (deprecated way) - with_items: "{{ unix_users }}" - when: authkeys_dir != None - authorized_key: - user: "{{ item.username }}" - key: "{{ lookup('file', authkeys_dir + '/' + item.username) }}" - -- name: add keys to authorized_keys (new way) +- name: add keys to authorized_keys with_items: "{{ unix_users }}" when: item.authorized_keys is defined authorized_key: |