Ansible: dynamic usernames
Let me start with the small quiz: What username Ansible uses to access remote targets? What if you need to change it? Is it possible to make it dynamic? If you know the answers to all questions, please hold my beer. I'm going to tell this story anyway.
In most cases, Ansible uses OS username to authenticate against targets. When you need to run commands with a different username, you have a few options. Let's talk them through.
Privilege escalation: This method works if you have sudo privilege on target hosts. By default, Ansible uses root as the target user, but the become_user variable alters this behavior. If you put ansible_become_user in the inventory, Ansible picks it up automatically. If you have privileged access to the targets, it's the best option because you define a username for a single task. There are two issues: you should be creative to alter username for more than one task, or for the paly and it doesn't work if you have limited sudo privilege.
Set SSH user name: An alternative to the become_user approach. To use it, distribute public key SSH from your account on Ansible controller on target devices, and you can directly connect to the target host using your name of choice.
## Equvivalent of ssh target-host-id
$ ansible target-host -a id
## Equvivalent of ssh oracle@target-host-id
$ ansible target-host -u oracle -a id
I use this method most of the cases due to limited sudo access in my current environment. Almost all automation books start with the play header similar to the one below.
---
- name: Some Oracle-related Play
remote_user: oracle
hosts: target-host
It's a straightforward, reliable method, but it comes with the price tags:
- You should prepare all target hosts
- You can't change username from command to command. It works for plays only.
- Options to dynamically username definitions are limited.
Yet, there is a way to calculate username during the execution. Let me give you an example.
Dynamic remote user name. The vast majority of targets in my environment share the same user names and groups. And I need no worry about the connection user, except for the small set of highly isolated machines where you have a totally different set of user names. For the sake of illustration, let say that my primary account is ofmwadmin, and for the secured targets, it's priv_ofmwadmin.
For the small installations or for a simple project, I would put this name straight into the inventory like this one:
app_servers:
hosts:
host1.apps.domain.com:
host2.apps.domain.com:
secured_servers:
vars:
ansible_user: priv_ofmwadmin
hosts:
host1.secure.domain.com:
host2.secure.domain.com:
Now Ansible will switch username for the secured_servers group, but it's not dynamic enough because it will use the same user name for everyone who runs this project.
We maintain shared Ansible controllers with common plays and inventories, so the ansible_user value should be set to something similar to "priv_<current username>".
The ideal version would look like
secured_servers:
vars:
ansible_user: "priv_{{ ansible_user }}"
hosts:
host1.secure.domain.com:
host2.secure.domain.com:
Most languages will handle such assignments, but Ansible fails the endless loop at the first access to the server. It means we need another source for the current username. Fortunately, there is a way, and it may be quite handy for similar situations. There is my working version with the lookup module:
secured_servers:
vars:
ansible_user: "priv_{{ lookup('env','USER') }}"
hosts:
this function gets my current user name from the controller environment and uses it to calculate user name for the secured group at runtime.
This function gets the username from the controller environment and uses it to calculate a final result for the secured group at runtime.