mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge remote-tracking branch 'upstream/2015.2' into develop
This commit is contained in:
commit
719cda1ca6
5 changed files with 209 additions and 11 deletions
|
@ -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
168
salt/auth/django.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Add table
Reference in a new issue