Managing ssh known hosts with ansible
Today I was looking how you can populate an ssh known_hosts
file with
ansible. For ssh access (pulling git repositories) I need to accept a
fingerprint and I do not want to do this manually for every target. Therefore,
I want to populate the known_hosts file so ssh can connect to them without
interaction. And in fact, it was quite easy to achieve.
The requirements to add the public key to the file were simply stated:
- Given a list, all the hosts must have their public key added
- When the host is already listed, the public key should not be added twice
The second requirement is added because otherwise you will append the public key of a host every time you invoke the ansible task.
First, I am using ssh-keygen to check if the host is available in the given host file. If it is listed, it will print its line to stdout. If not, the stdout is empty. This result can be used as a condition to execute ssh-keyscan to fetch the public key of the given host.
The ansible tasks look like the following:
- name: Make sure the known hosts file exists
file: "path={{ ssh_known_hosts_file }} state=touch"
- name: Check host name availability
shell: "ssh-keygen -f {{ ssh_known_hosts_file }} -F {{ item }}"
with_items: ssh_known_hosts
register: ssh_known_host_results
ignore_errors: yes
- name: Scan the public key
shell: "{{ ssh_known_hosts_command}} {{ item.item }} >> {{ ssh_known_hosts_file }}"
with_items: "{{ ssh_known_host_results.results }}"
when: item.stdout == ""
I extracted some variables to make it easy to add hosts, set the host file or modify the command to scan the public key. To make this work, you can for example use the following set of variables:
ssh_known_hosts_command: "ssh-keyscan -H -T 10"
ssh_known_hosts_file: "/etc/ssh/ssh_known_hosts"
ssh_known_hosts:
- foo.example.com
- bar.example.org
How does this work?
The loop in the second task (ssh-keygen) stores the result in a variable
called ssh_known_host_results
. This variable has a property results
which
is a list for all looped items from the list in ssh_known_hosts
.
In the third task I do not loop through all the hosts again, but through the
results stored from the ssh-keygen task. Subsequently, I have access to the
item used before and its output. I check the output of the item
(item.stdout
). If that is empty (i.e. the host is not listed in the known
host file yet), then I use it for the ssh-keyscan.
The item used in the particular entry of the results list is stored in the
property item
. Therefore it’s the item of the current item
, causing it to
be named item.item
.
As an example, this is the output of ssh_known_host_results
when I scan for both github.com, bitbucket.org (both already in my known host file) and unknown.example.com:
"ssh_known_host_results": {
"changed": true,
"msg": "All items completed",
"results": [
{
"changed": true,
"cmd": "ssh-keygen -f /etc/ssh/ssh_known_hosts -F github.com",
"delta": "0:00:00.003027",
"end": "2014-09-23 16:50:03.538573",
"invocation": {
"module_args": "ssh-keygen -f /etc/ssh/ssh_known_hosts -F github.com",
"module_name": "shell"
},
"item": "github.com",
"rc": 0,
"start": "2014-09-23 16:50:03.535546",
"stderr": "",
"stdout": "# Host github.com found: line 1 type RSA\n|1|Lu9ztjU5HQhMRnJ79MIcLTWMmVk=|gpSF2uOu4H/gbg1nkmgjDsGY+jU= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=="
},
{
"changed": true,
"cmd": "ssh-keygen -f /etc/ssh/ssh_known_hosts -F bitbucket.org",
"delta": "0:00:00.001750",
"end": "2014-09-23 16:50:03.635427",
"invocation": {
"module_args": "ssh-keygen -f /etc/ssh/ssh_known_hosts -F bitbucket.org",
"module_name": "shell"
},
"item": "bitbucket.org",
"rc": 0,
"start": "2014-09-23 16:50:03.633677",
"stderr": "",
"stdout": "# Host bitbucket.org found: line 2 type RSA\n|1|S2lhU/ZG0zDyi5havHjSOkIaqhQ=|3Pa2amMQYOJF/rfBA+NfZIYWJF8= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw=="
},
{
"changed": true,
"cmd": "ssh-keygen -f /etc/ssh/ssh_known_hosts -F unknown.example.org",
"delta": "0:00:00.003206",
"end": "2014-09-23 17:02:37.472696",
"invocation": {
"module_args": "ssh-keygen -f /etc/ssh/ssh_known_hosts -F unknown.example.org",
"module_name": "shell"
},
"item": "unknown.example.org",
"rc": 0,
"start": "2014-09-23 17:02:37.469490",
"stderr": "",
"stdout": ""
}
]
}