Merge pull request #32107 from rallytime/merge-develop

[develop] Merge forward from 2016.3 to develop
This commit is contained in:
Nicole Thomas 2016-03-24 11:14:39 -06:00
commit c03e8757e3
13 changed files with 370 additions and 70 deletions

View File

@ -124,14 +124,6 @@ MOCK_MODULES = [
'salt.ext.six.moves.winreg',
'win32security',
'ntsecuritycon',
'jnpr',
'jnpr.junos',
'jnpr.junos.device',
'lxml',
'napalm',
'json',
'hashlib',
'salt.utils.virtualbox',
]
for mod_name in MOCK_MODULES:

View File

@ -392,7 +392,7 @@ def expand_ldap_entries(entries, opts=None):
This function only gets called if auth.ldap.activedirectory = True
'''
bind = _bind_for_search(opts=opts)
acl_tree = {}
acl_tree = []
for user_or_group_dict in entries:
for minion_or_ou, matchers in six.iteritems(user_or_group_dict):
permissions = matchers
@ -407,15 +407,21 @@ def expand_ldap_entries(entries, opts=None):
search_string,
['cn'])
for ldap_match in search_results:
minion_id = ldap_match[1]['cn'][0].lower()
retrieved_minion_ids.append(minion_id)
try:
minion_id = ldap_match[1]['cn'][0].lower()
retrieved_minion_ids.append(minion_id)
except TypeError:
# TypeError here just means that one of the returned
# entries didn't match the format we expected
# from LDAP.
pass
for minion_id in retrieved_minion_ids:
acl_tree[minion_id] = permissions
acl_tree.append({minion_id: permissions})
except ldap.NO_SUCH_OBJECT:
pass
else:
acl_tree[minion_or_ou] = matchers
acl_tree.append({minion_or_ou: matchers})
log.trace('expand_ldap_entries: {0}'.format(acl_tree))
return acl_tree

View File

@ -646,6 +646,46 @@ def avail_sizes(call=None):
'ram': '60 GiB'
}
},
'Dense Storage': {
'd2.xlarge': {
'id': 'd2.xlarge',
'cores': '4',
'disk': '6 TiB (3 x 2 TiB hard disk drives)',
'ram': '30.5 GiB'
},
'd2.2xlarge': {
'id': 'd2.2xlarge',
'cores': '8',
'disk': '12 TiB (6 x 2 TiB hard disk drives)',
'ram': '61 GiB'
},
'd2.4xlarge': {
'id': 'd2.4xlarge',
'cores': '16',
'disk': '24 TiB (12 x 2 TiB hard disk drives)',
'ram': '122 GiB'
},
'd2.8xlarge': {
'id': 'd2.8xlarge',
'cores': '36',
'disk': '24 TiB (24 x 2 TiB hard disk drives)',
'ram': '244 GiB'
},
},
'GPU': {
'g2.2xlarge': {
'id': 'g2.2xlarge',
'cores': '8',
'disk': '60 GiB (1 x 60 GiB SSD)',
'ram': '15 GiB'
},
'g2.8xlarge': {
'id': 'g2.8xlarge',
'cores': '32',
'disk': '240 GiB (2 x 120 GiB SSD)',
'ram': '60 GiB'
},
},
'High I/O': {
'i2.xlarge': {
'id': 'i2.xlarge',

View File

@ -1969,7 +1969,6 @@ class ClearFuncs(object):
# Add any add'l permissions allowed by group membership
if group_auth_match:
auth_list = self.ckminions.fill_auth_list_from_groups(eauth_config, token['groups'], auth_list)
auth_list = self.ckminions.fill_auth_list_from_ou(auth_list, self.opts)
log.trace("Compiled auth_list: {0}".format(auth_list))
@ -2066,7 +2065,8 @@ class ClearFuncs(object):
self.opts['external_auth'][extra['eauth']],
groups,
auth_list)
auth_list = self.ckminions.fill_auth_list_from_ou(auth_list, self.opts)
if extra['eauth'] == 'ldap':
auth_list = self.ckminions.fill_auth_list_from_ou(auth_list, self.opts)
good = self.ckminions.auth_check(
auth_list,
clear_load['fun'],

View File

@ -194,22 +194,12 @@ def build_rule(table='filter', chain=None, command=None, position='', full=None,
rule.append('{0}-o {1}'.format(maybe_add_negation('of'), kwargs['of']))
del kwargs['of']
if 'protocol' in kwargs:
proto = kwargs['protocol']
proto_negation = maybe_add_negation('protocol')
del kwargs['protocol']
elif 'proto' in kwargs:
proto = kwargs['proto']
proto_negation = maybe_add_negation('proto')
del kwargs['proto']
if proto:
if proto.startswith('!') or proto.startswith('not'):
proto = re.sub(bang_not_pat, '', proto)
rule += '! '
rule.append('{0}-p {1}'.format(proto_negation, proto))
proto = True
for proto_arg in ('protocol', 'proto'):
if proto_arg in kwargs:
if not proto:
rule.append('{0}-p {1}'.format(maybe_add_negation(proto_arg), kwargs[proto_arg]))
proto = True
del kwargs[proto_arg]
if 'match' in kwargs:
match_value = kwargs['match']

View File

@ -134,7 +134,7 @@ def list_users(runas=None):
python_shell=False)
# func to get tags from string such as "[admin, monitoring]"
func = lambda string: [tag.strip() for tag in string[1:-1].split(' ')]
func = lambda string: set([x.strip() for x in string[1:-1].split(',')])
return _output_to_dict(res, func)

View File

@ -44,9 +44,10 @@ class daclConstants(object):
# in ntsecuritycon has the extra bits 0x200 enabled.
# Note that you when you set this permission what you'll generally get back is it
# ORed with 0x200 (SI_NO_ACL_PROTECT), which is what ntsecuritycon incorrectly defines.
FILE_ALL_ACCESS = (ntsecuritycon.STANDARD_RIGHTS_REQUIRED | ntsecuritycon.SYNCHRONIZE | 0x1ff)
def __init__(self):
self.FILE_ALL_ACCESS = (ntsecuritycon.STANDARD_RIGHTS_REQUIRED | ntsecuritycon.SYNCHRONIZE | 0x1ff)
self.hkeys_security = {
'HKEY_LOCAL_MACHINE': 'MACHINE',
'HKEY_USERS': 'USERS',
@ -88,7 +89,7 @@ class daclConstants(object):
ntsecuritycon.DELETE,
'TEXT': 'modify'},
'FULLCONTROL': {
'BITS': daclConstants.FILE_ALL_ACCESS,
'BITS': self.FILE_ALL_ACCESS,
'TEXT': 'full control'}
}
}
@ -368,7 +369,7 @@ def add_ace(path, objectType, user, permission, acetype, propagation):
path: path to the object (i.e. c:\\temp\\file, HKEY_LOCAL_MACHINE\\SOFTWARE\\KEY, etc)
user: user to add
permission: permissions for the user
acetypes: either allow/deny for each user/permission (ALLOW, DENY)
acetype: either allow/deny for each user/permission (ALLOW, DENY)
propagation: how the ACE applies to children for Registry Keys and Directories(KEY, KEY&SUBKEYS, SUBKEYS)
CLI Example:

View File

@ -87,6 +87,30 @@ def _format_comments(comments):
return ret
def _map_port_from_yaml_to_docker(port):
'''
docker-py interface is not very nice:
While for ``port_bindings`` they support:
.. code-block:: python
'8888/tcp'
For ``ports``, it has to be transformed into:
.. code-block:: python
(8888, 'tcp')
'''
if isinstance(port, six.string_types):
port, sep, protocol = port.partition('/')
if protocol:
return int(port), protocol
return int(port)
return port
def _prep_input(kwargs):
'''
Repack (if necessary) data that should be in a dict but is easier to
@ -197,7 +221,7 @@ def _compare(actual, create_kwargs, defaults_from_image):
for port_def in data:
if isinstance(port_def, six.integer_types):
port_def = str(port_def)
if isinstance(port_def, tuple):
if isinstance(port_def, (tuple, list)):
desired_ports.append('{0}/{1}'.format(*port_def))
elif '/' not in port_def:
desired_ports.append('{0}/tcp'.format(port_def))
@ -1569,7 +1593,8 @@ def running(name,
if create_kwargs.get('port_bindings') is not None:
# Be smart and try to provide `ports` argument derived from
# the "port_bindings" configuration.
auto_ports = list(create_kwargs['port_bindings'])
auto_ports = [_map_port_from_yaml_to_docker(port)
for port in create_kwargs['port_bindings']]
actual_ports = create_kwargs.setdefault('ports', [])
actual_ports.extend([p for p in auto_ports if
p not in actual_ports])

View File

@ -73,21 +73,14 @@ def _check_perms_changes(name, newperms, runas=None, existing=None):
return perm_need_change
def _check_tags_changes(name, new_tags, runas=None):
def _get_current_tags(name, runas=None):
'''
Whether Rabbitmq user's tags need to be changed
'''
if new_tags:
if isinstance(new_tags, str):
new_tags = new_tags.split()
try:
old_tags = __salt__['rabbitmq.list_users'](runas=runas)[name]
users = set(old_tags) - set(new_tags)
except CommandExecutionError as err:
log.error('Error: {0}'.format(err))
return []
return list(users)
else:
try:
return list(__salt__['rabbitmq.list_users'](runas=runas)[name])
except CommandExecutionError as err:
log.error('Error: {0}'.format(err))
return []
@ -180,17 +173,22 @@ def present(name,
{'old': 'Removed password.',
'new': ''}})
new_tags = _check_tags_changes(name, tags, runas=runas)
if new_tags:
if not __opts__['test']:
try:
__salt__['rabbitmq.set_user_tags'](name, tags, runas=runas)
except CommandExecutionError as err:
ret['comment'] = 'Error: {0}'.format(err)
return ret
ret['changes'].update({'tags':
{'old': tags,
'new': list(new_tags)}})
if tags is not None:
current_tags = _get_current_tags(name, runas=runas)
if isinstance(tags, str):
tags = tags.split()
# Diff the tags sets. Symmetric difference operator ^ will give us
# any element in one set, but not both
if set(tags) ^ set(current_tags):
if not __opts__['test']:
try:
__salt__['rabbitmq.set_user_tags'](name, tags, runas=runas)
except CommandExecutionError as err:
ret['comment'] = 'Error: {0}'.format(err)
return ret
ret['changes'].update({'tags':
{'old': current_tags,
'new': tags}})
try:
existing_perms = __salt__['rabbitmq.list_user_permissions'](name, runas=runas)
except CommandExecutionError as err:

View File

@ -593,7 +593,7 @@ def query(url,
else:
text = True
if decode_out and os.path.exists(decode_out):
if decode_out:
with salt.utils.fopen(decode_out, 'w') as dof:
dof.write(result_text)

View File

@ -698,6 +698,192 @@ class CkMinions(object):
fun,
form)
def auth_check_expanded(self,
auth_list,
funs,
args,
tgt,
tgt_type='glob',
groups=None,
publish_validate=False):
# Here's my thinking
# 1. Retrieve anticipated targeted minions
# 2. Iterate through each entry in the auth_list
# 3. If it is a minion_id, check to see if any targeted minions match.
# If there is a match, check to make sure funs are permitted
# (if it's not a match we don't care about this auth entry and can
# move on)
# a. If funs are permitted, Add this minion_id to a new set of allowed minion_ids
# If funs are NOT permitted, can short-circuit and return FALSE
# b. At the end of the auth_list loop, make sure all targeted IDs
# are in the set of allowed minion_ids. If not, return FALSE
# 4. If it is a target (glob, pillar, etc), retrieve matching minions
# and make sure that ALL targeted minions are in the set.
# then check to see if the funs are permitted
# a. If ALL targeted minions are not in the set, then return FALSE
# b. If the desired fun doesn't mass the auth check with any
# auth_entry's fun, then return FALSE
# NOTE we are not going to try to allow functions to run on partial
# sets of minions. If a user targets a group of minions and does not
# have access to run a job on ALL of these minions then the job will
# fail with 'Eauth Failed'.
# The recommended workflow in that case will be for the user to narrow
# his target.
# This should cover adding the AD LDAP lookup functionality while
# preserving the existing auth behavior.
# Recommend we config-get this behind an entry called
# auth.enable_expanded_auth_matching
# and default to False
v_tgt_type = tgt_type
if tgt_type.lower() in ('pillar', 'pillar_pcre'):
v_tgt_type = 'pillar_exact'
elif tgt_type.lower() == 'compound':
v_tgt_type = 'compound_pillar_exact'
v_minions = set(self.check_minions(tgt, v_tgt_type))
minions = set(self.check_minions(tgt, tgt_type))
mismatch = bool(minions.difference(v_minions))
# If the non-exact match gets more minions than the exact match
# then pillar globbing or PCRE is being used, and we have a
# problem
if publish_validate:
if mismatch:
return False
# compound commands will come in a list so treat everything as a list
if not isinstance(funs, list):
funs = [funs]
args = [args]
# Take the auth list and get all the minion names inside it
allowed_minions_from_auth_list = set()
auth_dictionary = {}
# Make a set, so we are guaranteed to have only one of each minion
# Also iterate through the entire auth_list and create a dictionary
# so it's easy to look up what functions are permitted
for auth_list_entry in auth_list:
if isinstance(auth_list_entry, six.string_types):
for fun in funs:
# represents toplevel auth entry is a function.
# so this fn is permitted by all minions
if self.match_check(auth_list_entry, fun):
return True
if isinstance(auth_list_entry, dict):
if len(auth_list_entry) != 1:
log.info('Malformed ACL: {0}'.format(auth_list_entry))
continue
allowed_minions_from_auth_list.update(set(auth_list_entry.keys()))
for key in auth_list_entry.keys():
if key in auth_dictionary:
auth_dictionary[key].concat(auth_list_entry[key])
else:
auth_dictionary[key] = auth_list_entry[key]
# 'minions' here are all the names of minions matched by the target
# if we take out all the allowed minions, and there are any left, then
# the target includes minions that are not allowed by eauth
# so we can give up here.
if len(minions - allowed_minions_from_auth_list) > 0:
return False
try:
log.trace('######################################')
log.trace(funs)
for minion in minions:
results = []
for num, fun in enumerate(auth_dictionary[minion]):
results.append(self.match_check(fun, funs))
if not any(results):
return False
return True
# for ind in auth_list:
# if isinstance(ind, six.string_types):
# # Allowed for all minions
# if self.match_check(ind, fun):
# return True
# elif isinstance(ind, dict):
# if len(ind) != 1:
# # Invalid argument
# continue
# valid = next(six.iterkeys(ind))
# # Check if minions are allowed
# # if self.validate_tgt(
# # valid,
# # tgt,
# # tgt_type):
# # Minions are allowed, verify function in allowed list
# if isinstance(ind[valid], six.string_types):
# if self.match_check(ind[valid], fun):
# return True
# elif isinstance(ind[valid], list):
# for cond in ind[valid]:
# # Function name match
# if isinstance(cond, six.string_types):
# if self.match_check(cond, fun):
# return True
# # Function and args match
# elif isinstance(cond, dict):
# if len(cond) != 1:
# # Invalid argument
# continue
# fcond = next(six.iterkeys(cond))
# # cond: {
# # 'mod.func': {
# # 'args': [
# # 'one.*', 'two\\|three'],
# # 'kwargs': {
# # 'functioin': 'teach\\|feed',
# # 'user': 'mother\\|father'
# # }
# # }
# # }
# if self.match_check(fcond, fun): # check key that is function name match
# acond = cond[fcond]
# if not isinstance(acond, dict):
# # Invalid argument
# continue
# # whitelist args, kwargs
# arg_list = args[num]
# cond_args = acond.get('args', [])
# good = True
# for i, cond_arg in enumerate(cond_args):
# if len(arg_list) <= i:
# good = False
# break
# if cond_arg is None: # None == '.*' i.e. allow any
# continue
# if not self.match_check(cond_arg, arg_list[i]):
# good = False
# break
# if not good:
# continue
# # Check kwargs
# cond_kwargs = acond.get('kwargs', {})
# arg_kwargs = {}
# for a in arg_list:
# if isinstance(a, dict) and '__kwarg__' in a:
# arg_kwargs = a
# break
# for k, v in six.iteritems(cond_kwargs):
# if k not in arg_kwargs:
# good = False
# break
# if v is None: # None == '.*' i.e. allow any
# continue
# if not self.match_check(v, arg_kwargs[k]):
# good = False
# break
# if good:
# return True
except TypeError:
return False
return False
def auth_check(self,
auth_list,
funs,
@ -743,9 +929,9 @@ class CkMinions(object):
valid = next(six.iterkeys(ind))
# Check if minions are allowed
if self.validate_tgt(
valid,
tgt,
tgt_type):
valid,
tgt,
tgt_type):
# Minions are allowed, verify function in allowed list
if isinstance(ind[valid], six.string_types):
if self.match_check(ind[valid], fun):
@ -772,7 +958,8 @@ class CkMinions(object):
# }
# }
# }
if self.match_check(fcond, fun): # check key that is function name match
if self.match_check(fcond,
fun): # check key that is function name match
acond = cond[fcond]
if not isinstance(acond, dict):
# Invalid argument
@ -787,7 +974,8 @@ class CkMinions(object):
break
if cond_arg is None: # None == '.*' i.e. allow any
continue
if not self.match_check(cond_arg, arg_list[i]):
if not self.match_check(cond_arg,
arg_list[i]):
good = False
break
if not good:
@ -796,7 +984,8 @@ class CkMinions(object):
cond_kwargs = acond.get('kwargs', {})
arg_kwargs = {}
for a in arg_list:
if isinstance(a, dict) and '__kwarg__' in a:
if isinstance(a,
dict) and '__kwarg__' in a:
arg_kwargs = a
break
for k, v in six.iteritems(cond_kwargs):
@ -805,7 +994,8 @@ class CkMinions(object):
break
if v is None: # None == '.*' i.e. allow any
continue
if not self.match_check(v, arg_kwargs[k]):
if not self.match_check(v,
arg_kwargs[k]):
good = False
break
if good:

View File

@ -40,7 +40,7 @@ class RabbitmqTestCase(TestCase):
'''
mock_run = MagicMock(return_value='Listing users ...\nguest\t[administrator]\n')
with patch.dict(rabbitmq.__salt__, {'cmd.run': mock_run}):
self.assertDictEqual(rabbitmq.list_users(), {'guest': ['administrator']})
self.assertDictEqual(rabbitmq.list_users(), {'guest': set(['administrator'])})
# 'list_users_with_warning' function tests: 1
@ -55,7 +55,7 @@ class RabbitmqTestCase(TestCase):
])
mock_run = MagicMock(return_value=rtn_val)
with patch.dict(rabbitmq.__salt__, {'cmd.run': mock_run}):
self.assertDictEqual(rabbitmq.list_users(), {'guest': ['administrator']})
self.assertDictEqual(rabbitmq.list_users(), {'guest': set(['administrator'])})
# 'list_vhosts' function tests: 1

View File

@ -200,6 +200,64 @@ class DockerngTestCase(TestCase):
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_with_udp_bindings(self):
'''
Check that `ports` contains ports defined from `port_bindings` with
protocol declaration passed as tuple. As stated by docker-py
documentation
https://docker-py.readthedocs.org/en/latest/port-bindings/
In sls:
.. code-block:: yaml
container:
dockerng.running:
- port_bindings:
- '9090:9797/udp'
is equivalent of:
.. code-block:: yaml
container:
dockerng.running:
- ports:
- 9797/udp
- port_bindings:
- '9090:9797/udp'
'''
dockerng_create = Mock()
dockerng_start = Mock()
dockerng_inspect_image = Mock(return_value={
'Id': 'abcd',
'Config': {'ExposedPorts': {}}
})
__salt__ = {'dockerng.list_containers': MagicMock(),
'dockerng.list_tags': MagicMock(),
'dockerng.pull': MagicMock(),
'dockerng.state': MagicMock(),
'dockerng.inspect_image': dockerng_inspect_image,
'dockerng.create': dockerng_create,
'dockerng.start': dockerng_start,
}
with patch.dict(dockerng_state.__dict__,
{'__salt__': __salt__}):
dockerng_state.running(
'cont',
image='image:latest',
port_bindings=['9090:9797/udp'])
dockerng_create.assert_called_with(
'image:latest',
validate_input=False,
name='cont',
ports=[(9797, 'udp')],
port_bindings={'9797/udp': [9090]},
validate_ip_addrs=False,
client_timeout=60)
dockerng_start.assert_called_with('cont')
def test_running_compare_images_by_id(self):
'''
Make sure the container is running