From 1d490a372945134f2aebe69b77fc97bf2087ae78 Mon Sep 17 00:00:00 2001 From: Paul Belanger Date: Wed, 13 Mar 2019 21:09:24 -0400 Subject: [PATCH] Manage bastion SSH known_hosts file This attempts to manage the SSH host keys of all the hosts in the inventory. The idea here is each host in the inventory will add a ansible_ssh_host_key_ecdsa_public variable, then when we first run our bastion playbook we'll properly populate the local known_hosts file each time. Change-Id: I26e328192a7127086e514dc62a27cb946a77440b Depends-On: https://review.openstack.org/643408 Signed-off-by: Paul Belanger --- launch/launch-node.py | 9 +++++-- launch/utils.py | 25 +++++++++++++++++++ playbooks/bootstrap/site.yaml | 9 +++++++ .../bastion/root/.ssh/known_hosts.j2 | 10 ++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 playbooks/bootstrap/templates/bastion/root/.ssh/known_hosts.j2 diff --git a/launch/launch-node.py b/launch/launch-node.py index 4aeab2b..f1f2998 100755 --- a/launch/launch-node.py +++ b/launch/launch-node.py @@ -69,6 +69,10 @@ def bootstrap_server(server, key, name, group, keep, timeout): ansible_user = None print("--- Running initial configuration on host %s ---" % ip) + host_key = utils.nodescan(ip) + if not host_key: + raise Exception("Unable to find ssh-ecdsa SSH host key") + for username in ['ubuntu', 'centos']: ssh_client = utils.ssh_connect( ip, username, ssh_kwargs, timeout=timeout) @@ -87,9 +91,10 @@ def bootstrap_server(server, key, name, group, keep, timeout): with open(runner.hosts, 'w') as inventory_file: inventory_file.write( "[{group}]\n{host} ansible_host={ip} " - "ansible_user={user}".format( + "ansible_user={user} " + "ansible_ssh_host_key_ed25519_public={host_key}".format( group=group, host=name, ip=server.interface_ip, - user=ansible_user)) + user=ansible_user, host_key=host_key)) project_dir = os.path.join( SCRIPT_DIR, '..', 'playbooks', 'bootstrap-ansible') diff --git a/launch/utils.py b/launch/utils.py index faef094..aa17ac6 100644 --- a/launch/utils.py +++ b/launch/utils.py @@ -15,6 +15,7 @@ # limitations under the License. import socket +import subprocess import time import paramiko @@ -32,6 +33,30 @@ def iterate_timeout(max_seconds, purpose): raise Exception("Timeout waiting for %s" % purpose) +def nodescan(ip, port=22, timeout=60): + """Scan the IP address for public SSH keys. + + Returns SSH host key + """ + + key = None + output = None + for count in iterate_timeout( + timeout, "connection to %s on port %s" % (ip, port)): + + try: + output = subprocess.check_output( + ['ssh-keyscan', '-t', 'ed25519', '-p', str(port), str(ip)]) + if output: + break + except Exception as e: + print("ssh-keyscan failure: %s", e) + + key = output.split()[2].decode('utf8') + + return key + + def ssh_connect(ip, username, connect_kwargs={}, timeout=60): # HPcloud may return errno 111 for about 30 seconds after adding the IP for count in iterate_timeout(timeout, "ssh access"): diff --git a/playbooks/bootstrap/site.yaml b/playbooks/bootstrap/site.yaml index 653f568..e6df345 100644 --- a/playbooks/bootstrap/site.yaml +++ b/playbooks/bootstrap/site.yaml @@ -12,6 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. --- +- name: Configure bastion SSH known_hosts + hosts: bastion:!disabled + gather_facts: false + tasks: + - name: Ensure SSH host keys are known + template: + dest: ~/.ssh/known_hosts + src: bastion/root/.ssh/known_hosts.j2 + - name: Bootstrap all hosts hosts: all:!disabled tasks: diff --git a/playbooks/bootstrap/templates/bastion/root/.ssh/known_hosts.j2 b/playbooks/bootstrap/templates/bastion/root/.ssh/known_hosts.j2 new file mode 100644 index 0000000..a6f2bc4 --- /dev/null +++ b/playbooks/bootstrap/templates/bastion/root/.ssh/known_hosts.j2 @@ -0,0 +1,10 @@ +# This file is generated by Ansible +# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN +# +{% for host in groups['all'] %} +{% if hostvars[host].ansible_host is defined %} +{% if hostvars[host].ansible_ssh_host_key_ed25519_public is defined %} +{{ hostvars[host].ansible_host }} ssh-ed25519 {{ hostvars[host].ansible_ssh_host_key_ed25519_public }} +{% endif %} +{% endif %} +{% endfor %}