Merge remote-tracking branch 'upstream/2015.2' into develop

This commit is contained in:
C. R. Oldham 2014-12-23 12:06:10 -07:00
commit 719cda1ca6
5 changed files with 209 additions and 11 deletions

View file

@ -188,7 +188,17 @@ class Authorize(object):
'''
Gather and create the authorization data sets
'''
return self.opts['external_auth']
auth_data = self.opts['external_auth']
if 'django' in auth_data and '^model' in auth_data['django']:
auth_from_django = salt.auth.django.retrieve_auth_entries()
auth_data = salt.utils.dictupdate.merge(auth_data, auth_from_django)
#for auth_back in self.opts.get('external_auth_sources', []):
# fstr = '{0}.perms'.format(auth_back)
# if fstr in self.loadauth.auth:
# auth_data.append(getattr(self.loadauth.auth)())
return auth_data
def token(self, adata, load):
'''
@ -265,6 +275,9 @@ class Authorize(object):
Note: this will check that the user has at least one right that will let
him execute "load", this does not deal with conflicting rules
'''
adata = self.auth_data()
good = False
if load.get('token', False):
for sub_auth in self.token(self.auth_data, load):
if sub_auth:

168
salt/auth/django.py Normal file
View file

@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
'''
Provide authentication using Django Web Framework
Django authentication depends on the presence of the django
framework in the PYTHONPATH, the django project's settings.py file being in
the PYTHONPATH and accessible via the DJANGO_SETTINGS_MODULE environment
variable. This can be hard to debug.
django auth can be defined like any other eauth module:
external_auth:
django:
fred:
- .*
- '@runner'
This will authenticate Fred via django and allow him to run any
execution module and all runners.
The details of the django auth can also be located inside the django database. The
relevant entry in the models.py file would look like this:
class SaltExternalAuthModel(models.Model):
user_fk = models.ForeignKey(auth.User)
minion_matcher = models.CharField()
minion_fn = models.CharField()
Then, in the master's config file the external_auth clause should look like
external_auth:
django:
^model: <fully-qualified reference to model class>
When a user attempts to authenticate via Django, Salt will import the package
indicated via the keyword '^model'. That model must have the fields
indicated above, though the model DOES NOT have to be named 'SaltExternalAuthModel'.
:depends: - Django Web Framework
'''
# Import python libs
from __future__ import absolute_import
import logging
# Import salt libs
log = logging.getLogger(__name__)
try:
import django
import django.conf
import django.contrib.auth
HAS_DJANGO = True
except ImportError:
HAS_DJANGO = False
django_auth_class = None
def django_auth_setup():
# Versions 1.7 and later of Django don't pull models until
# they are needed. When using framework facilities outside the
# web application container we need to run django.setup() to
# get the model definitions cached.
if '^model' in __opts__['external_auth']['django']:
django_model_fullname = __opts__['external_auth']['django']['^model']
django_model_name = django_model_fullname.split('.')[-1]
django_module_name = '.'.join(django_model_fullname.split('.')[0:-1])
django_auth_module = __import__(django_module_name, globals(), locals(), 'SaltExternalAuthModel')
django_auth_class_str = 'django_auth_module.{0}'.format(django_model_name)
django_auth_class = eval(django_auth_class_str) # pylint: disable=W0123
if django.VERSION >= (1, 7):
django.setup()
return django_auth_class
def auth(username, password):
'''
Simple Django auth
'''
global django_auth_class
if not django_auth_class:
django_auth_class = django_auth_setup()
user = django.contrib.auth.authenticate(username=username, password=password)
if user is not None:
if user.is_active:
log.debug('Django authentication successful')
auth_dict_from_db = retrieve_auth_entries(username)[username]
if auth_dict_from_db is not None:
__opts__['external_auth']['django'][username] = auth_dict_from_db
return True
else:
log.debug('Django authentication: the password is valid but the account is disabled.')
return False
def retrieve_auth_entries(u=None):
'''
:param django_auth_class: Reference to the django model class for auth
:param u: Username to filter for
:return: Dictionary that can be slotted into the __opts__ structure for eauth that designates the
user and his or her ACL
username minion_or_fn_matcher minion_fn
fred test.ping
fred server1 network.interfaces
fred server1 raid.list
fred server2 .*
guru .*
smartadmin server1 .*
Should result in
fred:
- test.ping
- server1:
- network.interfaces
- raid.list
- server2:
- .*
guru:
- .*
smartadmin:
- server1:
- .*
'''
global django_auth_class
if not django_auth_class:
django_auth_class = django_auth_setup()
if u is None:
db_records = django_auth_class.objects.all()
else:
db_records = django_auth_class.objects.filter(user_fk__username=u)
auth_dict = {}
for a in db_records:
if a.user_fk.username not in auth_dict:
auth_dict[a.user_fk.username] = []
if not a.minion_or_fn_matcher and a.minion_fn:
auth_dict[a.user_fk.username].append(a.minion_fn)
elif a.minion_or_fn_matcher and not a.minion_fn:
auth_dict[a.user_fk.username].append(a.minion_or_fn_matcher)
else:
found = False
for d in auth_dict[a.user_fk.username]:
if isinstance(d, dict):
if a.minion_or_fn_matcher in d.keys():
auth_dict[a.user_fk.username][a.minion_or_fn_matcher].append(a.minion_fn)
found = True
if not found:
auth_dict[a.user_fk.username].append({a.minion_or_fn_matcher: [a.minion_fn]})
log.debug('django auth_dict is {0}'.format(repr(auth_dict)))
return auth_dict

View file

@ -2222,11 +2222,17 @@ class ClearFuncs(object):
# If a group_auth_match is set it means only that we have a user which matches at least one or more
# of the groups defined in the configuration file.
external_auth_in_db = False
for d in self.opts['external_auth'][extra['eauth']]:
if d.startswith('^'):
external_auth_in_db = True
# If neither a catchall, a named membership or a group membership is found, there is no need
# to continue. Simply deny the user access.
if not ((name in self.opts['external_auth'][extra['eauth']]) |
('*' in self.opts['external_auth'][extra['eauth']]) |
group_auth_match):
group_auth_match | external_auth_in_db):
# A group def is defined and the user is a member
#[group for groups in ['external_auth'][extra['eauth']]]):
# Auth successful, but no matching user found in config

View file

@ -18,3 +18,13 @@ def update(dest, upd):
else:
dest[key] = upd[key]
return dest
def merge(d1, d2):
md = {}
for k, v in d1.iteritems():
if k in d2:
md[k] = [v, d2[k]]
else:
md[k] = v
return md

View file

@ -17,6 +17,7 @@ import salt.payload
import salt.utils
from salt.defaults import DEFAULT_TARGET_DELIM
from salt.exceptions import CommandExecutionError
from salt._compat import string_types
HAS_RANGE = False
try:
@ -133,7 +134,7 @@ class CkMinions(object):
'''
Return the minions found by looking via a list
'''
if isinstance(expr, str):
if isinstance(expr, string_types):
expr = [m for m in expr.split(',') if m]
ret = []
for fn_ in os.listdir(os.path.join(self.opts['pki_dir'], self.acc)):
@ -627,7 +628,7 @@ class CkMinions(object):
try:
for fun in funs:
for ind in auth_list:
if isinstance(ind, str):
if isinstance(ind, string_types):
# Allowed for all minions
if self.match_check(ind, fun):
return True
@ -642,7 +643,7 @@ class CkMinions(object):
tgt,
tgt_type):
# Minions are allowed, verify function in allowed list
if isinstance(ind[valid], str):
if isinstance(ind[valid], string_types):
if self.match_check(ind[valid], fun):
return True
elif isinstance(ind[valid], list):
@ -682,7 +683,7 @@ class CkMinions(object):
mod = comps[0]
fun = comps[1]
for ind in auth_list:
if isinstance(ind, str):
if isinstance(ind, string_types):
if ind.startswith('@') and ind[1:] == mod:
return True
if ind == '@wheel':
@ -694,7 +695,7 @@ class CkMinions(object):
continue
valid = next(iter(ind.keys()))
if valid.startswith('@') and valid[1:] == mod:
if isinstance(ind[valid], str):
if isinstance(ind[valid], string_types):
if self.match_check(ind[valid], fun):
return True
elif isinstance(ind[valid], list):
@ -713,7 +714,7 @@ class CkMinions(object):
mod = comps[0]
fun = comps[1]
for ind in auth_list:
if isinstance(ind, str):
if isinstance(ind, string_types):
if ind.startswith('@') and ind[1:] == mod:
return True
if ind == '@runners':
@ -725,7 +726,7 @@ class CkMinions(object):
continue
valid = next(iter(ind.keys()))
if valid.startswith('@') and valid[1:] == mod:
if isinstance(ind[valid], str):
if isinstance(ind[valid], string_types):
if self.match_check(ind[valid], fun):
return True
elif isinstance(ind[valid], list):
@ -747,7 +748,7 @@ class CkMinions(object):
else:
mod = fun
for ind in auth_list:
if isinstance(ind, str):
if isinstance(ind, string_types):
if ind.startswith('@') and ind[1:] == mod:
return True
if ind == '@{0}'.format(form):
@ -759,7 +760,7 @@ class CkMinions(object):
continue
valid = next(iter(ind.keys()))
if valid.startswith('@') and valid[1:] == mod:
if isinstance(ind[valid], str):
if isinstance(ind[valid], string_types):
if self.match_check(ind[valid], fun):
return True
elif isinstance(ind[valid], list):