mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 17:09:03 +00:00
salt.modules.ssh: big changes to clean things up
- Move all open(fname, ...).read()/.readlines() over to use try/except inside a with statement properly and raise a CommandExecutionError otherwise - Add __virtual__() so this module is never ran on windows - Get rid of the gratuitous use of for ... open(...).readlines(): since file-like objects are iterable and doing that natively uses a lot less memory. This helps with some of the "leaks" - Fix the docstring in host_keys() and raise SaltInvocationError() on any non-Linux host and the user doesn't specify the key dir. - Add various TODO items for things like dead code Overall... This module still needs a lot more love and some testing
This commit is contained in:
parent
2c5ac88cfc
commit
18d24ea907
@ -3,8 +3,23 @@ Manage client ssh components
|
||||
'''
|
||||
import os
|
||||
import re
|
||||
import binascii
|
||||
import hashlib
|
||||
import binascii
|
||||
import logging
|
||||
|
||||
import salt.utils
|
||||
from salt.exceptions import (
|
||||
SaltInvocationError,
|
||||
CommandExecutionError,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def __virtual__():
|
||||
# TODO: This could work on windows with some love
|
||||
if __grains__['os'] == 'Windows':
|
||||
return False
|
||||
return 'ssh'
|
||||
|
||||
|
||||
def _refine_enc(enc):
|
||||
@ -66,24 +81,35 @@ def _replace_auth_key(
|
||||
lines = []
|
||||
uinfo = __salt__['user.info'](user)
|
||||
full = os.path.join(uinfo['home'], config)
|
||||
for line in open(full, 'r').readlines():
|
||||
if line.startswith('#'):
|
||||
# Commented Line
|
||||
lines.append(line)
|
||||
continue
|
||||
comps = line.split()
|
||||
if len(comps) < 2:
|
||||
# Not a valid line
|
||||
lines.append(line)
|
||||
continue
|
||||
key_ind = 1
|
||||
if comps[0][:4:] not in ['ssh-', 'ecds']:
|
||||
key_ind = 2
|
||||
if comps[key_ind] == key:
|
||||
lines.append(auth_line)
|
||||
else:
|
||||
lines.append(line)
|
||||
open(full, 'w+').writelines(lines)
|
||||
|
||||
try:
|
||||
# open the file for both reading AND writing
|
||||
with open(full, 'r') as _fh:
|
||||
for line in _fh:
|
||||
if line.startswith('#'):
|
||||
# Commented Line
|
||||
lines.append(line)
|
||||
continue
|
||||
comps = line.split()
|
||||
if len(comps) < 2:
|
||||
# Not a valid line
|
||||
lines.append(line)
|
||||
continue
|
||||
key_ind = 1
|
||||
if comps[0][:4:] not in ['ssh-', 'ecds']:
|
||||
key_ind = 2
|
||||
if comps[key_ind] == key:
|
||||
lines.append(auth_line)
|
||||
else:
|
||||
lines.append(line)
|
||||
_fh.close()
|
||||
# Re-open the file writable after properly closing it
|
||||
with open(full, 'w') as _fh:
|
||||
# Write out any changes
|
||||
_fh.writelines(lines)
|
||||
except (IOError, OSError) as exc:
|
||||
msg = 'Problem reading or writing to key file: {0}'
|
||||
raise CommandExecutionError(msg.format(str(exc)))
|
||||
|
||||
|
||||
def _validate_keys(key_file):
|
||||
@ -92,44 +118,47 @@ def _validate_keys(key_file):
|
||||
'''
|
||||
ret = {}
|
||||
linere = re.compile(r'^(.*?)\s?((?:ssh\-|ecds).+)$')
|
||||
|
||||
try:
|
||||
for line in open(key_file, 'r').readlines():
|
||||
if line.startswith('#'):
|
||||
# Commented Line
|
||||
continue
|
||||
with open(key_file, 'r') as _fh:
|
||||
for line in _fh:
|
||||
if line.startswith('#'):
|
||||
# Commented Line
|
||||
continue
|
||||
|
||||
# get "{options} key"
|
||||
ln = re.search(linere, line)
|
||||
if not ln:
|
||||
# not an auth ssh key, perhaps a blank line
|
||||
continue
|
||||
# get "{options} key"
|
||||
ln = re.search(linere, line)
|
||||
if not ln:
|
||||
# not an auth ssh key, perhaps a blank line
|
||||
continue
|
||||
|
||||
opts = ln.group(1)
|
||||
comps = ln.group(2).split()
|
||||
opts = ln.group(1)
|
||||
comps = ln.group(2).split()
|
||||
|
||||
if len(comps) < 2:
|
||||
# Not a valid line
|
||||
continue
|
||||
if len(comps) < 2:
|
||||
# Not a valid line
|
||||
continue
|
||||
|
||||
if opts:
|
||||
# It has options, grab them
|
||||
options = opts.split(',')
|
||||
else:
|
||||
options = []
|
||||
if opts:
|
||||
# It has options, grab them
|
||||
options = opts.split(',')
|
||||
else:
|
||||
options = []
|
||||
|
||||
enc = comps[0]
|
||||
key = comps[1]
|
||||
comment = ' '.join(comps[2:])
|
||||
fingerprint = _fingerprint(key)
|
||||
if fingerprint is None:
|
||||
continue
|
||||
enc = comps[0]
|
||||
key = comps[1]
|
||||
comment = ' '.join(comps[2:])
|
||||
fingerprint = _fingerprint(key)
|
||||
if fingerprint is None:
|
||||
continue
|
||||
|
||||
ret[key] = {'enc': enc,
|
||||
'comment': comment,
|
||||
'options': options,
|
||||
'fingerprint': fingerprint}
|
||||
except IOError:
|
||||
return {}
|
||||
ret[key] = {'enc': enc,
|
||||
'comment': comment,
|
||||
'options': options,
|
||||
'fingerprint': fingerprint}
|
||||
except (IOError, OSError) as exc:
|
||||
msg = 'Problem reading ssh key file {0}'
|
||||
raise CommandExecutionError(msg.format(key_file))
|
||||
|
||||
return ret
|
||||
|
||||
@ -160,11 +189,14 @@ def host_keys(keydir=None):
|
||||
|
||||
salt '*' ssh.host_keys
|
||||
'''
|
||||
# Set up the default keydir - needs to support sshd_config parsing in the
|
||||
# future
|
||||
# TODO: support parsing sshd_config for the key directory
|
||||
if not keydir:
|
||||
if __grains__['kernel'] == 'Linux':
|
||||
keydir = '/etc/ssh'
|
||||
else:
|
||||
# If keydir is None, os.listdir() will blow up
|
||||
msg = 'ssh.host_keys: Please specify a keydir'
|
||||
raise SaltInvocationError(msg)
|
||||
keys = {}
|
||||
for fn_ in os.listdir(keydir):
|
||||
if fn_.startswith('ssh_host_'):
|
||||
@ -174,7 +206,8 @@ def host_keys(keydir=None):
|
||||
if len(top) > 1:
|
||||
kname += '.{0}'.format(top[1])
|
||||
try:
|
||||
keys[kname] = open(os.path.join(keydir, fn_), 'r').read()
|
||||
with open(os.path.join(keydir, fn_), 'r') as _fh:
|
||||
keys[kname] = _fh.readline().strip()
|
||||
except (IOError, OSError):
|
||||
keys[kname] = ''
|
||||
return keys
|
||||
@ -223,7 +256,7 @@ def check_key(user, key, enc, comment, options, config='.ssh/authorized_keys'):
|
||||
|
||||
CLI Example::
|
||||
|
||||
salt '*' ssh.check_key <user> <key>
|
||||
salt '*' ssh.check_key <user> <key> <enc> <comment> <options>
|
||||
'''
|
||||
current = auth_keys(user, config)
|
||||
nline = _format_auth_line(key, enc, comment, options)
|
||||
@ -254,36 +287,53 @@ def rm_auth_key(user, key, config='.ssh/authorized_keys'):
|
||||
# Remove the key
|
||||
uinfo = __salt__['user.info'](user)
|
||||
full = os.path.join(uinfo['home'], config)
|
||||
|
||||
# Return something sensible if the file doesn't exist
|
||||
if not os.path.isfile(full):
|
||||
return 'User authorized keys file not present'
|
||||
return 'Authorized keys file {1} not present'.format(full)
|
||||
|
||||
lines = []
|
||||
for line in open(full, 'r').readlines():
|
||||
if line.startswith('#'):
|
||||
# Commented Line
|
||||
lines.append(line)
|
||||
continue
|
||||
try:
|
||||
# Read every line in the file to find the right ssh key
|
||||
# and then write out the correct one. Open the file once
|
||||
with open(full, 'r') as _fh:
|
||||
for line in _fh:
|
||||
if line.startswith('#'):
|
||||
# Commented Line
|
||||
lines.append(line)
|
||||
continue
|
||||
|
||||
# get "{options} key"
|
||||
ln = re.search(linere, line)
|
||||
if not ln:
|
||||
# not an auth ssh key, perhaps a blank line
|
||||
continue
|
||||
# get "{options} key"
|
||||
ln = re.search(linere, line)
|
||||
if not ln:
|
||||
# not an auth ssh key, perhaps a blank line
|
||||
continue
|
||||
|
||||
comps = ln.group(2).split()
|
||||
comps = ln.group(2).split()
|
||||
|
||||
if len(comps) < 2:
|
||||
# Not a valid line
|
||||
lines.append(line)
|
||||
continue
|
||||
if len(comps) < 2:
|
||||
# Not a valid line
|
||||
lines.append(line)
|
||||
continue
|
||||
|
||||
pkey = comps[1]
|
||||
pkey = comps[1]
|
||||
|
||||
if pkey == key:
|
||||
continue
|
||||
else:
|
||||
lines.append(line)
|
||||
open(full, 'w+').writelines(lines)
|
||||
# This is the key we are "deleting", so don't put
|
||||
# it in the list of keys to be re-added back
|
||||
if pkey == key:
|
||||
continue
|
||||
|
||||
lines.append(line)
|
||||
|
||||
# Let the context manager do the right thing here and then
|
||||
# re-open the file in write mode to save the changes out.
|
||||
with open(full, 'w') as _fh:
|
||||
_fh.writelines(lines)
|
||||
except (IOError, OSError) as exc:
|
||||
log.warn('Could not read/write key file: {0}'.format(str(exc)))
|
||||
return 'Key not removed'
|
||||
return 'Key removed'
|
||||
# TODO: Should this function return a simple boolean?
|
||||
return 'Key not present'
|
||||
|
||||
def set_auth_key_from_file(
|
||||
@ -302,7 +352,8 @@ def set_auth_key_from_file(
|
||||
# TODO: add support for pulling keys from other file sources as well
|
||||
lfile = __salt__['cp.cache_file'](source, env)
|
||||
if not os.path.isfile(lfile):
|
||||
return 'fail'
|
||||
msg = 'Failed to pull key file from salt file server'
|
||||
raise CommandExecutionError(msg)
|
||||
|
||||
newkey = {}
|
||||
rval = ''
|
||||
@ -380,12 +431,21 @@ def set_auth_key(
|
||||
os.chmod(dpath, 448)
|
||||
|
||||
if not os.path.isfile(fconfig):
|
||||
open(fconfig, 'a+').write('{0}'.format(auth_line))
|
||||
new_file = True
|
||||
else:
|
||||
new_file = False
|
||||
|
||||
try:
|
||||
with open(fconfig, 'a+') as _fh:
|
||||
_fh.write('{0}'.format(auth_line))
|
||||
except (IOError, OSError) as exc:
|
||||
msg = 'Could not write to key file: {0}'
|
||||
raise CommandExecutionError(msg.format(str(exc)))
|
||||
|
||||
if new_file:
|
||||
if os.geteuid() == 0:
|
||||
os.chown(fconfig, uinfo['uid'], uinfo['gid'])
|
||||
os.chmod(fconfig, 384)
|
||||
else:
|
||||
open(fconfig, 'a+').write('{0}'.format(auth_line))
|
||||
return 'new'
|
||||
|
||||
|
||||
@ -429,7 +489,7 @@ def get_known_host(user, hostname, config='.ssh/known_hosts'):
|
||||
|
||||
def recv_known_host(user, hostname, enc=None, port=None, hash_hostname=False):
|
||||
'''
|
||||
Retreive information about host public key from remote server
|
||||
Retrieve information about host public key from remote server
|
||||
|
||||
CLI Example::
|
||||
|
||||
@ -543,12 +603,18 @@ def set_known_host(user, hostname,
|
||||
uinfo = __salt__['user.info'](user)
|
||||
full = os.path.join(uinfo['home'], config)
|
||||
line = '{hostname} {enc} {key}\n'.format(**remote_host)
|
||||
with open(full, 'a') as fd:
|
||||
fd.write(line)
|
||||
|
||||
try:
|
||||
with open(full, 'a') as fd:
|
||||
fd.write(line)
|
||||
except (IOError, OSError) as exc:
|
||||
raise CommandExecutionError("Couldn't append to known hosts file")
|
||||
|
||||
if os.geteuid() == 0:
|
||||
os.chown(full, uinfo['uid'], uinfo['gid'])
|
||||
return {'status': 'updated', 'old': stored_host, 'new': remote_host}
|
||||
|
||||
# TODO: The lines below this are dead code, fix the above return and make these work
|
||||
status = check_known_host(user, hostname, fingerprint=fingerprint,
|
||||
config=config)
|
||||
if status == 'exists':
|
||||
|
Loading…
Reference in New Issue
Block a user