adding hostscan
This commit is contained in:
parent
d264281284
commit
405bf79d56
205
aif/scripts/post/hostscan.py
Executable file
205
aif/scripts/post/hostscan.py
Executable file
@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import grp
|
||||
import os
|
||||
import pwd
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Defaults
|
||||
#def_supported_keys = subprocess.run(['ssh',
|
||||
# '-Q',
|
||||
# 'key'], stdout = subprocess.PIPE).stdout.decode('utf-8').splitlines()
|
||||
def_supported_keys = ['dsa', 'ecdsa', 'ed25519', 'rsa']
|
||||
def_mode = 'append'
|
||||
def_syshostkeys = '/etc/ssh/ssh_known_hosts'
|
||||
def_user = pwd.getpwuid(os.geteuid())[0]
|
||||
def_grp = grp.getgrgid(os.getegid())[0]
|
||||
|
||||
|
||||
class hostscanner(object):
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
if self.args['keytypes'] == ['all']:
|
||||
self.args['keytypes'] = def_supported_keys
|
||||
if self.args['system']:
|
||||
if os.geteuid() != 0:
|
||||
exit(('You have specified system-wide modification but ' +
|
||||
'are not running with root privileges! Exiting.'))
|
||||
self.args['output'] = def_syshostkeys
|
||||
if self.args['output'] != sys.stdout:
|
||||
_pardir = os.path.dirname(os.path.abspath(os.path.expanduser(self.args['output'])))
|
||||
if _pardir.startswith('/home'):
|
||||
_octmode = 0o700
|
||||
else:
|
||||
_octmode = 0o755
|
||||
os.makedirs(_pardir, mode = _octmode, exist_ok = True)
|
||||
os.chown(_pardir,
|
||||
pwd.getpwnam(self.args['chown_user'])[2],
|
||||
grp.getgrnam(self.args['chown_grp'])[2])
|
||||
|
||||
def getHosts(self):
|
||||
self.keys = {}
|
||||
_hosts = os.path.abspath(os.path.expanduser(self.args['infile']))
|
||||
with open(_hosts, 'r') as f:
|
||||
for l in f.readlines():
|
||||
l = l.strip()
|
||||
if re.search('^\s*(#.*)?$', l, re.MULTILINE):
|
||||
continue # Skip commented and blank lines
|
||||
k = re.sub('^([0-9a-z-\.]+)\s*#.*$',
|
||||
'\g<1>',
|
||||
l.strip().lower(),
|
||||
re.MULTILINE)
|
||||
self.keys[k] = []
|
||||
return()
|
||||
|
||||
def getKeys(self):
|
||||
def parseType(k):
|
||||
_newkey = re.sub('^ssh-', '', k).split('-')[0]
|
||||
if _newkey == 'dss':
|
||||
_newkey = 'dsa'
|
||||
return(_newkey)
|
||||
for h in list(self.keys.keys()):
|
||||
_h = h.split(':')
|
||||
if len(_h) == 1:
|
||||
_host = _h[0]
|
||||
_port = 22
|
||||
elif len(_h) == 2:
|
||||
_host = _h[0]
|
||||
_port = int(_h[1])
|
||||
_cmdline = ['ssh-keyscan',
|
||||
'-t', ','.join(self.args['keytypes']),
|
||||
'-p', str(_port),
|
||||
_host]
|
||||
if self.args['hash']:
|
||||
#https://security.stackexchange.com/a/56283
|
||||
# verify via:
|
||||
# SAMPLE ENTRY: |1|F1E1KeoE/eEWhi10WpGv4OdiO6Y=|3988QV0VE8wmZL7suNrYQLITLCg= ssh-rsa ...
|
||||
#key=$(echo F1E1KeoE/eEWhi10WpGv4OdiO6Y= | base64 -d | xxd -p)
|
||||
#echo -n "192.168.1.61" | openssl sha1 -mac HMAC -macopt hexkey:${key} | awk '{print $2}' | xxd -r -p | base64
|
||||
_cmdline.insert(1, '-H')
|
||||
_cmd = subprocess.run(_cmdline,
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE)
|
||||
if not re.match('\s*#.*', _cmd.stderr.decode('utf-8')):
|
||||
_printerr = []
|
||||
for i in _cmd.stderr.decode('utf-8').splitlines():
|
||||
if i.strip() not in _printerr:
|
||||
_printerr.append(i.strip())
|
||||
print('{0}: errors detected; skipping ({1})'.format(h, '\n'.join(_printerr)))
|
||||
del(self.keys[h])
|
||||
continue
|
||||
for l in _cmd.stdout.decode('utf-8').splitlines():
|
||||
_l = l.split()
|
||||
_key = {'type': _l[1],
|
||||
'host': _l[0],
|
||||
'key': _l[2]}
|
||||
if parseType(_key['type']) in self.args['keytypes']:
|
||||
self.keys[h].append(_key)
|
||||
return()
|
||||
|
||||
def write(self):
|
||||
for h in self.keys.keys():
|
||||
for i in self.keys[h]:
|
||||
_s = '# Automatically added via hostscan.py\n{0} {1} {2}\n'.format(i['host'],
|
||||
i['type'],
|
||||
i['key'])
|
||||
if self.args['output'] == sys.stdout:
|
||||
print(_s, end = '')
|
||||
else:
|
||||
if self.args['writemode'] == 'append':
|
||||
_wm = 'a'
|
||||
else:
|
||||
_wm = 'w'
|
||||
with open(self.args['output'], _wm) as f:
|
||||
f.write(_s)
|
||||
os.chmod(self.args['output'], 0o644)
|
||||
os.chown(self.args['output'],
|
||||
pwd.getpwnam(self.args['chown_user'])[2],
|
||||
grp.getgrnam(self.args['chown_grp'])[2])
|
||||
return()
|
||||
|
||||
def parseArgs():
|
||||
def getTypes(t):
|
||||
keytypes = t.split(',')
|
||||
keytypes = [k.strip() for k in keytypes]
|
||||
for k in keytypes:
|
||||
if k not in ('all', *def_supported_keys):
|
||||
raise argparse.ArgumentError('Must be one or more of the following: all, {0}'.format(', '.join(def_supported_keys)))
|
||||
return(keytypes)
|
||||
args = argparse.ArgumentParser(description = ('Scan a list of hosts and present their hostkeys in ' +
|
||||
'a format suitable for an SSH known_hosts file.'))
|
||||
args.add_argument('-u',
|
||||
'--user',
|
||||
dest = 'chown_user',
|
||||
default = def_user,
|
||||
help = ('The username to chown the file to (if \033[1m{0}\033[0m is specified). ' +
|
||||
'Default: \033[1m{1}\033[0m').format('-o/--output', def_user))
|
||||
args.add_argument('-g',
|
||||
'--group',
|
||||
dest = 'chown_grp',
|
||||
default = def_grp,
|
||||
help = ('The group to chown the file to (if \033[1m{0}\033[0m is specified). ' +
|
||||
'Default: \033[1m{1}\033[0m').format('-o/--output', def_grp))
|
||||
args.add_argument('-H',
|
||||
'--hash',
|
||||
dest = 'hash',
|
||||
action = 'store_true',
|
||||
help = ('If specified, hash the hostkeys (see ssh-keyscan(1)\'s -H option for more info)'))
|
||||
args.add_argument('-m',
|
||||
'--mode',
|
||||
dest = 'writemode',
|
||||
default = def_mode,
|
||||
choices = ['append', 'replace'],
|
||||
help = ('If \033[1m{0}\033[0m is specified, the mode to use for the ' +
|
||||
'destination file. The default is \033[1m{1}\033[0m').format('-o/--output', def_mode))
|
||||
args.add_argument('-k',
|
||||
'--keytypes',
|
||||
dest = 'keytypes',
|
||||
type = getTypes,
|
||||
default = 'all',
|
||||
help = ('A comma-separated list of key types to add (if supported by the target host). ' +
|
||||
'The default is to add all keys found. Must be one (or more) of: \033[1m{0}\033[0m').format(', '.join(def_supported_keys)))
|
||||
args.add_argument('-o',
|
||||
'--output',
|
||||
default = sys.stdout,
|
||||
metavar = 'OUTFILE',
|
||||
dest = 'output',
|
||||
help = ('If specified, write the hostkeys to \033[1m{0}\033[0m instead of ' +
|
||||
'\033[1m{1}\033[0m (the default). ' +
|
||||
'Overrides \033[1m{2}\033[0m').format('OUTFILE',
|
||||
'stdout',
|
||||
'-S/--system-wide'))
|
||||
args.add_argument('-S',
|
||||
'--system-wide',
|
||||
dest = 'system',
|
||||
action = 'store_true',
|
||||
help = ('If specified, apply to the entire system (not just the ' +
|
||||
'specified/running user) via {0}. ' +
|
||||
'Requires \033[1m{1}\033[0m in /etc/ssh/ssh_config (usually ' +
|
||||
'enabled silently by default) and running with root ' +
|
||||
'privileges').format(def_syshostkeys,
|
||||
'GlobalKnownHostsFile {0}'.format(def_syshostkeys)))
|
||||
args.add_argument(metavar = 'HOSTLIST_FILE',
|
||||
dest = 'infile',
|
||||
help = ('The path to the list of hosts. Can contain blank lines and/or comments. ' +
|
||||
'One host per line. Can be \033[1m{0}\033[0m (as long as it\'s resolvable), ' +
|
||||
'\033[1m{1}\033[0m, or \033[1m{2}\033[0m. To specify an alternate port, ' +
|
||||
'add \033[1m{3}\033[0m to the end (e.g. ' +
|
||||
'"some.host.tld:22")').format('hostname',
|
||||
'IP address',
|
||||
'FQDN',
|
||||
':<PORTNUM>'))
|
||||
return(args)
|
||||
|
||||
def main():
|
||||
args = vars(parseArgs().parse_args())
|
||||
scan = hostscanner(args)
|
||||
scan.getHosts()
|
||||
scan.getKeys()
|
||||
scan.write()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue
Block a user