External auth support in salt-key.

This commit is contained in:
Dmitry Kuzmenko 2016-07-28 14:30:56 +03:00
parent 0cf7b3fbb9
commit 0589ba8dce
6 changed files with 430 additions and 583 deletions

View File

@ -1191,6 +1191,7 @@ class LocalFuncs(object):
'''
# All wheel ops pass through eauth
if 'token' in load:
atype = 'token'
try:
token = self.loadauth.get_tok(load['token'])
except Exception as exc:
@ -1209,122 +1210,106 @@ class LocalFuncs(object):
log.warning(msg)
return dict(error=dict(name='TokenAuthenticationError',
message=msg))
good = self.ckminions.wheel_check(
self.opts['external_auth'][token['eauth']][token['name']]
if token['name'] in self.opts['external_auth'][token['eauth']]
else self.opts['external_auth'][token['eauth']]['*'],
load['fun'])
if not good:
msg = ('Authentication failure of type "token" occurred for '
'user {0}.').format(token['name'])
log.warning(msg)
return dict(error=dict(name='TokenAuthenticationError',
message=msg))
jid = salt.utils.jid.gen_jid()
fun = load.pop('fun')
tag = tagify(jid, prefix='wheel')
data = {'fun': "wheel.{0}".format(fun),
'jid': jid,
'tag': tag,
'user': token['name']}
username = token['name']
elif 'eauth' in load:
atype = 'eauth'
try:
self.event.fire_event(data, tagify([jid, 'new'], 'wheel'))
ret = self.wheel_.call_func(fun, **load)
data['return'] = ret
data['success'] = True
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
if load['eauth'] not in self.opts['external_auth']:
# The eauth system is not enabled, fail
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
message=msg))
username = self.loadauth.load_name(load)
if not ((username in self.opts['external_auth'][load['eauth']]) |
('*' in self.opts['external_auth'][load['eauth']])):
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
message=msg))
if not self.loadauth.time_auth(load):
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
message=msg))
except Exception as exc:
log.error(exc)
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
data['return'] = 'Exception occurred in wheel {0}: {1}: {2}'.format(
fun,
exc.__class__.__name__,
exc,
)
data['success'] = False
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
if 'eauth' not in load:
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
log.error(
'Exception occurred in the wheel system: {0}'.format(exc)
)
return dict(error=dict(name=exc.__class__.__name__,
args=exc.args,
message=str(exc)))
else:
atype = 'user'
if 'key' not in load:
msg = 'Authentication failure of type "user" occurred'
log.warning(msg)
return dict(error=dict(name='UserAuthenticationError',
message=msg))
if load['eauth'] not in self.opts['external_auth']:
# The eauth system is not enabled, fail
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
key = load.pop('key')
if 'user' in load:
username = load['user']
auth_user = salt.auth.AuthUser(username)
if auth_user.is_sudo:
username = self.opts.get('user', 'root')
else:
username = salt.utils.get_user()
if username not in self.key and \
key != self.key[username] and \
key != self.key['root']:
msg = 'Authentication failure of type "user" occurred for user {0}'.format(username)
log.warning(msg)
return dict(error=dict(name='UserAuthenticationError',
message=msg))
if not atype == 'user':
auth_list = []
if '*' in self.opts['external_auth'][load['eauth']]:
auth_list.extend(self.opts['external_auth'][load['eauth']]['*'])
if username in self.opts['external_auth'][load['eauth']]:
auth_list = self.opts['external_auth'][load['eauth']][username]
good = self.ckminions.wheel_check(auth_list, load['fun'])
if not good:
msg = ('Authentication failure of type "{0}" occurred for '
'user {1}.').format(atype, load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
message=msg))
# Authenticated. Do the job.
jid = salt.utils.jid.gen_jid()
fun = load.pop('fun')
tag = tagify(jid, prefix='wheel')
data = {'fun': "wheel.{0}".format(fun),
'jid': jid,
'tag': tag,
'user': username}
try:
name = self.loadauth.load_name(load)
if not ((name in self.opts['external_auth'][load['eauth']]) |
('*' in self.opts['external_auth'][load['eauth']])):
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
message=msg))
if not self.loadauth.time_auth(load):
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
message=msg))
good = self.ckminions.wheel_check(
self.opts['external_auth'][load['eauth']][name]
if name in self.opts['external_auth'][load['eauth']]
else self.opts['external_auth'][token['eauth']]['*'],
load['fun'])
if not good:
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(load.get('username', 'UNKNOWN'))
log.warning(msg)
return dict(error=dict(name='EauthAuthenticationError',
message=msg))
jid = salt.utils.jid.gen_jid()
fun = load.pop('fun')
tag = tagify(jid, prefix='wheel')
data = {'fun': "wheel.{0}".format(fun),
'jid': jid,
'tag': tag,
'user': load.get('username', 'UNKNOWN')}
try:
self.event.fire_event(data, tagify([jid, 'new'], 'wheel'))
ret = self.wheel_.call_func(fun, **load)
data['return'] = ret
data['success'] = True
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
except Exception as exc:
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
data['return'] = 'Exception occurred in wheel {0}: {1}: {2}'.format(
fun,
exc.__class__.__name__,
exc,
)
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
self.event.fire_event(data, tagify([jid, 'new'], 'wheel'))
ret = self.wheel_.call_func(fun, **load)
data['return'] = ret
data['success'] = True
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
except Exception as exc:
log.error(
'Exception occurred in the wheel system: {0}'.format(exc)
)
return dict(error=dict(name=exc.__class__.__name__,
args=exc.args,
message=str(exc)))
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
data['return'] = 'Exception occurred in wheel {0}: {1}: {2}'.format(
fun,
exc.__class__.__name__,
exc,
)
data['success'] = False
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
def mk_token(self, load):
'''

View File

@ -16,10 +16,11 @@ import hashlib
import logging
# Import salt libs
import salt.crypt
import salt.client
import salt.exceptions
import salt.crypt
import salt.daemons.masterapi
import salt.exceptions
import salt.minion
import salt.utils
import salt.utils.event
import salt.utils.kinds
@ -51,412 +52,193 @@ class KeyCLI(object):
'''
def __init__(self, opts):
self.opts = opts
self.client = salt.wheel.WheelClient(opts)
if self.opts['transport'] in ('zeromq', 'tcp'):
self.key = Key(opts)
self.key = Key
else:
self.key = RaetKey(opts)
self.key = RaetKey
self.auth = None
def list_status(self, status):
'''
Print out the keys under a named status
def _update_opts(self):
# get the key command
for cmd in ('gen_keys',
'gen_signature',
'list',
'list_all',
'print',
'print_all',
'accept',
'accept_all',
'reject',
'reject_all',
'delete',
'delete_all',
'finger',
'finger_all',
'list_all'): # last is default
if self.opts[cmd]:
break
# set match if needed
if not cmd.startswith('gen_'):
if cmd == 'list_all':
self.opts['match'] = 'all'
elif cmd.endswith('_all'):
self.opts['match'] = '*'
else:
self.opts['match'] = self.opts[cmd]
if cmd.startswith('accept'):
self.opts['include_rejected'] = self.opts['include_all'] or self.opts['include_rejected']
self.opts['include_accepted'] = False
elif cmd.startswith('reject'):
self.opts['include_accepted'] = self.opts['include_all'] or self.opts['include_accepted']
self.opts['include_rejected'] = False
elif cmd == 'gen_keys':
self.opts['keydir'] = self.opts['gen_keys_dir']
self.opts['keyname'] = self.opts['gen_keys']
# match is set to opts, now we can forget about *_all commands
self.opts['fun'] = cmd.replace('_all', '')
:param str status: A string indicating which set of keys to return
'''
keys = self.key.list_keys()
if status.startswith('acc'):
salt.output.display_output(
{self.key.ACC: keys[self.key.ACC]},
'key',
self.opts
)
elif status.startswith(('pre', 'un')):
salt.output.display_output(
{self.key.PEND: keys[self.key.PEND]},
'key',
self.opts
)
elif status.startswith('rej'):
salt.output.display_output(
{self.key.REJ: keys[self.key.REJ]},
'key',
self.opts
)
elif status.startswith('den'):
if self.key.DEN:
salt.output.display_output(
{self.key.DEN: keys[self.key.DEN]},
'key',
self.opts
)
elif status.startswith('all'):
self.list_all()
def list_all(self):
'''
Print out all keys
'''
salt.output.display_output(
self.key.list_keys(),
'key',
self.opts)
def accept(self, match, include_rejected=False, include_denied=False):
'''
Accept the keys matched
:param str match: A string to match against. i.e. 'web*'
:param bool include_rejected: Whether or not to accept a matched key that was formerly rejected
:param bool include_denied: Whether or not to accept a matched key that was formerly denied
'''
def _print_accepted(matches, after_match):
if self.key.ACC in after_match:
accepted = sorted(
set(after_match[self.key.ACC]).difference(
set(matches.get(self.key.ACC, []))
)
)
for key in accepted:
print('Key for minion {0} accepted.'.format(key))
matches = self.key.name_match(match)
keys = {}
if self.key.PEND in matches:
keys[self.key.PEND] = matches[self.key.PEND]
if include_rejected and bool(matches.get(self.key.REJ)):
keys[self.key.REJ] = matches[self.key.REJ]
if include_denied and bool(matches.get(self.key.DEN)):
keys[self.key.DEN] = matches[self.key.DEN]
if not keys:
msg = (
'The key glob \'{0}\' does not match any unaccepted{1} keys.'
.format(match, (('', ' or denied'),
(' or rejected', ', rejected or denied')
)[include_rejected][include_denied])
)
print(msg)
raise salt.exceptions.SaltSystemExit(code=1)
if not self.opts.get('yes', False):
print('The following keys are going to be accepted:')
salt.output.display_output(
keys,
'key',
self.opts)
try:
veri = input('Proceed? [n/Y] ')
except KeyboardInterrupt:
raise SystemExit("\nExiting on CTRL-c")
if not veri or veri.lower().startswith('y'):
_print_accepted(
matches,
self.key.accept(
match_dict=keys,
include_rejected=include_rejected,
include_denied=include_denied
)
)
else:
print('The following keys are going to be accepted:')
salt.output.display_output(
keys,
'key',
self.opts)
_print_accepted(
matches,
self.key.accept(
match_dict=keys,
include_rejected=include_rejected,
include_denied=include_denied
)
)
def accept_all(self, include_rejected=False, include_denied=False):
'''
Accept all keys
:param bool include_rejected: Whether or not to accept a matched key that was formerly rejected
:param bool include_denied: Whether or not to accept a matched key that was formerly denied
'''
self.accept('*', include_rejected=include_rejected, include_denied=include_denied)
def delete(self, match):
'''
Delete the matched keys
:param str match: A string to match against. i.e. 'web*'
'''
def _print_deleted(matches, after_match):
deleted = []
for keydir in (self.key.ACC, self.key.PEND, self.key.REJ):
deleted.extend(list(
set(matches.get(keydir, [])).difference(
set(after_match.get(keydir, []))
)
))
for key in sorted(deleted):
print('Key for minion {0} deleted.'.format(key))
matches = self.key.name_match(match)
if not matches:
print(
'The key glob \'{0}\' does not match any accepted, unaccepted '
'or rejected keys.'.format(match)
)
raise salt.exceptions.SaltSystemExit(code=1)
if not self.opts.get('yes', False):
print('The following keys are going to be deleted:')
salt.output.display_output(
matches,
'key',
self.opts)
try:
veri = input('Proceed? [N/y] ')
except KeyboardInterrupt:
raise SystemExit("\nExiting on CTRL-c")
if veri.lower().startswith('y'):
_print_deleted(
matches,
self.key.delete_key(match_dict=matches, revoke_auth=True)
)
else:
print('Deleting the following keys:')
salt.output.display_output(
matches,
'key',
self.opts)
_print_deleted(
matches,
self.key.delete_key(match_dict=matches)
)
def delete_all(self):
'''
Delete all keys
'''
self.delete('*')
def reject(self, match, include_accepted=False, include_denied=False):
'''
Reject the matched keys
:param str match: A string to match against. i.e. 'web*'
:param bool include_accepted: Whether or not to reject a matched key
that was formerly accepted
:param bool include_denied: Whether or not to reject a matched key
that was formerly denied
'''
def _print_rejected(matches, after_match):
if self.key.REJ in after_match:
rejected = sorted(
set(after_match[self.key.REJ]).difference(
set(matches.get(self.key.REJ, []))
)
)
for key in rejected:
print('Key for minion {0} rejected.'.format(key))
matches = self.key.name_match(match)
keys = {}
if self.key.PEND in matches:
keys[self.key.PEND] = matches[self.key.PEND]
if include_accepted and bool(matches.get(self.key.ACC)):
keys[self.key.ACC] = matches[self.key.ACC]
if include_denied and bool(matches.get(self.key.DEN)):
keys[self.key.DEN] = matches[self.key.DEN]
if not keys:
msg = 'The key glob \'{0}\' does not match any {1} keys.'.format(
match,
(('unaccepted', 'unaccepted or denied'),
('accepted or unaccepted', 'accepted, unaccepted or denied')
)[include_accepted][include_denied]
)
print(msg)
def _init_auth(self):
if self.auth:
return
if not self.opts.get('yes', False):
print('The following keys are going to be rejected:')
salt.output.display_output(
keys,
'key',
self.opts)
veri = input('Proceed? [n/Y] ')
if veri.lower().startswith('n'):
return
_print_rejected(
matches,
self.key.reject(
match_dict=matches,
include_accepted=include_accepted,
include_denied=include_denied
)
low = {}
skip_perm_errors = self.opts['eauth'] != ''
if self.opts['eauth']:
if 'token' in self.opts:
try:
with salt.utils.fopen(os.path.join(self.opts['cachedir'], '.root_key'), 'r') as fp_:
low['key'] = fp_.readline()
except IOError:
low['token'] = self.opts['token']
#
# If using eauth and a token hasn't already been loaded into
# low, prompt the user to enter auth credentials
if 'token' not in low and 'key' not in low and self.opts['eauth']:
# This is expensive. Don't do it unless we need to.
resolver = salt.auth.Resolver(self.opts)
res = resolver.cli(self.opts['eauth'])
if self.opts['mktoken'] and res:
tok = resolver.token_cli(
self.opts['eauth'],
res
)
if tok:
low['token'] = tok.get('token', '')
if not res:
log.error('Authentication failed')
return {}
low.update(res)
low['eauth'] = self.opts['eauth']
else:
low['user'] = salt.utils.get_specific_user()
low['key'] = salt.utils.get_master_key(low['user'], self.opts, skip_perm_errors)
self.auth = low
def _run_cmd(self, cmd, args=None):
fun_name = 'key.{0}'.format(cmd)
fun = self.client.functions[fun_name]
if args is None:
argspec = salt.utils.args.get_function_argspec(fun)
args = []
if argspec.args:
for arg in argspec.args:
args.append(self.opts[arg])
args, kwargs = salt.minion.load_args_and_kwargs(
fun,
args,
self.opts,
)
def reject_all(self, include_accepted=False, include_denied=False):
'''
Reject all keys
low = {
'fun': fun_name,
'arg': args,
'kwarg': kwargs,
}
:param bool include_accepted: Whether or not to reject a matched key that was formerly accepted
:param bool include_denied: Whether or not to reject a matched key that was formerly denied
'''
self.reject('*', include_accepted=include_accepted, include_denied=include_denied)
self._init_auth()
low.update(self.auth)
def print_key(self, match):
'''
Print out a single key
# Execute the key request!
ret = self.client.cmd_sync(low)
:param str match: A string to match against. i.e. 'web*'
'''
matches = self.key.key_str(match)
salt.output.display_output(
matches,
'key',
self.opts)
ret = ret['data']['return']
if isinstance(ret, dict) and 'local' in ret:
ret.pop('local', None)
def print_all(self):
'''
Print out all managed keys
'''
self.print_key('*')
return ret
def finger(self, match):
'''
Print out the fingerprints for the matched keys
def _filter_ret(self, cmd, ret):
if cmd.startswith('delete'):
return ret
:param str match: A string to match against. i.e. 'web*'
'''
matches = self.key.finger(match)
salt.output.display_output(
matches,
'key',
self.opts)
keys = {}
if self.key.PEND in ret:
keys[self.key.PEND] = ret[self.key.PEND]
if self.opts['include_accepted'] and bool(ret.get(self.key.ACC)):
keys[self.key.ACC] = ret[self.key.ACC]
if self.opts['include_rejected'] and bool(ret.get(self.key.REJ)):
keys[self.key.REJ] = ret[self.key.REJ]
if self.opts['include_denied'] and bool(ret.get(self.key.DEN)):
keys[self.key.DEN] = ret[self.key.DEN]
return keys
def finger_all(self):
'''
Print out all fingerprints
'''
matches = self.key.finger('*')
salt.output.display_output(
matches,
'key',
self.opts)
def prep_signature(self):
'''
Searches for usable keys to create the
master public-key signature
'''
self.privkey = None
self.pubkey = None
# check given pub-key
if self.opts['pub']:
if not os.path.isfile(self.opts['pub']):
print('Public-key {0} does not exist'.format(self.opts['pub']))
return
self.pubkey = self.opts['pub']
# default to master.pub
def _print_no_match(self, cmd, match):
statuses = ['unaccepted']
if self.opts['include_accepted']:
statuses.append('accepted')
if self.opts['include_rejected']:
statuses.append('rejected')
if self.opts['include_denied']:
statuses.append('denied')
if len(statuses) == 1:
stat_str = statuses[0]
else:
mpub = self.opts['pki_dir'] + '/' + 'master.pub'
if os.path.isfile(mpub):
self.pubkey = mpub
# check given priv-key
if self.opts['priv']:
if not os.path.isfile(self.opts['priv']):
print('Private-key {0} does not exist'.format(self.opts['priv']))
return
self.privkey = self.opts['priv']
# default to master_sign.pem
else:
mpriv = self.opts['pki_dir'] + '/' + 'master_sign.pem'
if os.path.isfile(mpriv):
self.privkey = mpriv
if not self.privkey:
if self.opts['auto_create']:
print('Generating new signing key-pair {0}.* in {1}'
''.format(self.opts['master_sign_key_name'],
self.opts['pki_dir']))
salt.crypt.gen_keys(self.opts['pki_dir'],
self.opts['master_sign_key_name'],
self.opts['keysize'],
self.opts.get('user'))
self.privkey = self.opts['pki_dir'] + '/' + self.opts['master_sign_key_name'] + '.pem'
else:
print('No usable private-key found')
return
if not self.pubkey:
print('No usable public-key found')
return
print('Using public-key {0}'.format(self.pubkey))
print('Using private-key {0}'.format(self.privkey))
if self.opts['signature_path']:
if not os.path.isdir(self.opts['signature_path']):
print('target directory {0} does not exist'
''.format(self.opts['signature_path']))
else:
self.opts['signature_path'] = self.opts['pki_dir']
sign_path = self.opts['signature_path'] + '/' + self.opts['master_pubkey_signature']
self.key.gen_signature(self.privkey,
self.pubkey,
sign_path)
stat_str = '{0} or {1}'.format(', '.join(statuses[:-1]), statuses[-1])
msg = 'The key glob \'{0}\' does not match any {1} keys.'.format(match, stat_str)
print(msg)
def run(self):
'''
Run the logic for saltkey
'''
if self.opts['gen_keys']:
self.key.gen_keys()
return
elif self.opts['gen_signature']:
self.prep_signature()
return
if self.opts['list']:
self.list_status(self.opts['list'])
elif self.opts['list_all']:
self.list_all()
elif self.opts['print']:
self.print_key(self.opts['print'])
elif self.opts['print_all']:
self.print_all()
elif self.opts['accept']:
self.accept(
self.opts['accept'],
include_rejected=self.opts['include_all'] or self.opts['include_rejected'],
include_denied=self.opts['include_denied']
)
elif self.opts['accept_all']:
self.accept_all(
include_rejected=self.opts['include_all'] or self.opts['include_rejected'],
include_denied=self.opts['include_denied']
)
elif self.opts['reject']:
self.reject(
self.opts['reject'],
include_accepted=self.opts['include_all'] or self.opts['include_accepted'],
include_denied=self.opts['include_denied']
)
elif self.opts['reject_all']:
self.reject_all(
include_accepted=self.opts['include_all'] or self.opts['include_accepted'],
include_denied=self.opts['include_denied']
)
elif self.opts['delete']:
self.delete(self.opts['delete'])
elif self.opts['delete_all']:
self.delete_all()
elif self.opts['finger']:
self.finger(self.opts['finger'])
elif self.opts['finger_all']:
self.finger_all()
else:
self.list_all()
self._update_opts()
cmd = self.opts['fun']
veri = None
if cmd in ('accept', 'reject', 'delete'):
ret = self._run_cmd('list_match')
if not isinstance(ret, dict):
salt.output.display_output(ret, 'key', opts=self.opts)
return ret
ret = self._filter_ret(cmd, ret)
if not ret:
self._print_no_match(cmd, self.opts['match'])
return
print('The following keys are going to be {0}ed:'.format(cmd))
salt.output.display_output(ret, 'key', opts=self.opts)
if not self.opts.get('yes', False):
try:
if cmd.startswith('delete'):
veri = input('Proceed? [N/y] ')
if not veri:
veri = 'n'
else:
veri = input('Proceed? [n/Y] ')
if not veri:
veri = 'y'
except KeyboardInterrupt:
raise SystemExit("\nExiting on CTRL-c")
if veri is None or veri.lower().startswith('y'):
ret = self._run_cmd(cmd)
if isinstance(ret, dict):
salt.output.display_output(ret, 'key', opts=self.opts)
else:
salt.output.display_output({'return': ret}, 'key', opts=self.opts)
class MultiKeyCLI(KeyCLI):
@ -692,14 +474,13 @@ class Key(object):
# We have to differentiate between RaetKey._check_minions_directories
# and Zeromq-Keys. Raet-Keys only have three states while ZeroMQ-keys
# havd an additional 'denied' state.
if self.opts['transport'] in ('zeromq', 'tcp'):
key_dirs = self._check_minions_directories()
else:
key_dirs = self._check_minions_directories()
key_dirs = self._check_minions_directories()
ret = {}
for dir_ in key_dirs:
if dir_ is None:
continue
ret[os.path.basename(dir_)] = []
try:
for fn_ in salt.utils.isorted(os.listdir(dir_)):
@ -743,7 +524,7 @@ class Key(object):
if not fn_.startswith('.'):
if os.path.isfile(os.path.join(rej, fn_)):
ret[os.path.basename(rej)].append(fn_)
elif match.startswith('den'):
elif match.startswith('den') and den is not None:
ret[os.path.basename(den)] = []
for fn_ in salt.utils.isorted(os.listdir(den)):
if not fn_.startswith('.'):
@ -1064,7 +845,7 @@ class RaetKey(Key):
accepted = os.path.join(self.opts['pki_dir'], self.ACC)
pre = os.path.join(self.opts['pki_dir'], self.PEND)
rejected = os.path.join(self.opts['pki_dir'], self.REJ)
return accepted, pre, rejected
return accepted, pre, rejected, None
def check_minion_cache(self, preserve_minions=False):
'''
@ -1151,7 +932,7 @@ class RaetKey(Key):
If the key has been accepted return "accepted"
if the key should be rejected, return "rejected"
'''
acc, pre, rej = self._check_minions_directories() # pylint: disable=W0632
acc, pre, rej, _ = self._check_minions_directories() # pylint: disable=W0632
acc_path = os.path.join(acc, minion_id)
pre_path = os.path.join(pre, minion_id)
rej_path = os.path.join(rej, minion_id)

View File

@ -1790,6 +1790,7 @@ class ClearFuncs(object):
Send a master control function back to the wheel system
'''
# All wheel ops pass through eauth
username = None
if 'token' in clear_load:
auth_error = self.process_token(clear_load['token'],
clear_load['fun'],
@ -1798,77 +1799,77 @@ class ClearFuncs(object):
return auth_error
else:
token = self.loadauth.get_tok(clear_load.pop('token'))
jid = salt.utils.jid.gen_jid()
fun = clear_load.pop('fun')
tag = tagify(jid, prefix='wheel')
data = {'fun': "wheel.{0}".format(fun),
'jid': jid,
'tag': tag,
'user': token['name']}
username = token['name']
elif 'eauth' in clear_load:
try:
self.event.fire_event(data, tagify([jid, 'new'], 'wheel'))
ret = self.wheel_.call_func(fun, **clear_load)
data['return'] = ret
data['success'] = True
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
except Exception as exc:
log.error(exc)
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
data['return'] = 'Exception occurred in wheel {0}: {1}: {2}'.format(
fun,
exc.__class__.__name__,
exc,
)
data['success'] = False
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
try:
eauth_error = self.process_eauth(clear_load, 'wheel')
if eauth_error:
return eauth_error
eauth_error = self.process_eauth(clear_load, 'wheel')
if eauth_error:
return eauth_error
# No error occurred, consume the password from the clear_load if
# passed
clear_load.pop('password', None)
jid = salt.utils.jid.gen_jid()
fun = clear_load.pop('fun')
tag = tagify(jid, prefix='wheel')
data = {'fun': "wheel.{0}".format(fun),
'jid': jid,
'tag': tag,
'user': clear_load.pop('username', 'UNKNOWN')}
try:
self.event.fire_event(data, tagify([jid, 'new'], 'wheel'))
ret = self.wheel_.call_func(fun, **clear_load)
data['return'] = ret
data['success'] = True
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
# No error occurred, consume the password from the clear_load if
# passed
clear_load.pop('password', None)
username = clear_load.pop('username', 'UNKNOWN')
except Exception as exc:
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
data['return'] = 'Exception occurred in wheel {0}: {1}: {2}'.format(
fun,
exc.__class__.__name__,
exc,
log.error(
'Exception occurred in the wheel system: {0}'.format(exc)
)
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
return dict(error=dict(name=exc.__class__.__name__,
args=exc.args,
message=str(exc)))
else:
if 'key' not in clear_load:
msg = 'Authentication failure of type "user" occurred'
log.warning(msg)
return dict(error=dict(name='UserAuthenticationError',
message=msg))
key = clear_load.pop('key')
if 'user' in clear_load:
username = clear_load['user']
auth_user = salt.auth.AuthUser(username)
if auth_user.is_sudo:
username = self.opts.get('user', 'root')
else:
username = salt.utils.get_user()
if username not in self.key and \
key != self.key[username] and \
key != self.key['root']:
msg = 'Authentication failure of type "user" occurred for user {0}'.format(username)
log.warning(msg)
return dict(error=dict(name='UserAuthenticationError',
message=msg))
# Authorized. Do the job!
try:
jid = salt.utils.jid.gen_jid()
fun = clear_load.pop('fun')
tag = tagify(jid, prefix='wheel')
data = {'fun': "wheel.{0}".format(fun),
'jid': jid,
'tag': tag,
'user': username}
self.event.fire_event(data, tagify([jid, 'new'], 'wheel'))
ret = self.wheel_.call_func(fun, **clear_load)
data['return'] = ret
data['success'] = True
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
except Exception as exc:
log.error(
'Exception occurred in the wheel system: {0}'.format(exc)
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
data['return'] = 'Exception occurred in wheel {0}: {1}: {2}'.format(
fun,
exc.__class__.__name__,
exc,
)
return dict(error=dict(name=exc.__class__.__name__,
args=exc.args,
message=str(exc)))
data['success'] = False
self.event.fire_event(data, tagify([jid, 'ret'], 'wheel'))
return {'tag': tag,
'data': data}
def mk_token(self, clear_load):
'''

View File

@ -2165,7 +2165,8 @@ class SaltKeyOptionParser(six.with_metaclass(OptionParserMeta,
OutputOptionsMixIn,
RunUserMixin,
HardCrashMixin,
SaltfileMixIn)):
SaltfileMixIn,
EAuthMixIn)):
description = 'Salt key is used to manage Salt authentication keys'
@ -2181,6 +2182,7 @@ class SaltKeyOptionParser(six.with_metaclass(OptionParserMeta,
def _mixin_setup(self):
actions_group = optparse.OptionGroup(self, 'Actions')
actions_group.set_conflict_handler('resolve')
actions_group.add_option(
'-l', '--list',
default='',
@ -2206,7 +2208,7 @@ class SaltKeyOptionParser(six.with_metaclass(OptionParserMeta,
default='',
help='Accept the specified public key (use --include-rejected and '
'--include-denied to match rejected and denied keys in '
'addition to pending keys). Globs are supported.'
'addition to pending keys). Globs are supported.',
)
actions_group.add_option(

View File

@ -64,7 +64,8 @@ class WheelClient(salt.client.mixins.SyncClientMixin,
':' + str(self.opts['ret_port'])
channel = salt.transport.Channel.factory(self.opts,
crypt='clear',
master_uri=master_uri)
master_uri=master_uri,
usage='master_call')
ret = channel.send(load)
if isinstance(ret, collections.Mapping):
if 'error' in ret:

View File

@ -7,22 +7,26 @@ Wheel system wrapper for key system
from __future__ import absolute_import
import os
import hashlib
import logging
# Import salt libs
import salt.key
from salt.key import get_key
import salt.crypt
import salt.utils
__func_alias__ = {
'list_': 'list'
'list_': 'list',
'key_str': 'print',
}
log = logging.getLogger(__name__)
def list_(match):
'''
List all the keys under a named status
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.list_status(match)
@ -30,15 +34,23 @@ def list_all():
'''
List all the keys
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.all_keys()
def list_match(match):
'''
List all the keys based on a glob match
'''
skey = get_key(__opts__)
return skey.name_match(match)
def accept(match, include_rejected=False, include_denied=False):
'''
Accept keys based on a glob match
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.accept(match, include_rejected=include_rejected, include_denied=include_denied)
@ -59,7 +71,7 @@ def accept_dict(match):
],
}
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.accept(match_dict=match)
@ -67,7 +79,7 @@ def delete(match):
'''
Delete keys based on a glob match
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.delete_key(match)
@ -75,7 +87,7 @@ def delete_dict(match):
'''
Delete keys based on a dict of keys
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.delete_key(match_dict=match)
@ -83,7 +95,7 @@ def reject(match, include_accepted=False, include_denied=False):
'''
Reject keys based on a glob match
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.reject(match, include_accepted=include_accepted, include_denied=include_denied)
@ -91,7 +103,7 @@ def reject_dict(match):
'''
Reject keys based on a dict of keys
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.reject(match_dict=match)
@ -99,7 +111,7 @@ def key_str(match):
'''
Return the key strings
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.key_str(match)
@ -107,7 +119,7 @@ def finger(match):
'''
Return the matching key fingerprints
'''
skey = salt.key.Key(__opts__)
skey = get_key(__opts__)
return skey.finger(match)
@ -143,3 +155,68 @@ def gen_accept(id_, keysize=2048, force=False):
with salt.utils.fopen(acc_path, 'w+') as fp_:
fp_.write(ret['pub'])
return ret
def gen_keys(keydir=None, keyname=None, keysize=None, user=None):
'''
Generate minion RSA public keypair
'''
skey = get_key(__opts__)
return skey.gen_keys(keydir, keyname, keysize, user)
def gen_signature(priv, pub, signature_path, auto_create=False, keysize=None):
'''
Generate master public-key-signature
'''
# check given pub-key
if pub:
if not os.path.isfile(pub):
return 'Public-key {0} does not exist'.format(pub)
# default to master.pub
else:
mpub = __opts__['pki_dir'] + '/' + 'master.pub'
if os.path.isfile(mpub):
pub = mpub
# check given priv-key
if priv:
if not os.path.isfile(priv):
return 'Private-key {0} does not exist'.format(priv)
# default to master_sign.pem
else:
mpriv = __opts__['pki_dir'] + '/' + 'master_sign.pem'
if os.path.isfile(mpriv):
priv = mpriv
if not priv:
if auto_create:
log.debug('Generating new signing key-pair {0}.* in {1}'
''.format(__opts__['master_sign_key_name'],
__opts__['pki_dir']))
salt.crypt.gen_keys(__opts__['pki_dir'],
__opts__['master_sign_key_name'],
keysize or __opts__['keysize'],
__opts__.get('user'))
priv = __opts__['pki_dir'] + '/' + __opts__['master_sign_key_name'] + '.pem'
else:
return 'No usable private-key found'
if not pub:
return 'No usable public-key found'
log.debug('Using public-key {0}'.format(pub))
log.debug('Using private-key {0}'.format(priv))
if signature_path:
if not os.path.isdir(signature_path):
log.debug('target directory {0} does not exist'
''.format(signature_path))
else:
signature_path = __opts__['pki_dir']
sign_path = signature_path + '/' + __opts__['master_pubkey_signature']
skey = get_key(__opts__)
return skey.gen_signature(priv, pub, sign_path)