How to handle SSH port changes with Ansible?
Solution 1:
You could try a local_action on the hosts to see if you can connect to the respective ports and register the one that succeeds and set that as a fact. You want to turn off gather facts because otherwise the setup module will fail when it tries to connect with the hosts which have already been reconfigured. Once you've done this play just add others below with gather_facts and all the rest.
- name: determine ssh port
hosts: all
gather_facts: false
vars:
custom_ssh_port: 222
tasks:
- name: test default ssh port
local_action: wait_for port=22 timeout=5 host={{inventory_hostname}}
register: default_ssh
ignore_errors: true
- name: set ansible_ssh_port to default
set_fact: ansible_ssh_port=22
when: default_ssh.elapsed < 5
- name: test ssh on high port
local_action: wait_for port={{custom_ssh_port}} timeout=5 host={{inventory_hostname}}
register: high_ssh
when: default_ssh.elapsed >= 5
ignore_errors: true
- name: set ansible_ssh_port high
set_fact: ansible_ssh_port={{custom_ssh_port}}
when: default_ssh.elapsed >= 5 and high_ssh.elapsed < 5
It was pointed out to me that this will blow out the time for playbooks where you use this. You could also set ansible_ssh_port in the vars section of plays that should only be run on hosts with reconfigured ssh port. e.g.
- name: change ssh ports
tasks:
- name: edit sshd_config
lineinfile ..
notify: restart ssh
handlers:
- name: restart ssh
service: sshd state=restarted
- name: continue setup
vars:
- ansible_ssh_port : 5422
tasks:
...
Solution 2:
@RichardSalts thanks for getting me started with this. I used nc to check ports which should be a lot faster. This is my bootstrap.xml:
Tested using ansible 1.5 (devel 3b8fd62ff9) last updated 2014/01/28 20:26:03
---
# Be sure to set the following variables for all hosts:
# vars:
# oldsshport: 22
# sshport: 555
# Might fail without setting remote_tmp = /tmp/ansible/$USER in your ansible.cfg. Also fix for directly below.
# Once host is setup most of the checks are skipped and works very quickly.
# Also, be sure to set non-standard shells in a different playbook later. Stick with /bin/bash until you can run apt install.
# Assumes root user has sshkey setup already. Not sure how to utilize the --ask-pass option. For now, use ssh-copy-id prior to running playbook on new host for root user (if needed).
# Test new ssh port
- name: ssh test nc {{ sshport }}
local_action: shell nc -z -w5 {{ inventory_hostname }} {{ sshport }}
register: nc_ssh_port
failed_when: nc_ssh_port.stdout.find('failed') != -1
changed_when: nc_ssh_port.stdout == ""
ignore_errors: yes
# Set port to new port if connection success
- name: set ansible_ssh_port
set_fact: ansible_ssh_port={{ sshport }}
when: nc_ssh_port|success
# Fail back to old port if new ssh port fails
- name: ssh test nc port {{ oldsshport }}
local_action: shell nc -z -w5 {{ inventory_hostname }} {{ oldsshport }}
register: nc_ssh_default
changed_when: nc_ssh_default.stdout == ""
ignore_errors: yes
when: nc_ssh_port|changed
# Set ansible to old port since new failed
- name: set ansible_ssh_port to {{ oldsshport }}
set_fact: ansible_ssh_port={{ oldsshport }}
when: nc_ssh_default|success and nc_ssh_port|changed
# Check if root user can ssh
- name: find user
local_action: shell ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5 -p {{ ansible_ssh_port }} root@{{ inventory_hostname }} exit
register: ssh_as_root
failed_when: ssh_as_root.stdout.find('failed') != -1
changed_when: ssh_as_root.stderr.find('Permission denied') == -1
# If root user success, set this up to change later
- name: first user
set_fact: first_user={{ ansible_ssh_user }}
when: ssh_as_root|changed
# Set ssh user to root
- name: root user
set_fact: ansible_ssh_user=root
when: ssh_as_root|changed
# ANSIBLE FIX: /tmp/ansible isn't world-writable for setting remote_tmp = /tmp/ansible/$USER in ansible.cfg
- name: /tmp/ansible/ directory exists with 0777 permission
file: path=/tmp/ansible/ owner=root group=root mode=0777 recurse=no state=directory
changed_when: False
sudo: yes
# Setup user accounts
- include: users.yml
# Set ssh user back to default user (that was setup in users.yml)
- name: ansible_ssh_user back to default
set_fact: ansible_ssh_user={{ first_user }}
when: ssh_as_root|changed
# Reconfigure ssh with new port (also disables non-ssh key logins and disable root logins)
- name: sshd.conf
template: src=sshd_config.j2 dest=/etc/ssh/sshd_config owner=root group=root mode=0644
register: sshd_config
sudo: yes
# Force changes immediately to ssh
- name: restart ssh
service: name=ssh state=restarted
when: sshd_config|changed
sudo: yes
# Use updated ssh port
- name: set ansible_ssh_port
set_fact: ansible_ssh_port={{ sshport }}
when: nc_ssh_port|changed
Solution 3:
Since you probably deploy your ssh config early, you really should keep this simple. Just configure your inventory with the target ansible_ssh_port
and use -e
when deploying your ssh configuration for the first time:
ansible-playbook bootstrap_ssh.yml -e 'ansible_ssh_port=22'
Note that ansible_ssh_port
is deprecated in 2.0 (superseded by ansible_port
)
Solution 4:
Is it possible to automate this by having Ansible fallback to a specified port if the connection could not be established to the default SSH port?
I also needed similar functionality, so I forked and patched the Ansible ssh plugin hoping that Ansible Inc. would adopt it; they didn't. It tests non-std ssh port specifications to see if they are open and reverts to the default ssh port if not. It's a very small patch, available at https://github.com/crlb/ansible.