Managing user accounts with Ansible
By DevOps on Fri 24 May 2019
inAs part of developing and deploying web applications, we need to be able to manage OS user accounts and control access for developers and systems admins.
To do this, we wrote an Ansible role to manage users. It is basically an opinionated wrapper on the Ansible user module. This post describes how it works.
At a high level, we create an OS account for the app to run under and an account to deploy the app. We may also create accounts for specific users. We then add ssh user keys to control access to these accounts. The ssh keys for users can be stored locally or pulled from GitHub.
User types
We separate users into different types:
- Global system admins / ops team
When we provision a server, we automatically create accounts for our system admin team, independent of the project.
These users have their own accounts on the server with sudo permissions. We add
them to the wheel
or admin
group, then allow them to run sudo without a
password.
- Project admins / power users
These users have the same rights as global admins, but are set up on per-project or per-server basis, controlled with inventory host/group vars. Normally the tech lead for the project would be an admin.
- Deploy account
This user account is used to deploy the application to the server. It owns the application software files and has write permissions to the deploy and config directories.
The app and deploy accounts do not have sudo permissions. We may, however, configure
sudo to allow them to run specific commands to e.g. restart the app by
running systemctl restart foo
. That is handled by the role that installs and
configures the app, not this role.
For example, make a file like /etc/sudoers.d/deploy-foo
:
deploy ALL=(ALL) NOPASSWD: /bin/systemctl start foo, /bin/systemctl stop foo, /bin/systemctl restart foo, /bin/systemctl status foo
Another option is to configure systemd to look for a flag file and restart the app if it changes. See mix_deploy for an example.
- App account
The application runs under this user account.
For better security, we limit what this account can do. It has write access to the directories it needs at runtime, e.g. logs, and has read-only access to its code and config files.
- Developers
Developers may need to access the deploy
account to deploy or the app user
account to look at the logs and debug it. We add the ssh keys for developers to
the accounts, allowing them to log in via ssh.
For systems with sensitive data like health care or financial services, we tightly control access to production servers, but give more access to dev servers.
- Project users
These users have their own OS user accounts like admins, but don't have sudo. An example might be an account for a customer who needs to be able to log in and run queries against the db. You can give them permissions to e.g. access the log files for the app by adding them to the app group and setting file permissions.
Configuration
By default, this role does nothing.
For it to do something, you need to define variables in group vars like
inventory/group_vars/app-servers
, host vars like
inventory/host_vars/web-server
or a vars
section in a playbook.
You can have different settings on a host or group level to e.g. give developers login access in the dev environment but not on prod.
App accounts
users_deploy_user
specifies the account that deploys the app.
Optional, if not specified the deploy user will not be created.
users_deploy_group
specifies the group, defaults to users_deploy_user
.
users_deploy_user: deploy
users_deploy_group: deploy
users_app_user
specifies the account that runs the app.
Optional, if not specified the app user will not be created.
users_app_group
specifies the group, defaults to users_app_user
.
users_app_user: foo
users_app_group: foo
User accounts
users_users
defines OS account names and ssh keys for users. It is simply a list of
users, the accounts are not created until they are referenced.
It is a list of dicts with four fields:
user
: Name of the OS accountname
: User's name. Optional.key
: ssh public key file. Put them in e.g. your playbookfiles
directory. Optional.github
: The user's GitHub id. The role gets the user keys fromhttps://github.com/{{ github }}.keys
. Optional.
Example:
users_users:
- user: jake
name: "Jake Morrison"
github: reachfh
- user: ci
name: "CI server"
key: ci.pub
Lists of users
After you have defined users, you add them to groups for the specific servers,
specifying the id used in the user
key. By default, these are empty, so if
you don't specify users, they will not be created.
Global admin users with a separate OS account and sudo permissions.
users_global_admin_users:
- jake
Project level admin users with a separate OS account and sudo permissions.
users_admin_users:
- fred
Project users with a separate OS account but no sudo permission.
users_regular_users:
- bob
Users (ssh keys) who can access the deploy account.
users_deploy_users:
- ci
Users (ssh keys) who can access the app account.
users_app_users:
- fred
Group configuration
You can specify additional groups which the different types of users will have. By default these lists are empty, but you can use it to fine tune access to the app.
We normally configure ssh so that a user account must must be a member of a
sshusers
group, or ssh will not allow anyone to log in.
Add this to /etc/ssh/sshd_config
AllowGroups sshusers
Then add sshusers
to the users_admin_groups
, e.g.
users_admin_groups:
- sshusers
Admin user groups
Additional groups that admin users should have.
The role will always be added the wheel
or admin
group, depending on the
platform, independent of the OS groups specified here. If there are admin users
defined, then this role sets up sudo with a /etc/sudoers.d/00-admin
file so
that admin users can run sudo without a password.
users_admin_groups:
- sshusers
Regular user groups
Additional groups that regular users should have.
users_regular_groups:
- sshusers
Deploy user groups
Additional groups that the deploy account should have.
users_deploy_groups:
- sshusers
App user groups
Additional groups that the app account should have.
users_app_groups:
- sshusers
Deleting users
This role puts "ansible-" in the comment when it creates users. This allows it to track when users are added or removed from the lists and delete the accounts.
You can also specify accounts in the users_delete_users
list and they will be
deleted. This is useful for cleaning up legacy accounts.
You can control whether to delete the user's home directory when deleting the
account with the users_delete_remove
and users_delete_force
variables.
See the Ansible docs for details.
For safety, these variables are no
by default, but if you are managing the
system users with this role, you probably want to set them to yes
.
users_delete_remove: yes
users_delete_force: yes
The role can optionally remove authorized keys from system users like 'root' or 'centos'. This is useful for security to avoid backup root keys, once you have set up named admin users.
users_remove_system_authorized_keys: true
Setup
This role is normally run as the first thing on a new instance. That creates admin users and sets up their keys so that they can run the other roles which configure the server.
A project specific role is responsible for preparing the server for the app,
e.g. creating directories and installing dependencies. We normally deploy the
app from a build or CI server, without sudo, using the deploy
user account.
Here is a typical playbook:
- name: Manage users
hosts: '*'
vars:
users_app_user: foo
users_app_group: foo
users_deploy_user: deploy
users_deploy_group: deploy
users_users:
- user: jake
name: "Jake Morrison"
github: reachfh
users_app_users:
- jake
users_deploy_users:
- jake
roles:
- { role: cogini.users, become: true }
Add the host to the inventory/hosts
file.
[web-servers]
web-server-01
Add the host to .ssh/config
or a project specific ssh.config
file.
Host web-server-01
HostName 123.45.67.89
On a physical server where we start with a root account and no ssh keys, we need to bootstrap the server the first time, specifying the password with -k.
ansible-playbook -k -u root -v -l web-server-01 playbooks/manage-users.yml --extra-vars "ansible_host=123.45.67.89"
On macOS the -k command requires the askpass
utility, which is not installed
by default, so it falls back to Paramiko, which
doesn't understand .ssh/config
, so we specify ansible_host
manually.
On following runs, after the admin users are set up, use:
ansible-playbook -u $USER -v -l web-server-01 playbooks/manage-users.yml
Deleting legacy users
Define legacy user accounts to delete in the users_delete_users
list, e.g.:
ansible-playbook -u $USER -v -l web-servers playbooks/manage-users.yml --extra-vars "users_delete_users=[fred] users_delete_remove=yes users_delete_force=yes"