Is it possible to change the current user's password in-band with ansible?

Solution 1:

Is there a way to make Ansible try the default password, and keep running the playbook if that fails?

Here is an example, it doesn't exactly match what you mentioned, but might be a starting point.

---
# SYNOPSIS
# try authentication using keys, if that fails, fall back to default credentials

- import_playbook: ../bootstrap.yml

- hosts: linux_systems
  gather_facts: no
  become: yes
  any_errors_fatal: true
  vars: 
    ansible_user_first_run: vagrant
    ansible_pass_first_run: vagrant
  tasks:

  - block:
    - name: Check if connection is possible using keys
      command: ssh -F {{project_dir}}/.ssh/ansible_ssh_config -o User={{ ansible_user }} -o ConnectTimeout=10 -o PreferredAuthentications=publickey -o PubkeyAuthentication=yes {{ ansible_host }} /bin/true
      register: result
      connection: local
      ignore_errors: yes
      changed_when: False

    - name: If using user_first_run
      connection: local
      set_fact:
        using_first_run: true
      when: result is failed

    - name: If no connection, change ansible_user
      connection: local
      set_fact:
        ansible_user: "{{ ansible_user_first_run }}"
      when: result is failed

    - name: If no connection, change ansible_ssh_pass
      connection: local
      set_fact:
        ansible_ssh_pass: "{{ ansible_pass_first_run }}"
      when: result is failed

    - name: If no connection, change ansible_become_pass
      connection: local
      set_fact:
        ansible_become_pass: "{{ ansible_pass_first_run }}"
      when: result is failed

    # since any_errors_fatal this should fail the play
    # if we still cannot reach all hosts.
    - name: Check if connection is possible
      raw: /bin/true
      changed_when: False

    tags:
    - always

  - name: Perform a ansible ping
    ping: {}

Solution 2:

Wow - I'm dumb. I can't believe how simple the solution ended up being.

My playbook looks something like this now:

---
- hosts: RaspberryPis
  become: True
  become_user: root

  tasks:
    - name: Do stuff
      notify:
        - restart some service

  handlers:
    - name: restart some service
      systemd:
        name: some_service
        state: restarted

- hosts: RaspberryPis
  become: True
  become_user: root

  tasks:

    - name: Set pi password
      user:
        name: pi
        password: "{{ 'test'|password_hash('sha512', 'mysalt') }}"

I run it with ansible-playbook --ask-pass. The first run, I give it the default password. The next run, I give it the new password. Basically, it's always the password the device currently has.

Of course for security reasons you'd want the new password to be a variable coming from an ansible vault, or something like that, but that's the idea, and it seems to work.

Having it in a separate play at the end ensures that the handlers from your main play run before the password is changed. This way, changing the user's password really is the last step.

I can't believe I never thought of this before.

This is as simple as I could make it. Is there a way to make Ansible try the default password, and keep running the playbook if that fails? That way, I could have both the default and the new password in my vault, and would only need to supply the vault password - not both the current user password, AND the vault password?