updates to sshsecure... HOPEFULLY it works with centos 6 now.
This commit is contained in:
parent
46357d8cf8
commit
aa8fa6f1c4
@ -100,35 +100,69 @@ import subprocess # REMOVE WHEN SWITCHING TO PURE PYTHON
|
|||||||
# from cryptography.hazmat.backends import default_backend as crypto_default_backend
|
# from cryptography.hazmat.backends import default_backend as crypto_default_backend
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# TODO: associate various config directives with version, too.
|
||||||
|
# For now, we use this for primarily CentOS 6.x, which doesn't support ED25519 and probably some of the MACs.
|
||||||
|
# Bastards.
|
||||||
|
# https://ssh-comparison.quendi.de/comparison/cipher.html at some point in the future...
|
||||||
|
# TODO: maybe implement some parsing of the ssh -Q stuff? https://superuser.com/a/869005/984616
|
||||||
|
# If you encounter a version incompatibility, please let me know!
|
||||||
|
magic_ver = 6.5
|
||||||
|
ssh_ver = subprocess.run(['ssh', '-V'], stderr = subprocess.PIPE).stderr.decode('utf-8').strip().split()[0]
|
||||||
|
ssh_ver = float(re.sub('^(Open|Sun_)SSH_([0-9\.]+)(p[0-9]+)?,.*$', '\g<2>', ssh_ver))
|
||||||
|
if ssh_ver >= magic_ver:
|
||||||
|
has_ed25519 = True
|
||||||
|
supported_keys = ('ed25519', 'rsa')
|
||||||
|
else:
|
||||||
|
has_ed25519 = False
|
||||||
|
supported_keys = ('rsa', )
|
||||||
|
|
||||||
|
|
||||||
conf_options = {}
|
conf_options = {}
|
||||||
conf_options['sshd'] = {'KexAlgorithms': 'curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256',
|
conf_options['sshd'] = {'KexAlgorithms': 'diffie-hellman-group-exchange-sha256',
|
||||||
'Protocol': '2',
|
'Protocol': '2',
|
||||||
'HostKey': ['/etc/ssh/ssh_host_ed25519_key',
|
'HostKey': ['/etc/ssh/ssh_host_rsa_key'],
|
||||||
'/etc/ssh/ssh_host_rsa_key'],
|
#'PermitRootLogin': 'prohibit-password',
|
||||||
'PermitRootLogin': 'prohibit-password',
|
'PermitRootLogin': 'without-password',
|
||||||
'PasswordAuthentication': 'no',
|
'PasswordAuthentication': 'no',
|
||||||
'ChallengeResponseAuthentication': 'no',
|
'ChallengeResponseAuthentication': 'no',
|
||||||
'PubkeyAuthentication': 'yes',
|
'PubkeyAuthentication': 'yes',
|
||||||
'Ciphers': 'chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr',
|
'Ciphers': 'aes256-ctr,aes192-ctr,aes128-ctr',
|
||||||
'MACs': ('hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,' +
|
'MACs': ('hmac-sha2-512,hmac-sha2-256')}
|
||||||
'hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com')}
|
if has_ed25519:
|
||||||
|
conf_options['sshd']['HostKey'].append('/etc/ssh/ssh_host_ed25519_key')
|
||||||
|
conf_options['sshd']['KexAlgorithms'] = ','.join(('curve25519-sha256@libssh.org',
|
||||||
|
conf_options['sshd']['KexAlgorithms']))
|
||||||
|
conf_options['sshd']['Ciphers'] = ','.join((('chacha20-poly1305@openssh.com,'
|
||||||
|
'aes256-gcm@openssh.com,'
|
||||||
|
'aes128-gcm@openssh.com'),
|
||||||
|
conf_options['sshd']['Ciphers']))
|
||||||
|
conf_options['sshd']['MACs'] = ','.join((('hmac-sha2-512-etm@openssh.com,'
|
||||||
|
'hmac-sha2-256-etm@openssh.com,'
|
||||||
|
'umac-128-etm@openssh.com'),
|
||||||
|
conf_options['sshd']['MACs'],
|
||||||
|
'umac-128@openssh.com'))
|
||||||
# Uncomment if this is further configured
|
# Uncomment if this is further configured
|
||||||
#conf_options['sshd']['AllowGroups'] = 'ssh-user'
|
#conf_options['sshd']['AllowGroups'] = 'ssh-user'
|
||||||
|
|
||||||
conf_options['ssh'] = {'Host': {'*': {'KexAlgorithms': 'curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256',
|
conf_options['ssh'] = {'Host': {'*': {'KexAlgorithms': 'diffie-hellman-group-exchange-sha256',
|
||||||
'PubkeyAuthentication': 'yes',
|
'PubkeyAuthentication': 'yes',
|
||||||
'HostKeyAlgorithms': 'ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-ed25519,ssh-rsa'}}}
|
'HostKeyAlgorithms': 'ssh-rsa'}}}
|
||||||
# Uncomment below if Github still needs diffie-hellman-group-exchange-sha1 sometimes.
|
if has_ed25519:
|
||||||
# For what it's worth, it doesn't seem to.
|
conf_options['ssh']['Host']['*']['KexAlgorithms'] = ','.join(('curve25519-sha256@libssh.org',
|
||||||
#conf_options['ssh']['Host']['github.com'] = {'KexAlgorithms': 'curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256,' +
|
conf_options['ssh']['Host']['*']['KexAlgorithms']))
|
||||||
# 'diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1'}
|
conf_options['ssh']['Host']['*']['HostKeyAlgorithms'] = ','.join(
|
||||||
|
(('ssh-ed25519-cert-v01@openssh.com,'
|
||||||
|
'ssh-rsa-cert-v01@openssh.com,'
|
||||||
|
'ssh-ed25519'),
|
||||||
|
conf_options['ssh']['Host']['*']['HostKeyAlgorithms']))
|
||||||
|
|
||||||
|
|
||||||
def hostKeys(buildmoduli):
|
def hostKeys(buildmoduli):
|
||||||
# Starting haveged should help lessen the time load a non-negligible amount, especially on virtual platforms.
|
# Starting haveged should help lessen the time load a non-negligible amount, especially on virtual platforms.
|
||||||
if os.path.lexists('/usr/bin/haveged'):
|
if os.path.lexists('/usr/bin/haveged'):
|
||||||
# We could use psutil here, but then that's a python dependency we don't need.
|
# We could use psutil here, but then that's a python dependency we don't need.
|
||||||
# We could parse the /proc directory, but that's quite unnecessary. pgrep's installed by default on Arch.
|
# We could parse the /proc directory, but that's quite unnecessary. pgrep's installed by default on
|
||||||
|
# most distros.
|
||||||
with open(os.devnull, 'wb') as devnull:
|
with open(os.devnull, 'wb') as devnull:
|
||||||
if subprocess.run(['pgrep', 'haveged'], stdout = devnull).returncode != 0:
|
if subprocess.run(['pgrep', 'haveged'], stdout = devnull).returncode != 0:
|
||||||
subprocess.run(['haveged'], stdout = devnull)
|
subprocess.run(['haveged'], stdout = devnull)
|
||||||
@ -143,11 +177,12 @@ def hostKeys(buildmoduli):
|
|||||||
for suffix in ('', '.pub'):
|
for suffix in ('', '.pub'):
|
||||||
for k in glob.glob('/etc/ssh/ssh_host_*key{0}'.format(suffix)):
|
for k in glob.glob('/etc/ssh/ssh_host_*key{0}'.format(suffix)):
|
||||||
os.rename(k, '{0}.old.{1}'.format(k, int(datetime.datetime.utcnow().timestamp())))
|
os.rename(k, '{0}.old.{1}'.format(k, int(datetime.datetime.utcnow().timestamp())))
|
||||||
subprocess.run(['ssh-keygen', '-t', 'ed25519', '-f', '/etc/ssh/ssh_host_ed25519_key', '-q', '-N', ''])
|
if has_ed25519:
|
||||||
|
subprocess.run(['ssh-keygen', '-t', 'ed25519', '-f', '/etc/ssh/ssh_host_ed25519_key', '-q', '-N', ''])
|
||||||
subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '4096', '-f', '/etc/ssh/ssh_host_rsa_key', '-q', '-N', ''])
|
subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '4096', '-f', '/etc/ssh/ssh_host_rsa_key', '-q', '-N', ''])
|
||||||
# We currently don't use this, but for simplicity's sake let's return the host keys.
|
# We currently don't use this, but for simplicity's sake let's return the host keys.
|
||||||
hostkeys = {}
|
hostkeys = {}
|
||||||
for k in ('ed25519', 'rsa'):
|
for k in supported_keys:
|
||||||
with open('/etc/ssh/ssh_host_{0}_key.pub'.format(k), 'r') as f:
|
with open('/etc/ssh/ssh_host_{0}_key.pub'.format(k), 'r') as f:
|
||||||
hostkeys[k] = f.read()
|
hostkeys[k] = f.read()
|
||||||
return(hostkeys)
|
return(hostkeys)
|
||||||
@ -158,15 +193,19 @@ def config(opts, t):
|
|||||||
special['sshd']['opts'] = ['Match']
|
special['sshd']['opts'] = ['Match']
|
||||||
special['sshd']['filters'] = ['User', 'Group', 'Host', 'LocalAddress', 'LocalPort', 'Address']
|
special['sshd']['filters'] = ['User', 'Group', 'Host', 'LocalAddress', 'LocalPort', 'Address']
|
||||||
# These are arguments supported by each of the special options. We'll use this to verify entries.
|
# These are arguments supported by each of the special options. We'll use this to verify entries.
|
||||||
special['sshd']['args'] = ['AcceptEnv', 'AllowAgentForwarding', 'AllowGroups', 'AllowStreamLocalForwarding', 'AllowTcpForwarding',
|
special['sshd']['args'] = ['AcceptEnv', 'AllowAgentForwarding', 'AllowGroups', 'AllowStreamLocalForwarding',
|
||||||
'AllowUsers', 'AuthenticationMethods', 'AuthorizedKeysCommand', 'AuthorizedKeysCommandUser',
|
'AllowTcpForwarding', 'AllowUsers', 'AuthenticationMethods', 'AuthorizedKeysCommand',
|
||||||
'AuthorizedKeysFile', 'AuthorizedPrincipalsCommand', 'AuthorizedPrincipalsCommandUser', 'AuthorizedPrincipalsFile',
|
'AuthorizedKeysCommandUser', 'AuthorizedKeysFile', 'AuthorizedPrincipalsCommand',
|
||||||
'Banner', 'ChrootDirectory', 'ClientAliveCountMax', 'ClientAliveInterval', 'DenyGroups', 'DenyUsers', 'ForceCommand',
|
'AuthorizedPrincipalsCommandUser', 'AuthorizedPrincipalsFile', 'Banner',
|
||||||
'GatewayPorts', 'GSSAPIAuthentication', 'HostbasedAcceptedKeyTypes', 'HostbasedAuthentication',
|
'ChrootDirectory', 'ClientAliveCountMax', 'ClientAliveInterval', 'DenyGroups',
|
||||||
'HostbasedUsesNameFromPacketOnly', 'IPQoS', 'KbdInteractiveAuthentication', 'KerberosAuthentication', 'MaxAuthTries',
|
'DenyUsers', 'ForceCommand', 'GatewayPorts', 'GSSAPIAuthentication',
|
||||||
'MaxSessions', 'PasswordAuthentication', 'PermitEmptyPasswords', 'PermitOpen', 'PermitRootLogin', 'PermitTTY',
|
'HostbasedAcceptedKeyTypes', 'HostbasedAuthentication',
|
||||||
'PermitTunnel', 'PermitUserRC', 'PubkeyAcceptedKeyTypes', 'PubkeyAuthentication', 'RekeyLimit', 'RevokedKeys',
|
'HostbasedUsesNameFromPacketOnly', 'IPQoS', 'KbdInteractiveAuthentication',
|
||||||
'StreamLocalBindMask', 'StreamLocalBindUnlink', 'TrustedUserCAKeys', 'X11DisplayOffset', 'X11Forwarding', 'X11UseLocalHost']
|
'KerberosAuthentication', 'MaxAuthTries', 'MaxSessions', 'PasswordAuthentication',
|
||||||
|
'PermitEmptyPasswords', 'PermitOpen', 'PermitRootLogin', 'PermitTTY', 'PermitTunnel',
|
||||||
|
'PermitUserRC', 'PubkeyAcceptedKeyTypes', 'PubkeyAuthentication', 'RekeyLimit',
|
||||||
|
'RevokedKeys', 'StreamLocalBindMask', 'StreamLocalBindUnlink', 'TrustedUserCAKeys',
|
||||||
|
'X11DisplayOffset', 'X11Forwarding', 'X11UseLocalHost']
|
||||||
special['ssh']['opts'] = ['Host', 'Match']
|
special['ssh']['opts'] = ['Host', 'Match']
|
||||||
special['ssh']['args'] = ['canonical', 'exec', 'host', 'originalhost', 'user', 'localuser']
|
special['ssh']['args'] = ['canonical', 'exec', 'host', 'originalhost', 'user', 'localuser']
|
||||||
cf = '/etc/ssh/{0}_config'.format(t)
|
cf = '/etc/ssh/{0}_config'.format(t)
|
||||||
@ -181,7 +220,6 @@ def config(opts, t):
|
|||||||
if opt:
|
if opt:
|
||||||
if not re.match('^(#.*|\s+.*)$', opt[0]):
|
if not re.match('^(#.*|\s+.*)$', opt[0]):
|
||||||
confopts.append(opt[0])
|
confopts.append(opt[0])
|
||||||
#print(confopts)
|
|
||||||
# We also need to modify the config file- comment out starting with the first occurrence of the
|
# We also need to modify the config file- comment out starting with the first occurrence of the
|
||||||
# specopts, if it exists. This is why we make a backup.
|
# specopts, if it exists. This is why we make a backup.
|
||||||
commentidx = None
|
commentidx = None
|
||||||
@ -203,16 +241,11 @@ def config(opts, t):
|
|||||||
# Since the config should be explicit, we remove any existing entries specified that we find.
|
# Since the config should be explicit, we remove any existing entries specified that we find.
|
||||||
else:
|
else:
|
||||||
if o in confopts:
|
if o in confopts:
|
||||||
#print('commenting out old {0}'.format(o))
|
|
||||||
# If I was more worried about recursion, or if I was appending here, I should use conf[:].
|
# If I was more worried about recursion, or if I was appending here, I should use conf[:].
|
||||||
# But I'm not. So I won't.
|
# But I'm not. So I won't.
|
||||||
for idx, opt in enumerate(conf):
|
for idx, opt in enumerate(conf):
|
||||||
if re.match('^{0}(\s.*)?\n$'.format(o), opt):
|
if re.match('^{0}(\s.*)?\n$'.format(o), opt):
|
||||||
#l = opt.split()
|
|
||||||
#conf[idx] = '#{0} {1}'.format(l[0].strip, l[1].strip)
|
|
||||||
#print('old {0}: {1}'.format(o, conf[idx]))
|
|
||||||
conf[idx] = '#{0}'.format(opt)
|
conf[idx] = '#{0}'.format(opt)
|
||||||
#print('new {0}: {1}'.format(o, conf[idx]))
|
|
||||||
# Here we handle the "multiple-specifying" options- notably, HostKey.
|
# Here we handle the "multiple-specifying" options- notably, HostKey.
|
||||||
if isinstance(opts[o], list):
|
if isinstance(opts[o], list):
|
||||||
for l in opts[o]:
|
for l in opts[o]:
|
||||||
@ -247,9 +280,11 @@ def clientKeys(user = 'root'):
|
|||||||
homedir = os.path.expanduser('~{0}'.format(user))
|
homedir = os.path.expanduser('~{0}'.format(user))
|
||||||
sshdir = '{0}/.ssh'.format(homedir)
|
sshdir = '{0}/.ssh'.format(homedir)
|
||||||
os.makedirs(sshdir, mode = 0o700, exist_ok = True)
|
os.makedirs(sshdir, mode = 0o700, exist_ok = True)
|
||||||
if not os.path.lexists('{0}/id_ed25519'.format(sshdir)) and not os.path.lexists('{0}/id_ed25519.pub'.format(sshdir)):
|
if has_ed25519:
|
||||||
subprocess.run(['ssh-keygen', '-t', 'ed25519', '-o', '-a', '100',
|
if not os.path.lexists('{0}/id_ed25519'.format(sshdir)) \
|
||||||
'-f', '{0}/id_ed25519'.format(sshdir), '-q', '-N', ''])
|
and not os.path.lexists('{0}/id_ed25519.pub'.format(sshdir)):
|
||||||
|
subprocess.run(['ssh-keygen', '-t', 'ed25519', '-o', '-a', '100',
|
||||||
|
'-f', '{0}/id_ed25519'.format(sshdir), '-q', '-N', ''])
|
||||||
if not os.path.lexists('{0}/id_rsa'.format(sshdir)) and not os.path.lexists('{0}/id_rsa.pub'.format(sshdir)):
|
if not os.path.lexists('{0}/id_rsa'.format(sshdir)) and not os.path.lexists('{0}/id_rsa.pub'.format(sshdir)):
|
||||||
subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '4096', '-o', '-a', '100',
|
subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '4096', '-o', '-a', '100',
|
||||||
'-f', '{0}/id_rsa'.format(sshdir), '-q', '-N', ''])
|
'-f', '{0}/id_rsa'.format(sshdir), '-q', '-N', ''])
|
||||||
@ -262,7 +297,7 @@ def clientKeys(user = 'root'):
|
|||||||
if 'pubkeys' not in globals():
|
if 'pubkeys' not in globals():
|
||||||
pubkeys = {}
|
pubkeys = {}
|
||||||
pubkeys[user] = {}
|
pubkeys[user] = {}
|
||||||
for k in ('ed25519', 'rsa'):
|
for k in supported_keys:
|
||||||
with open('{0}/id_{1}.pub'.format(sshdir, k), 'r') as f:
|
with open('{0}/id_{1}.pub'.format(sshdir, k), 'r') as f:
|
||||||
pubkeys[user][k] = f.read()
|
pubkeys[user][k] = f.read()
|
||||||
return(pubkeys)
|
return(pubkeys)
|
||||||
@ -277,8 +312,8 @@ def main():
|
|||||||
config(conf_options[t], t)
|
config(conf_options[t], t)
|
||||||
clientKeys()
|
clientKeys()
|
||||||
with open(_chkfile, 'w') as f:
|
with open(_chkfile, 'w') as f:
|
||||||
f.write(('ssh, sshd, and hostkey configurations/keys have been ' +
|
f.write(('ssh, sshd, and hostkey configurations/keys have been modified by sshsecure.py from OpTools.\n'
|
||||||
'modified by sshsecure.py from OpTools.\nhttps://git.square-r00t.net/OpTools/\n'))
|
'https://git.square-r00t.net/OpTools/\n'))
|
||||||
return()
|
return()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -105,6 +105,7 @@ class FileExtractor(object):
|
|||||||
return()
|
return()
|
||||||
|
|
||||||
def extractFiles(self):
|
def extractFiles(self):
|
||||||
|
# TODO: globbing or regex on self.paths?
|
||||||
# If we have yum, we can, TECHNICALLY, do this with:
|
# If we have yum, we can, TECHNICALLY, do this with:
|
||||||
# http://yum.baseurl.org/api/yum/rpmUtils/miscutils.html#rpmUtils.miscutils.rpm2cpio
|
# http://yum.baseurl.org/api/yum/rpmUtils/miscutils.html#rpmUtils.miscutils.rpm2cpio
|
||||||
# But nope. We can't selectively decompress members based on path with rpm2cpio-like funcs.
|
# But nope. We can't selectively decompress members based on path with rpm2cpio-like funcs.
|
||||||
|
Loading…
Reference in New Issue
Block a user