Managing users accounts with Ansible

The Plan

This guide builds on the minimum version of a the playbook for Ansible to create user accounts and setup ssh keys outlined in the first part of this tutorial

Now we are going to look at improving that example to a more Viable version with the following steps:

  • defining expanding the vars to have multiple properties per item to add groups per user.
  • using user state for a method to disable users accounts.
  • Update the servers SSH config, and add a notify rule so sshd is restarted if required.

Adding multiple properties to an Ansible var

Our user var in the first example we used was just a simple list of usernames, but we probably want a bit more control of how each user is setup and so, we can expand on this.

Instead of just adding a simple string, we add each additional parameter at the same indentation and then when looped over using with_items the properties become available such as "{{ item.username }}"

vars with complex data

4
5
6
7
8
9
10
11
  vars:
    users:
    - username: "paul"
      groups: "admin,www-data"
    - username: "tanya"
      groups: "www-data"
    - username: "ruby"
      groups: "www-data"
13
14
15
16
17
  - name: "Create user accounts"
    user:
      name: "{{ item.username }}"
      groups: "{{ item.groups }}"
    with_items: "{{ users }}"
18
19
20
21
22
  - name: "Add authorized keys"
    authorized_key:
      user: "{{ item.username }}"
      key: "{{ lookup('file', 'files/'+ item.username + '.key.pub') }}"
    with_items: "{{ users }}"

users.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
---
- hosts: "localhost"
  connection: "local"
  vars:
    users:
    - username: "paul"
      groups: "admin,www-data"
    - username: "tanya"
      groups: "www-data"
    - username: "ruby"
      groups: "www-data"
  tasks:
  - name: "Create user accounts"
    user:
      name: "{{ item.username }}"
      groups: "{{ item.groups }}"
    with_items: "{{ users }}"
  - name: "Add authorized keys"
    authorized_key:
      user: "{{ item.username }}"
      key: "{{ lookup('file', 'files/'+ item.username + '.key.pub') }}"
    with_items: "{{ users }}"
  - name: "Allow admin users to sudo without a password"
    lineinfile:
      dest: "/etc/sudoers" # path: in version 2.3
      state: "present"
      regexp: "^%admin"
      line: "%admin ALL=(ALL) NOPASSWD: ALL"
...

Using user state to remove user

Now we are adding users to the server using an automated script, we will at some point need a method to remove them.

When we’ve used user so far, we’ve relied on the default state:"present". You could add an other property to the current users list, but I think to make it clear at a glance which are active users and which are disabled users, we going to create a new var as a simple list of old users.

We will also update the existing user task to explicitly define state as present. Even though it’s the default, when it’s next to a similar task it helps make it really clear what happens in each task.

Remove users with user state

12
13
14
  vars:
    remove_users:
    - ruby
15
16
17
18
19
20
21
22
23
24
25
  - name: "Create user accounts"
    user:
      name: "{{ item.username }}"
      groups: "{{ item.groups }}"
      state: "present"    with_items: "{{ users }}"
  - name: "Remove old user accounts"
    user:
      name: "{{ item }}"
      state: "absent"
    with_items: "{{ remove_users }}"

users.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
---
- hosts: "localhost"
  connection: "local"
  vars:
    users:
    - username: "paul"
      groups: "admin,www-data"
    - username: "tanya"
      groups: "www-data"
    remove_users:
    - "ruby"
  tasks:
  - name: "Create user accounts"
    user:
      name: "{{ item.username }}"
      groups: "{{ item.groups }}"
      state: "present"
    with_items: "{{ users }}"
  - name: "Remove old user accounts in remove_users"
    user:
      name: "{{ item }}"
      state: "absent"
    with_items: "{{ remove_users }}"
  - name: "Add authorized keys"
    authorized_key:
      user: "{{ item.username }}"
      key: "{{ lookup('file', 'files/'+ item.username + '.key.pub') }}"
    with_items: "{{ users }}"
  - name: "Allow admin users to sudo without a password"
    lineinfile:
      dest: "/etc/sudoers" # path: in version 2.3
      state: "present"
      regexp: "^%admin"
      line: "%admin ALL=(ALL) NOPASSWD: ALL"
...

Update sshd config to disable root login and restart ssh service

Now we are managing adding and removing users to the server we can improve security by disabling root logins via ssh. Using lineinfile we will update /etc/ssh/config

Handlers have two important traits: They at the end of the playbook only run if notified, and they run only once no matter how many times they are notified.

handlers and notify

notify matches name

12
13
14
15
16
  handlers:
  - name: "Restart sshd"    service:
      name: "sshd"
      state: "restarted"
20
21
22
23
24
25
  - name: "Disable root login via SSH"
    lineinfile:
      dest: "/etc/ssh/sshd_config"
      regexp: "^PermitRootLogin"
      line: "PermitRootLogin no"
    notify: "Restart sshd"

users.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
---
- hosts: "localhost"
  connection: "local"
  vars:
    users:
    - username: "paul"
      groups: "admin,www-data"
    - username: "tanya"
      groups: "www-data"
    remove_users:
    - "ruby"
  handlers:
  - name: "Restart sshd"
    service:
      name: "sshd"
      state: "restarted"
  tasks:
  - name: "Create user accounts"
    user:
      name: "{{ item.username }}"
      groups: "{{ item.groups }}"
      state: "present"
    with_items: "{{ users }}"
  - name: "Remove old user accounts in remove_users"
    user:
      name: "{{ item }}"
      state: "absent"
    with_items: "{{ remove_users }}"
  - name: "Add authorized keys"
    authorized_key:
      user: "{{ item.username }}"
      key: "{{ lookup('file', 'files/'+ item.username + '.key.pub') }}"
    with_items: "{{ users }}"
  - name: "Allow admin users to sudo without a password"
    lineinfile:
      dest: "/etc/sudoers" # path: in version 2.3
      state: "present"
      regexp: "^%admin"
      line: "%admin ALL=(ALL) NOPASSWD: ALL"
  - name: "Disable root login via SSH"
    lineinfile:
      dest: "/etc/ssh/sshd_config"
      regexp: "^PermitRootLogin"
      line: "PermitRootLogin no"
    notify: "Restart sshd"
...

Leave a Reply