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:

  1. Given a list, all the hosts must have their public key added
  2. 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": ""
        }
    ]
}