Add the "fingerprint_hash_type" option to ssh state and module

Fixes #40005

This new option allows the user to specify what kind of hash was
used when the public key was hashed to a fingerprint. The default
hash is md5, but the user should be able to specify this if they
know what the fingerprint was originally hashed with.
This commit is contained in:
rallytime 2017-04-05 10:58:58 -06:00
parent a6291b17c1
commit 1ef81e6a55
2 changed files with 117 additions and 24 deletions

View file

@ -221,7 +221,7 @@ def _validate_keys(key_file):
return ret
def _fingerprint(public_key):
def _fingerprint(public_key, fingerprint_hash_type=None):
'''
Return a public key fingerprint based on its base64-encoded representation
@ -229,7 +229,39 @@ def _fingerprint(public_key):
in the form "xx:xx:...:xx"
If the key is invalid (incorrect base64 string), return None
public_key
The public key to return the fingerprint for
fingerprint_hash_type
The public key fingerprint hash type that the public key fingerprint
was originally hashed with. This defaults to ``md5`` if not specified.
.. versionadded:: 2016.11.4
.. note::
The default value of the ``fingerprint_hash_type`` will change to
``sha256`` in Salt Nitrogen.
'''
if fingerprint_hash_type:
hash_type = fingerprint_hash_type.lower()
else:
# Set fingerprint_hash_type to md5 as default
log.warning('Public Key hashing currently defaults to "md5". This will '
'change to "sha256" in the Nitrogen release.')
hash_type = 'md5'
try:
hash_func = getattr(hashlib, hash_type)
except AttributeError:
raise CommandExecutionError(
'The fingerprint_hash_type {0} is not supported.'.format(
hash_type
)
)
try:
if six.PY2:
raw_key = public_key.decode('base64')
@ -237,7 +269,9 @@ def _fingerprint(public_key):
raw_key = base64.b64decode(public_key, validate=True) # pylint: disable=E1123
except binascii.Error:
return None
ret = hashlib.md5(raw_key).hexdigest()
ret = hash_func(raw_key).hexdigest()
chunks = [ret[i:i + 2] for i in range(0, len(ret), 2)]
return ':'.join(chunks)
@ -703,7 +737,7 @@ def set_auth_key(
return 'new'
def _parse_openssh_output(lines):
def _parse_openssh_output(lines, fingerprint_hash_type=None):
'''
Helper function which parses ssh-keygen -F and ssh-keyscan function output
and yield dict with keys information, one by one.
@ -715,7 +749,8 @@ def _parse_openssh_output(lines):
hostname, enc, key = line.split()
except ValueError: # incorrect format
continue
fingerprint = _fingerprint(key)
fingerprint = _fingerprint(key,
fingerprint_hash_type=fingerprint_hash_type)
if not fingerprint:
continue
yield {'hostname': hostname, 'key': key, 'enc': enc,
@ -723,7 +758,11 @@ def _parse_openssh_output(lines):
@decorators.which('ssh-keygen')
def get_known_host(user, hostname, config=None, port=None):
def get_known_host(user,
hostname,
config=None,
port=None,
fingerprint_hash_type=None):
'''
Return information about known host from the configfile, if any.
If there is no such key, return None.
@ -744,7 +783,10 @@ def get_known_host(user, hostname, config=None, port=None):
lines = __salt__['cmd.run'](cmd,
ignore_retcode=True,
python_shell=False).splitlines()
known_hosts = list(_parse_openssh_output(lines))
known_hosts = list(
_parse_openssh_output(lines,
fingerprint_hash_type=fingerprint_hash_type)
)
return known_hosts[0] if known_hosts else None
@ -753,7 +795,8 @@ def recv_known_host(hostname,
enc=None,
port=None,
hash_known_hosts=True,
timeout=5):
timeout=5,
fingerprint_hash_type=None):
'''
Retrieve information about host public key from remote server
@ -780,6 +823,17 @@ def recv_known_host(hostname,
.. versionadded:: 2016.3.0
fingerprint_hash_type
The public key fingerprint hash type that the public key fingerprint
was originally hashed with. This defaults to ``md5`` if not specified.
.. versionadded:: 2016.11.4
.. note::
The default value of the ``fingerprint_hash_type`` will change to
``sha256`` in Salt Nitrogen.
CLI Example:
.. code-block:: bash
@ -806,12 +860,13 @@ def recv_known_host(hostname,
while not lines and attempts > 0:
attempts = attempts - 1
lines = __salt__['cmd.run'](cmd, python_shell=False).splitlines()
known_hosts = list(_parse_openssh_output(lines))
known_hosts = list(_parse_openssh_output(lines,
fingerprint_hash_type=fingerprint_hash_type))
return known_hosts[0] if known_hosts else None
def check_known_host(user=None, hostname=None, key=None, fingerprint=None,
config=None, port=None):
config=None, port=None, fingerprint_hash_type=None):
'''
Check the record in known_hosts file, either by its value or by fingerprint
(it's enough to set up either key or fingerprint, you don't need to set up
@ -838,7 +893,11 @@ def check_known_host(user=None, hostname=None, key=None, fingerprint=None,
else:
config = config or '.ssh/known_hosts'
known_host = get_known_host(user, hostname, config=config, port=port)
known_host = get_known_host(user,
hostname,
config=config,
port=port,
fingerprint_hash_type=fingerprint_hash_type)
if not known_host or 'fingerprint' not in known_host:
return 'add'
@ -892,7 +951,8 @@ def set_known_host(user=None,
enc=None,
config=None,
hash_known_hosts=True,
timeout=5):
timeout=5,
fingerprint_hash_type=None):
'''
Download SSH public key from remote host "hostname", optionally validate
its fingerprint against "fingerprint" variable and save the record in the
@ -907,7 +967,7 @@ def set_known_host(user=None,
The name of the remote host (e.g. "github.com")
fingerprint
The fingerprint of the key which must be presented in the known_hosts
The fingerprint of the key which must be present in the known_hosts
file (optional if key specified)
key
@ -940,6 +1000,17 @@ def set_known_host(user=None,
.. versionadded:: 2016.3.0
fingerprint_hash_type
The public key fingerprint hash type that the public key fingerprint
was originally hashed with. This defaults to ``md5`` if not specified.
.. versionadded:: 2016.11.4
.. note::
The default value of the ``fingerprint_hash_type`` will change to
``sha256`` in Salt Nitrogen.
CLI Example:
.. code-block:: bash
@ -957,7 +1028,11 @@ def set_known_host(user=None,
update_required = False
check_required = False
stored_host = get_known_host(user, hostname, config, port)
stored_host = get_known_host(user,
hostname,
config=config,
port=port,
fingerprint_hash_type=fingerprint_hash_type)
if not stored_host:
update_required = True
@ -976,7 +1051,8 @@ def set_known_host(user=None,
enc=enc,
port=port,
hash_known_hosts=hash_known_hosts,
timeout=timeout)
timeout=timeout,
fingerprint_hash_type=fingerprint_hash_type)
if not remote_host:
return {'status': 'error',
'error': 'Unable to receive remote host key'}

View file

@ -50,7 +50,8 @@ def present(
enc=None,
config=None,
hash_known_hosts=True,
timeout=5):
timeout=5,
fingerprint_hash_type=None):
'''
Verifies that the specified host is known by the specified user
@ -96,6 +97,18 @@ def present(
and the host in question considered unavailable. Default is 5 seconds.
.. versionadded:: 2016.3.0
fingerprint_hash_type
The public key fingerprint hash type that the public key fingerprint
was originally hashed with. This defaults to ``md5`` if not specified.
.. versionadded:: 2016.11.4
.. note::
The default value of the ``fingerprint_hash_type`` will change to
``sha256`` in Salt Nitrogen.
'''
ret = {'name': name,
'changes': {},
@ -127,7 +140,8 @@ def present(
key=key,
fingerprint=fingerprint,
config=config,
port=port)
port=port,
fingerprint_hash_type=fingerprint_hash_type)
except CommandNotFoundError as err:
ret['result'] = False
ret['comment'] = 'ssh.check_known_host error: {0}'.format(err)
@ -146,14 +160,17 @@ def present(
config)
return dict(ret, comment=comment)
result = __salt__['ssh.set_known_host'](user=user, hostname=name,
fingerprint=fingerprint,
key=key,
port=port,
enc=enc,
config=config,
hash_known_hosts=hash_known_hosts,
timeout=timeout)
result = __salt__['ssh.set_known_host'](
user=user,
hostname=name,
fingerprint=fingerprint,
key=key,
port=port,
enc=enc,
config=config,
hash_known_hosts=hash_known_hosts,
timeout=timeout,
fingerprint_hash_type=fingerprint_hash_type)
if result['status'] == 'exists':
return dict(ret,
comment='{0} already exists in {1}'.format(name, config))