Updating passwords with Ansible

I’ve recently migrated from KeePassXC to Bitwarden_RS (which I highly recommend, by the way) to manage my passwords.

I figured it was an opportunity to update passwords I hadn’t changed in… years. My Linux users’ passwords were among those.

Instead of updating them manually on each machine, I thought there might be a way to do so with Ansible, and since it turns out there is, I thought I might as well share it here!

Please be careful when it comes to password modification automation. You might end up locking yourself out of your servers.

Generating password hashes

First thing first, we’ll need to generate passwd compatible password hashes for our users.

In this example, I’ll generate a hash for the P@ssw0rd password :

mkpasswd -m sha-512

You might notice you’ll get a different result if you run the same command again :

mkpasswd -m sha-512

That’s because, unless you specify it, the salt used by mkpasswd to encrypt the password is random.

You can choose a salt with the -S parameter if you want :

mkpasswd -m sha-512 -S hmmmsalt

mkpasswd -m sha-512 -S hmmmsalt
captainark@heimdall ~ %

Ansible vault

Passwords are secrets. Even if there are hashed, you don’t want them to be publicly accessible.

If, like me, your Ansible configuration is in a git repository, you should not commit them in an unencrypted file.

Ansible offers a way to encrypt a file containing variables with the ansible-vault command.

We first have to create a vault :

cd /your/ansible/project
mkdir -p group_vars/all/
ansible-vault create group_vars/all/vault.yml
New Vault password:
Confirm New Vault password:

As you can see, the command will ask you to provide a password for the vault. It will then open the file in your $EDITOR (that’ll be vim if you’re a cool kid).

The file is like any Ansible vault file. I’ll create a variable for the root and captainark users in my case :

vault_captainarkpwd: "$6$hmmmsalt$RwZR2r9W5cSv5bVgeSFPX0rJiovWOD5kMDFey1xPR6JtasqQZqHTiuW5JoQ.0VCW6oNHJlgOYJ.auhl82gfX8/"
vault_rootpwd: "$6$hmmmsalt$RwZR2r9W5cSv5bVgeSFPX0rJiovWOD5kMDFey1xPR6JtasqQZqHTiuW5JoQ.0VCW6oNHJlgOYJ.auhl82gfX8/"

An Ansible best practice is to prepend all variables that are stored in a vault with the vault_ prefix. It makes where they are stored clear when reading through a playbook.

Once saved, if you try to cat the file, you won’t be able to see its actual content :

cat group_vars/all/vault.yml

To view the variables in the file, you can use the ansible-vault view command :

ansible-vault view group_vars/all/vault.yml
Vault password:
vault_captainarkpwd: "$6$hmmmsalt$RwZR2r9W5cSv5bVgeSFPX0rJiovWOD5kMDFey1xPR6JtasqQZqHTiuW5JoQ.0VCW6oNHJlgOYJ.auhl82gfX8/"
vault_rootpwd: "$6$hmmmsalt$RwZR2r9W5cSv5bVgeSFPX0rJiovWOD5kMDFey1xPR6JtasqQZqHTiuW5JoQ.0VCW6oNHJlgOYJ.auhl82gfX8/"

To edit the vault’s content, you can use the ansible-vault edit group_vars/all/vault.yml command.

The playbook

Now that our vault is ready, all that’s left is to run the following playbook :

- hosts: all
  become: yes
  become_method: sudo

  - name: PASSWORDS | Check if the captainark user exists
    shell: id -u captainark
    register: captainark_exists
    ignore_errors: true

  - name: PASSWORDS | Update captainark password
      name: captainark
      password: "{{ vault_captainarkpwd }}"
      update_password: always
    when: captainark_exists.rc == 0

  - name: PASSWORDS | Create captainark user
      name: captainark
      password: "{{ vault_captainarkpwd }}"
      shell: /usr/bin/zsh
      uid: 1000
      groups: adm,sudo,apps
    when: captainark_exists.rc != 0

  - name: PASSWORDS | Update root password
      name: root
      password: "{{ vault_rootpwd }}"
      update_password: always

I’ve called this playbook passwords.yml. To run it, I simply execute the following command :

ansible-playbook passwords.yml --ask-vault-pass

The command asks for the vault password to decrypt its contents.

The playbook will first check if the captainark user exists, and it will update its password if it does. If it doesn’t, the user will be created with the defined options.

Since the root user should always exist, the playbook changes its password without checking.

N.B. : If you’ve decided to store your vault somewhere else, you might need to add a task at the beginning of the playbook to load it, like so :

  - name: PASSWORDS | Load the vault
    include_vars: /path/to/your/vault.yml


That’s all! As always, I hope someone finds this article useful!

If you do, please let me know in the comments here, on Twitter or on the Fediverse!

Also, if you have any questions, feel free to hit me up on my Rocket.Chat instance! Hopefully I’ll write about it in the future!