mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-08-22 22:11:44 -07:00
known_hosts: support --diff (#20349)
* known_hosts: support --diff
* known_hosts: support --diff also without --check
* Add unit tests and fix incorrect diff in one corner case
Tests are good!
* Refactor for readability
* Python 3 compat
* More Python 3 compat
* Add an integration test for known_hosts
* Handle ssh-keygen -HF returning non-zero exit code
AFAICT this is a bug in ssh-keygen in some newer OpenSSH versions
(>= 6.4 probably; see commit dd9d5cc670
):
when you invoke ssh-keygen with -H and -F <host> options, it always
returns exit code 1. This is because in ssh-keygen.c there's a function
do_known_hosts() which calls
exit (find_host && !ctx.found_key);
at the end, and find_host is 1 (because we passed -F on the command line),
but ctx.found_key is always 0. Why is found_key always 0? Because the
callback passed to hostkeys_foreach(), which is known_hosts_hash(),
never bothers to set found_key to 1.
* This test does not need root
* Avoid ssh-ed25519 keys in sample known_hosts file
Older versions of OpenSSH do not like them and ssh-keygen -HF
aborts with an error when it sees such keys:
line 5 invalid key: example.net...
/root/ansible_testing/known_hosts is not a valid known_hosts file.
* Fix Python 3 errors
Specifically, the default mode of tempfile.NamedTemporaryFile is 'w+b',
which means Python 3 wants us to write bytes objects to it -- but the
keys we have are all unicode strings.
This commit is contained in:
parent
d0bc98bddb
commit
2efb692cc4
8 changed files with 324 additions and 4 deletions
35
lib/ansible/modules/system/known_hosts.py
Normal file → Executable file
35
lib/ansible/modules/system/known_hosts.py
Normal file → Executable file
|
@ -119,12 +119,15 @@ def enforce_state(module, params):
|
|||
|
||||
found,replace_or_add,found_line,key=search_for_host_key(module,host,key,hash_host,path,sshkeygen)
|
||||
|
||||
params['diff'] = compute_diff(path, found_line, replace_or_add, state, key)
|
||||
|
||||
#We will change state if found==True & state!="present"
|
||||
#or found==False & state=="present"
|
||||
#i.e found XOR (state=="present")
|
||||
#Alternatively, if replace is true (i.e. key present, and we must change it)
|
||||
if module.check_mode:
|
||||
module.exit_json(changed = replace_or_add or (state=="present") != found)
|
||||
module.exit_json(changed = replace_or_add or (state=="present") != found,
|
||||
diff=params['diff'])
|
||||
|
||||
#Now do the work.
|
||||
|
||||
|
@ -145,7 +148,7 @@ def enforce_state(module, params):
|
|||
module.fail_json(msg="Failed to read %s: %s" % \
|
||||
(path,str(e)))
|
||||
try:
|
||||
outf=tempfile.NamedTemporaryFile(dir=os.path.dirname(path))
|
||||
outf = tempfile.NamedTemporaryFile(mode='w+', dir=os.path.dirname(path))
|
||||
if inf is not None:
|
||||
for line_number, line in enumerate(inf):
|
||||
if found_line==(line_number + 1) and (replace_or_add or state=='absent'):
|
||||
|
@ -188,7 +191,7 @@ def sanity_check(module,host,key,sshkeygen):
|
|||
#The approach is to write the key to a temporary file,
|
||||
#and then attempt to look up the specified host in that file.
|
||||
try:
|
||||
outf=tempfile.NamedTemporaryFile()
|
||||
outf = tempfile.NamedTemporaryFile(mode='w+')
|
||||
outf.write(key)
|
||||
outf.flush()
|
||||
except IOError:
|
||||
|
@ -240,7 +243,7 @@ def search_for_host_key(module,host,key,hash_host,path,sshkeygen):
|
|||
|
||||
sshkeygen_command.insert(1,'-H')
|
||||
rc,stdout,stderr=module.run_command(sshkeygen_command,check_rc=False)
|
||||
if rc!=0: #something went wrong
|
||||
if rc not in (0, 1) or stderr != '': #something went wrong
|
||||
module.fail_json(msg="ssh-keygen failed to hash host (rc=%d,stdout='%s',stderr='%s')" % (rc,stdout,stderr))
|
||||
hashed_lines=stdout.split('\n')
|
||||
|
||||
|
@ -294,6 +297,30 @@ def normalize_known_hosts_key(key):
|
|||
d['key']=k[2]
|
||||
return d
|
||||
|
||||
def compute_diff(path, found_line, replace_or_add, state, key):
|
||||
diff = {
|
||||
'before_header': path,
|
||||
'after_header': path,
|
||||
'before': '',
|
||||
'after': '',
|
||||
}
|
||||
try:
|
||||
inf = open(path, "r")
|
||||
except IOError:
|
||||
e = get_exception()
|
||||
if e.errno == errno.ENOENT:
|
||||
diff['before_header'] = '/dev/null'
|
||||
else:
|
||||
diff['before'] = inf.read()
|
||||
inf.close()
|
||||
lines = diff['before'].splitlines(1)
|
||||
if (replace_or_add or state == 'absent') and found_line is not None and 1 <= found_line <= len(lines):
|
||||
del lines[found_line - 1]
|
||||
if state == 'present' and (replace_or_add or found_line is None):
|
||||
lines.append(key)
|
||||
diff['after'] = ''.join(lines)
|
||||
return diff
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue