Skip to content

Instantly share code, notes, and snippets.

@QNimbus
Last active March 8, 2021 09:14
Show Gist options
  • Save QNimbus/30671b33abde3a1b81b8b9b117062d5f to your computer and use it in GitHub Desktop.
Save QNimbus/30671b33abde3a1b81b8b9b117062d5f to your computer and use it in GitHub Desktop.
Ansible playbook to run if provisioning hosts that enforce the user to enter a new password (due to expired password after initial setup/installation e.g. Ubuntu 20.04 LTS Server ARM64 for Raspberry Pi). This play will attempt to login to the hosts and update the password and thereby resetting the password expiry.
---
- hosts: all:!localhost
gather_facts: no
# This play attempts to establish an SSH connection with inventory hosts
# and when it encounters an expired password it will change and reset the password
# so other playbooks and roles can normally execute without failing because of expired password.
# Tested with:
# - Ubuntu
# - RedHat
# - Fedora
# - Centos
# Multiple possible results for command (ssh) task:
# - Successful password login (rc == 0)
# - Successful public key login (rc == 0)
# - Successful login with password, password expired (rc == 1, stderr: WARNING: Your password has expired.)
# - Successful login with pubkey, password expired (rc == 1, stderr: WARNING: Your password has expired.)
# - No password auth possible (rc == 255, stderr: Permission denied (publickey).)
# - No pubkey auth possible (rc == 255, std_err: Permission denied (password).)
# - Wrong password (rc == 5, std_err: Permission denied, please try again.)
vars:
os_dependencies:
- ssh
- sshpass
original_host: '{{ ansible_host }}'
ssh_options:
- '-T'
- '-o StrictHostKeyChecking=no'
- '-o UserKnownHostsFile=/dev/null'
ssh_options_tty:
- '-tt'
- '-o StrictHostKeyChecking=no'
- '-o UserKnownHostsFile=/dev/null'
tasks:
- name: Ensure OS dependencies are installed.
connection: local
shell: "which {{ os_dependencies | join(' ') }}"
changed_when: false
failed_when: false
ignore_errors: true
register: os_dependencies_check
- name: Fail execution if any of the needed OS tools is missing.
fail:
msg: "One or more of the: {{ os_dependencies | join(', ') }} are missing on this machine. Please install them - using your distribution's recommended method - before continuing."
when: os_dependencies_check.rc != 0
- name: Test ssh connection.
connection: local
command: sshpass -p {{ ansible_password }} -- ssh {{ ssh_options | join(' ') }} '{{ ansible_user }}@{{ original_host }}'
no_log: yes
failed_when: false
changed_when: false
register: result
- set_fact:
successful_login: '{{ result.rc == 0 }}'
expired_password: '{{ result.rc == 1 and result.stderr is search("warning: your password has expired", ignorecase=True) }}'
wrong_password: '{{ result.rc == 5 and result.stderr is search("permission denied, please try again", ignorecase=True) }}'
no_valid_auth: '{{ result.rc == 255 and result.stderr is search("permission denied \([^)]+\)", ignorecase=True) }}'
- name: Check reason for possible failed ssh connection.
block:
- fail:
msg: Password authentication for remote host {{ ansible_host }} failed due to wrong password.
when: wrong_password == true
- fail:
msg: No valid authentication method for remote host {{ ansible_host }} available.
when: no_valid_auth == true
- fail:
msg: Unable to login using ssh.
when: expired_password != true
when: successful_login != true
- block:
- name: Generate temporary password.
set_fact:
temp_passwd: "{{ lookup('password', '/dev/null length=14 chars=ascii_letters,digits') }}"
- name: Set expired password to temp password.
no_log: yes
connection: local
expect:
command: sshpass -p {{ ansible_password }} -- ssh {{ ssh_options_tty | join(' ') }} '{{ ansible_user }}@{{ original_host }}'
responses:
'(?i)\(current\)|current (UNIX\s)?password': '{{ ansible_password }}'
'(?i)new (UNIX\s)?password':
- '{{ temp_passwd }}'
- '{{ temp_passwd }}'
failed_when: "result.rc != 0 and 'password updated successfully' not in result.stdout"
register: result
- name: Ensure {{ ansible_user }} has sudo privileges.
no_log: yes
connection: local
command: sshpass -p {{ temp_passwd }} -- ssh {{ ssh_options_tty | join(' ') }} '{{ ansible_user }}@{{ original_host }}' "echo {{ temp_passwd }} | sudo -S id > /dev/null 2>&1 || exit 1"
changed_when: false
failed_when: false
register: result
- fail:
msg: User {{ ansible_user }} does not have sudo privileges. Password for {{ ansible_user }}@{{ original_host }} set to '{{ temp_passwd }}'
when: result.rc != 0
- name: Reset expired password to original password.
no_log: yes
connection: local
expect:
command: sshpass -p {{ temp_passwd }} -- ssh {{ ssh_options_tty | join(' ') }} '{{ ansible_user }}@{{ original_host }}' sudo passwd {{ ansible_user }}"
responses:
'(?i)\[sudo\] password for':
- '{{ temp_passwd }}'
'(?i)new (UNIX\s)?password':
- '{{ ansible_password }}'
- '{{ ansible_password }}'
failed_when: "result.rc != 0 and 'password updated successfully' not in result.stdout"
register: result
when: expired_password == true
@tfuzeau
Copy link

tfuzeau commented Mar 8, 2021

Thank for that!
I've replaced the connection: local with delegate_to: localhost as it wasn't taking the proper python instance (and not the virtualenv running ansible) but it is working perfectly!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment