1# -*- coding: utf-8 -*- 2# This code is part of Ansible, but is an independent component. 3# This particular file snippet, and this file snippet only, is BSD licensed. 4# Modules you write using this snippet, which is embedded dynamically by Ansible 5# still belong to the author of the module, and may assign their own license 6# to the complete work. 7# 8# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013 9# 10# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) 11 12from __future__ import (absolute_import, division, print_function) 13__metaclass__ = type 14 15import os 16import hmac 17import re 18 19from ansible.module_utils.six.moves.urllib.parse import urlparse 20 21try: 22 from hashlib import sha1 23except ImportError: 24 import sha as sha1 25 26HASHED_KEY_MAGIC = "|1|" 27 28 29def is_ssh_url(url): 30 31 """ check if url is ssh """ 32 33 if "@" in url and "://" not in url: 34 return True 35 for scheme in "ssh://", "git+ssh://", "ssh+git://": 36 if url.startswith(scheme): 37 return True 38 return False 39 40 41def get_fqdn_and_port(repo_url): 42 43 """ chop the hostname and port out of a url """ 44 45 fqdn = None 46 port = None 47 ipv6_re = re.compile(r'(\[[^]]*\])(?::([0-9]+))?') 48 if "@" in repo_url and "://" not in repo_url: 49 # most likely an user@host:path or user@host/path type URL 50 repo_url = repo_url.split("@", 1)[1] 51 match = ipv6_re.match(repo_url) 52 # For this type of URL, colon specifies the path, not the port 53 if match: 54 fqdn, path = match.groups() 55 elif ":" in repo_url: 56 fqdn = repo_url.split(":")[0] 57 elif "/" in repo_url: 58 fqdn = repo_url.split("/")[0] 59 elif "://" in repo_url: 60 # this should be something we can parse with urlparse 61 parts = urlparse(repo_url) 62 # parts[1] will be empty on python2.4 on ssh:// or git:// urls, so 63 # ensure we actually have a parts[1] before continuing. 64 if parts[1] != '': 65 fqdn = parts[1] 66 if "@" in fqdn: 67 fqdn = fqdn.split("@", 1)[1] 68 match = ipv6_re.match(fqdn) 69 if match: 70 fqdn, port = match.groups() 71 elif ":" in fqdn: 72 fqdn, port = fqdn.split(":")[0:2] 73 return fqdn, port 74 75 76def check_hostkey(module, fqdn): 77 return not not_in_host_file(module, fqdn) 78 79 80# this is a variant of code found in connection_plugins/paramiko.py and we should modify 81# the paramiko code to import and use this. 82 83def not_in_host_file(self, host): 84 85 if 'USER' in os.environ: 86 user_host_file = os.path.expandvars("~${USER}/.ssh/known_hosts") 87 else: 88 user_host_file = "~/.ssh/known_hosts" 89 user_host_file = os.path.expanduser(user_host_file) 90 91 host_file_list = [ 92 user_host_file, 93 "/etc/ssh/ssh_known_hosts", 94 "/etc/ssh/ssh_known_hosts2", 95 "/etc/openssh/ssh_known_hosts", 96 ] 97 98 hfiles_not_found = 0 99 for hf in host_file_list: 100 if not os.path.exists(hf): 101 hfiles_not_found += 1 102 continue 103 104 try: 105 host_fh = open(hf) 106 except IOError: 107 hfiles_not_found += 1 108 continue 109 else: 110 data = host_fh.read() 111 host_fh.close() 112 113 for line in data.split("\n"): 114 if line is None or " " not in line: 115 continue 116 tokens = line.split() 117 if tokens[0].find(HASHED_KEY_MAGIC) == 0: 118 # this is a hashed known host entry 119 try: 120 (kn_salt, kn_host) = tokens[0][len(HASHED_KEY_MAGIC):].split("|", 2) 121 hash = hmac.new(kn_salt.decode('base64'), digestmod=sha1) 122 hash.update(host) 123 if hash.digest() == kn_host.decode('base64'): 124 return False 125 except Exception: 126 # invalid hashed host key, skip it 127 continue 128 else: 129 # standard host file entry 130 if host in tokens[0]: 131 return False 132 133 return True 134 135 136def add_host_key(module, fqdn, port=22, key_type="rsa", create_dir=False): 137 138 """ use ssh-keyscan to add the hostkey """ 139 140 keyscan_cmd = module.get_bin_path('ssh-keyscan', True) 141 142 if 'USER' in os.environ: 143 user_ssh_dir = os.path.expandvars("~${USER}/.ssh/") 144 user_host_file = os.path.expandvars("~${USER}/.ssh/known_hosts") 145 else: 146 user_ssh_dir = "~/.ssh/" 147 user_host_file = "~/.ssh/known_hosts" 148 user_ssh_dir = os.path.expanduser(user_ssh_dir) 149 150 if not os.path.exists(user_ssh_dir): 151 if create_dir: 152 try: 153 os.makedirs(user_ssh_dir, int('700', 8)) 154 except Exception: 155 module.fail_json(msg="failed to create host key directory: %s" % user_ssh_dir) 156 else: 157 module.fail_json(msg="%s does not exist" % user_ssh_dir) 158 elif not os.path.isdir(user_ssh_dir): 159 module.fail_json(msg="%s is not a directory" % user_ssh_dir) 160 161 if port: 162 this_cmd = "%s -t %s -p %s %s" % (keyscan_cmd, key_type, port, fqdn) 163 else: 164 this_cmd = "%s -t %s %s" % (keyscan_cmd, key_type, fqdn) 165 166 rc, out, err = module.run_command(this_cmd) 167 # ssh-keyscan gives a 0 exit code and prints nothing on timeout 168 if rc != 0 or not out: 169 msg = 'failed to retrieve hostkey' 170 if not out: 171 msg += '. "%s" returned no matches.' % this_cmd 172 else: 173 msg += ' using command "%s". [stdout]: %s' % (this_cmd, out) 174 175 if err: 176 msg += ' [stderr]: %s' % err 177 178 module.fail_json(msg=msg) 179 180 module.append_to_file(user_host_file, out) 181 182 return rc, out, err 183