summaryrefslogtreecommitdiff
path: root/roles/unix_users
diff options
context:
space:
mode:
Diffstat (limited to 'roles/unix_users')
-rw-r--r--roles/unix_users/defaults/main.yml23
-rw-r--r--roles/unix_users/subplot.md62
-rw-r--r--roles/unix_users/subplot.py59
-rw-r--r--roles/unix_users/subplot.yaml29
-rw-r--r--roles/unix_users/tasks/main.yml21
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: