diff --git a/playbooks/base/pre.yaml b/playbooks/base/pre.yaml index 320c179df..d0b31210a 100644 --- a/playbooks/base/pre.yaml +++ b/playbooks/base/pre.yaml @@ -1,6 +1,7 @@ - hosts: all roles: - add-build-sshkey + - multi-node-known-hosts - prepare-workspace - role: validate-host # TODO(mordred) When we have site-local variables, these should go there diff --git a/roles/multi-node-known-hosts/library/generate_all_known_hosts.py b/roles/multi-node-known-hosts/library/generate_all_known_hosts.py new file mode 100644 index 000000000..98106925f --- /dev/null +++ b/roles/multi-node-known-hosts/library/generate_all_known_hosts.py @@ -0,0 +1,87 @@ +#!/usr/bin/python + +# Copyright (c) 2017 Red Hat +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + + +def main(): + module = AnsibleModule( + argument_spec=dict( + hostvars=dict(required=True, type='dict'), + hosts=dict(required=False, type='list'), + ) + ) + + hostvars = module.params['hostvars'] + hosts = module.params['hosts'] + if not hosts: + hosts = hostvars.keys() + + known_hosts = set() + + for host in hosts: + this = hostvars[host] + + public_keys = [x for x in this.keys() if + x.startswith('ansible_ssh_host_key')] + + for iface in this.get('ansible_interfaces', []): + iface_key = 'ansible_{}'.format(iface.replace('-', '_')) + ipv4 = this[iface_key].get('ipv4') + if not isinstance(ipv4, list): + ipv4 = [ipv4] + ipv6 = this[iface_key].get('ipv6') + if not isinstance(ipv6, list): + ipv6 = [ipv6] + addresses = [x['address'] for x in ipv4 if x and not + x['address'].startswith('127.')] + addresses += [y['address'] for y in ipv6 if y and not + y['scope'] == 'host'] + addresses += [this['ansible_hostname']] + for addr in addresses: + for key in public_keys: + if key.endswith('rsa_public'): + key_type = 'ssh-rsa' + elif key.endswith('ecdsa_public'): + # XXX This will not work with > 256 bit ecdsa keys + # until https://github.com/ansible/ansible/issues/28325 + # is fixed. We're using the proposed scheme in case it + # does merge as-is, but it may need to be updated if + # the patch is changed as well. + key_type = this.get('{}_type'.format(key), + 'ecdsa-sha2-nistp256') + elif key.endswith('ed25519_public'): + key_type = 'ssh-ed25519' + else: + continue + known_hosts.add( + '{addr} {key_type} {key}'.format(addr=addr, + key_type=key_type, + key=this[key])) + + ret = { + 'ansible_facts': { + 'all_known_hosts': [dict(name=x.split()[0], key=x) for x in + sorted(known_hosts)] + } + } + + module.exit_json(changed=False, _zuul_nolog_return=True, **ret) + +from ansible.module_utils.basic import * # noqa +from ansible.module_utils.basic import AnsibleModule + +if __name__ == '__main__': + main() diff --git a/roles/multi-node-known-hosts/tasks/main.yaml b/roles/multi-node-known-hosts/tasks/main.yaml new file mode 100644 index 000000000..18763df7c --- /dev/null +++ b/roles/multi-node-known-hosts/tasks/main.yaml @@ -0,0 +1,3 @@ +- name: Setup known_hosts for cross-node SSH'ing when we have more than 1 host + include: setup-multinode-known-hosts.yaml + when: hostvars|length > 1 diff --git a/roles/multi-node-known-hosts/tasks/setup-multinode-known-hosts.yaml b/roles/multi-node-known-hosts/tasks/setup-multinode-known-hosts.yaml new file mode 100644 index 000000000..ad41f6230 --- /dev/null +++ b/roles/multi-node-known-hosts/tasks/setup-multinode-known-hosts.yaml @@ -0,0 +1,10 @@ +- name: Get known_hosts facts + generate_all_known_hosts: + hostvars: "{{ hostvars }}" + +- name: add known_host record for every public key of every other ip, hostname + known_hosts: + name: "{{ item.name }}" + key: "{{ item.key }}" + with_items: + "{{ all_known_hosts }}"