If you are not familiar with modules, check out Ansible - Getting Started with Modules.
known_hosts can be used to append an SSH servers SSH key to a clients known hosts file. In this example, an SSH key is appended to /home/john.doe/.ssh/known_hosts2.
---
- hosts: all
tasks:
- name: append server1.example.com SSH key to John Doe's known_hosts file
ansible.builtin.known_hosts:
path: /home/john.doe/.ssh/known_hosts2
name: server1.example.com <- must be an exact match of the key DNS name or IP
key: "server1.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNpFc+HimGAZWJcAgRx6P8ycxh2JRHBeaTfVzu/HncxP0nR
delegate_to: localhost
...
known_hosts can also be used to remove one or more SSH key from a known_hosts file.
---
- hosts: all
tasks:
- name: remove any SSH name containing name server1 from John Doe's known_hosts file
ansible.builtin.known_hosts:
path: /home/john.doe/.ssh/known_hosts2
name: server1 <- must be an exact match of the key DNS name or IP
state: absent
delegate_to: localhost
...
Example scenario
Let's say you have a playbook that contains the following.
---
- hosts: all
tasks:
- ansible.builtin.stat:
path: /tmp/example.txt
...
Here is one way to run this playbook against server2.example.com. This will make an SSH connection from to server2.example.com.
[john.doe@server1.example.com]# ansible-playbook foo.yml -i server2.example.com,
In this example, if the SSH key of server2.example.com is not listed in the /etc/ssh/ssh_known_hosts or /home/username/.ssh/known_hosts file on the control node (server1.example.com), a prompt will appear stating The authenticity of host 'hostname (ip address)' can't be established.
The authenticity of host 'server2.example.com (10.14.157.95)' can't be established
DSA key fingerprint is BB37 83F2 5E3A 7A4C 6C84 F047 D97B DD4E 38BB 2082
Are you sure you want to continue connecting (yes/no)?
Typing yes and pressing enter will append the SSH key of server2.example.com to the known_hosts file on the control node (server1.example.com). However, what if you are connecting to 100 target systems? Do you really want to type yes and press enter 100 times? Or, what if you have some sort of silent process that runs the playbooks. It would just hang indefinitely.
Solution
Here is how I handle this. I start my playbooks with a section that is run against localhost.
gather_facts must be set to false because the gathering of facts makes an SSH connection.
I then invoke a role as a pre_task to take care of the known hosts.
---
- hosts: localhost
gather_facts: false
serial: 1 # see https://github.com/ansible/ansible-modules-extras/issues/2489
pre_tasks:
- name: include_role 'knownHosts'
ansible.builtin.include_role:
name: knownHosts
- hosts: all
tasks:
- ansible.builtin.stat:
path: /tmp/example.txt
...
Known Hosts Role
- I use the file module to create the users known_hosts file if it does not exist.
- lineinfile is used to remove any lines in the users known_hosts file that contain the hostname of the target server.
- command or shell are used to invoke the ssh-keyscan command to get the SSH key.
- debug is used to display the SSH key
- known_hosts is used to append the SSH key to the users known_hosts file
- name: set_fact known_hosts_list
set_fact:
known_hosts_list: ['known_hosts', 'known_hosts2']
- name: create the known_hosts and known_hosts2 files if they do not exist
ansible.builtin.file:
path: "{{ lookup('env', 'HOME') }}/.ssh/{{ item }}"
owner: "{{ lookup('env', 'USER') }}"
mode: "0644"
state: touch
delegate_to: localhost
with_items: "{{ known_hosts_list }}"
run_once: true
- name: use nslookup to check for 'NXDOMAIN' or 'No answer'
ansible.builtin.shell: nslookup {{ inventory_hostname_short }}
register: answer
delegate_to: localhost
failed_when: answer.rc not in [ 0, 1 ]
- name: print warning if 'NXDOMAIN' or 'No answer' is returned
ansible.builtin.debug:
msg: the 'nslookup {{ inventory_hostname_short }}' command returned 'NXDOMAIN' or 'No answer' - the subsequent tasks will be skipped
when: answer.rc == 1 or answer.stdout is search 'No answer'
- block:
- name: use nslookup to get the IP address of the target server
ansible.builtin.shell: "nslookup {{ inventory_hostname_short }} | grep ^Address | tail -1 | sed 's|Address:
||'"
register: nslookup
delegate_to: localhost
- name: set fact IP address
ansible.builtin.set_fact:
ipaddress: "{{ nslookup.stdout }}"
- name: Remove SSH keys from known_hosts / known_hosts2
ansible.builtin.known_hosts:
path: "{{ lookup('env', 'HOME') }}/.ssh/{{ item.key }}"
name: "{{ item.value }}"
state: absent
delegate_to: localhost
with_items:
- { key: 'known_hosts', value: "{{ inventory_hostname | upper }}" }
- { key: 'known_hosts', value: "{{ inventory_hostname | lower }}" }
- { key: 'known_hosts2', value: "{{ inventory_hostname | upper }}" }
- { key: 'known_hosts2', value: "{{ inventory_hostname | lower }}" }
- { key: 'known_hosts', value: "{{ inventory_hostname_short | upper }}" }
- { key: 'known_hosts', value: "{{ inventory_hostname_short | lower }}" }
- { key: 'known_hosts2', value: "{{ inventory_hostname_short | upper }}" }
- { key: 'known_hosts2', value: "{{ inventory_hostname_short | lower }}" }
- { key: 'known_hosts', value: "{{ ipaddress }}" }
- { key: 'known_hosts2', value: "{{ ipaddress }}" }
- name: ssh-keyscan for SSH servers SSH key
connection: local
ansible.builtin.shell: ssh-keyscan {{ inventory_hostname }},{{ ipaddress }} 2>/dev/null | head -1
register: keyscan_key
- name: display the contents of the 'keyscan_key' variable
ansible.builtin.debug:
var: keyscan_key.stdout
- name: create the tmplist list
ansible.builtin.set_fact:
tmplist : "{{ ssh_key.stdout.split() }}"
- name: create the sshkey_for_known_hosts2 variable
ansible.builtin.set_fact:
sshkey_for_known_hosts2: "{{ inventory_hostname_short | upper }},{{ ipaddress }} {{ tmplist [1] }} {{ tmplist [2] }}"
- name: append SSH key to known_hosts / known_hosts2
ansible.builtin.known_hosts:
path: ""{{ lookup('env', 'HOME') }}/.ssh/{{ item.path }}"
name: "{{ ipaddress }}"
key: "{{ item.key }}"
delegate_to: localhost
with_items:
- { path: 'known_hosts', key: "{{ ssh_key.stdout }}" }
- { path: 'known_hosts2', key: "{{ sshkey_for_known_hosts2 }}" }
when: answer.rc == 0 and answer.stdout is not search 'No answer'
Did you find this article helpful?
If so, consider buying me a coffee over at