Merge branch '2017.7' into 2017.7

This commit is contained in:
Nicole Thomas 2017-10-13 11:15:49 -04:00 committed by GitHub
commit 3d597db51c
38 changed files with 266 additions and 90 deletions

View file

@ -40,7 +40,7 @@ Set up an initial profile at /etc/salt/cloud.profiles or in the /etc/salt/cloud.
.. code-block:: yaml
scalewa-ubuntu:
scaleway-ubuntu:
provider: my-scaleway-config
image: Ubuntu Trusty (14.04 LTS)

View file

@ -218,6 +218,7 @@ Server configuration values and their defaults:
# Bind to LDAP anonymously to determine group membership
# Active Directory does not allow anonymous binds without special configuration
# In addition, if auth.ldap.anonymous is True, empty bind passwords are not permitted.
auth.ldap.anonymous: False
# FOR TESTING ONLY, this is a VERY insecure setting.
@ -257,7 +258,11 @@ and groups, it re-authenticates as the user running the Salt commands.
If you are already aware of the structure of your DNs and permissions in your LDAP store are set such that
users can look up their own group memberships, then the first and second users can be the same. To tell Salt this is
the case, omit the ``auth.ldap.bindpw`` parameter. You can template the ``binddn`` like this:
the case, omit the ``auth.ldap.bindpw`` parameter. Note this is not the same thing as using an anonymous bind.
Most LDAP servers will not permit anonymous bind, and as mentioned above, if `auth.ldap.anonymous` is False you
cannot use an empty password.
You can template the ``binddn`` like this:
.. code-block:: yaml

View file

@ -4,9 +4,21 @@ Salt 2016.11.8 Release Notes
Version 2016.11.8 is a bugfix release for :ref:`2016.11.0 <release-2016-11-0>`.]
Anonymous Binds and LDAP/Active Directory
-----------------------------------------
When auth.ldap.anonymous is set to False, the bind password can no longer be empty.
Changes for v2016.11.7..v2016.11.8
----------------------------------
Security Fix
============
CVE-2017-14695 Directory traversal vulnerability in minion id validation in SaltStack. Allows remote minions with incorrect credentials to authenticate to a master via a crafted minion ID. Credit for discovering the security flaw goes to: Julian Brost (julian@0x4a42.net)
CVE-2017-14696 Remote Denial of Service with a specially crafted authentication request. Credit for discovering the security flaw goes to: Julian Brost (julian@0x4a42.net)
Extended changelog courtesy of Todd Stansell (https://github.com/tjstansell/salt-changelogs):
*Generated at: 2017-09-11T14:52:27Z*

View file

@ -0,0 +1,6 @@
============================
Salt 2016.11.9 Release Notes
============================
Version 2016.11.9 is a bugfix release for :ref:`2016.11.0 <release-2016-11-0>`.]

View file

@ -7,23 +7,9 @@ Version 2016.3.8 is a bugfix release for :ref:`2016.3.0 <release-2016-3-0>`.
Changes for v2016.3.7..v2016.3.8
--------------------------------
New master configuration option `allow_minion_key_revoke`, defaults to True. This option
controls whether a minion can request that the master revoke its key. When True, a minion
can request a key revocation and the master will comply. If it is False, the key will not
be revoked by the msater.
Security Fix
============
New master configuration option `require_minion_sign_messages`
This requires that minions cryptographically sign the messages they
publish to the master. If minions are not signing, then log this information
at loglevel 'INFO' and drop the message without acting on it.
CVE-2017-14695 Directory traversal vulnerability in minion id validation in SaltStack. Allows remote minions with incorrect credentials to authenticate to a master via a crafted minion ID. Credit for discovering the security flaw goes to: Julian Brost (julian@0x4a42.net)
New master configuration option `drop_messages_signature_fail`
Drop messages from minions when their signatures do not validate.
Note that when this option is False but `require_minion_sign_messages` is True
minions MUST sign their messages but the validity of their signatures
is ignored.
New minion configuration option `minion_sign_messages`
Causes the minion to cryptographically sign the payload of messages it places
on the event bus for the master. The payloads are signed with the minion's
private key so the master can verify the signature with its public key.
CVE-2017-14696 Remote Denial of Service with a specially crafted authentication request. Credit for discovering the security flaw goes to: Julian Brost (julian@0x4a42.net)

View file

@ -0,0 +1,29 @@
===========================
Salt 2016.3.9 Release Notes
===========================
Version 2016.3.9 is a bugfix release for :ref:`2016.3.0 <release-2016-3-0>`.
Changes for v2016.3.7..v2016.3.9
--------------------------------
New master configuration option `allow_minion_key_revoke`, defaults to True. This option
controls whether a minion can request that the master revoke its key. When True, a minion
can request a key revocation and the master will comply. If it is False, the key will not
be revoked by the msater.
New master configuration option `require_minion_sign_messages`
This requires that minions cryptographically sign the messages they
publish to the master. If minions are not signing, then log this information
at loglevel 'INFO' and drop the message without acting on it.
New master configuration option `drop_messages_signature_fail`
Drop messages from minions when their signatures do not validate.
Note that when this option is False but `require_minion_sign_messages` is True
minions MUST sign their messages but the validity of their signatures
is ignored.
New minion configuration option `minion_sign_messages`
Causes the minion to cryptographically sign the payload of messages it places
on the event bus for the master. The payloads are signed with the minion's
private key so the master can verify the signature with its public key.

View file

@ -16,6 +16,11 @@ CVE-2017-14696 Remote Denial of Service with a specially crafted authentication
Extended changelog courtesy of Todd Stansell (https://github.com/tjstansell/salt-changelogs):
Known Issues
============
On 2017.7.2 when using salt-api and cherrypy version 5.6.0, issue `#43581`_ will occur when starting the salt-api service. We have patched the cherry-py packages for python-cherrypy-5.6.0-2 from repo.saltstack.com. If you are using python-cherrypy-5.6.0-1 please ensure to run `yum install python-cherrypy` to install the new patched version.
*Generated at: 2017-09-26T21:06:19Z*
Statistics:
@ -3104,6 +3109,7 @@ Changes:
.. _`#475`: https://github.com/saltstack/salt/issues/475
.. _`#480`: https://github.com/saltstack/salt/issues/480
.. _`#495`: https://github.com/saltstack/salt/issues/495
.. _`#43581`: https://github.com/saltstack/salt/issues/43581
.. _`bp-37424`: https://github.com/saltstack/salt/pull/37424
.. _`bp-39366`: https://github.com/saltstack/salt/pull/39366
.. _`bp-41543`: https://github.com/saltstack/salt/pull/41543

View file

@ -0,0 +1,6 @@
============================
Salt 2017.7.3 Release Notes
============================
Version 2017.7.3 is a bugfix release for :ref:`2017.7.0 <release-2017-7-0>`.

View file

@ -33,3 +33,5 @@ Tutorials Index
* :ref:`The macOS (Maverick) Developer Step By Step Guide To Salt Installation <tutorial-macos-walk-through>`
* :ref:`SaltStack Walk-through <tutorial-salt-walk-through>`
* :ref:`Writing Salt Tests <tutorial-salt-testing>`
* :ref:`Running Salt States and Commands in Docker Containers <docker-sls>`
* :ref:`Preseed Minion with Accepted Key <tutorial-preseed-key>`

View file

@ -23,7 +23,7 @@ Supported Operating Systems
.. note::
In the event you do not see your distribution or version available please
review the develop branch on GitHub as it main contain updates that are
review the develop branch on GitHub as it may contain updates that are
not present in the stable release:
https://github.com/saltstack/salt-bootstrap/tree/develop

View file

@ -67,7 +67,7 @@ _su_cmd() {
_get_pid() {
netstat $NS_NOTRIM -ap --protocol=unix 2>$ERROR_TO_DEVNULL \
netstat -n $NS_NOTRIM -ap --protocol=unix 2>$ERROR_TO_DEVNULL \
| sed -r -e "\|\s${SOCK_DIR}/minion_event_${MINION_ID_HASH}_pub\.ipc$|"'!d; s|/.*||; s/.*\s//;' \
| uniq
}
@ -155,7 +155,7 @@ start() {
printf "\nPROCESSES:\n" >&2
ps wwwaxu | grep '[s]alt-minion' >&2
printf "\nSOCKETS:\n" >&2
netstat $NS_NOTRIM -ap --protocol=unix | grep 'salt.*minion' >&2
netstat -n $NS_NOTRIM -ap --protocol=unix | grep 'salt.*minion' >&2
printf "\nLOG_FILE:\n" >&2
tail -n 20 "$LOG_FILE" >&2
printf "\nENVIRONMENT:\n" >&2

View file

@ -110,6 +110,10 @@ class _LDAPConnection(object):
self.ldap.set_option(ldap.OPT_REFERRALS, 0) # Needed for AD
if not anonymous:
if self.bindpw is None or len(self.bindpw) < 1:
raise CommandExecutionError(
'LDAP bind password is not set: password cannot be empty if auth.ldap.anonymous is False'
)
self.ldap.simple_bind_s(self.binddn, self.bindpw)
except Exception as ldap_error:
raise CommandExecutionError(

View file

@ -2580,7 +2580,10 @@ def create(vm_=None, call=None):
ssh_user, ssh_key = __get_ssh_credentials(vm_)
vm_['ssh_host'] = __get_host(node_data, vm_)
vm_['key_filename'] = ssh_key
__utils__['cloud.bootstrap'](vm_, __opts__)
ret = __utils__['cloud.bootstrap'](vm_, __opts__)
ret.update(node_dict)
log.info('Created Cloud VM \'{0[name]}\''.format(vm_))
log.trace(
@ -2598,7 +2601,7 @@ def create(vm_=None, call=None):
transport=__opts__['transport']
)
return node_dict
return ret
def update_pricing(kwargs=None, call=None):

View file

@ -607,6 +607,9 @@ class AsyncAuth(object):
raise tornado.gen.Return('retry')
else:
raise SaltClientError('Attempt to authenticate with the salt master failed with timeout error')
if not isinstance(payload, dict):
log.error('Sign-in attempt failed: %s', payload)
raise tornado.gen.Return(False)
if 'load' in payload:
if 'ret' in payload['load']:
if not payload['load']['ret']:

View file

@ -622,12 +622,15 @@ class Client(object):
def on_header(hdr):
if write_body[1] is not False and write_body[2] is None:
if not hdr.strip() and 'Content-Type' not in write_body[1]:
# We've reached the end of the headers and not yet
# found the Content-Type. Reset write_body[0] so that
# we properly follow the redirect. Note that slicing is
# used below to ensure that we re-use the same list
# rather than creating a new one.
write_body[0:2] = (None, False)
# If write_body[0] is True, then we are not following a
# redirect (initial response was a 200 OK). So there is
# no need to reset write_body[0].
if write_body[0] is not True:
# We are following a redirect, so we need to reset
# write_body[0] so that we properly follow it.
write_body[0] = None
# We don't need the HTTPHeaders object anymore
write_body[1] = False
return
# Try to find out what content type encoding is used if
# this is a text file

View file

@ -128,12 +128,12 @@ def setup_handlers():
callable(transport_registry.compute_scope)):
conf_extras = transport_registry.compute_scope(url, dsn_config)
dsn_config.update(conf_extras)
options.update({
'project': dsn_config['SENTRY_PROJECT'],
'servers': dsn_config['SENTRY_SERVERS'],
'public_key': dsn_config['SENTRY_PUBLIC_KEY'],
'secret_key': dsn_config['SENTRY_SECRET_KEY']
})
options.update({
'project': dsn_config['SENTRY_PROJECT'],
'servers': dsn_config['SENTRY_SERVERS'],
'public_key': dsn_config['SENTRY_PUBLIC_KEY'],
'secret_key': dsn_config['SENTRY_SECRET_KEY']
})
except ValueError as exc:
log.info(
'Raven failed to parse the configuration provided '

View file

@ -118,7 +118,7 @@ def get(key, default='', delimiter=DEFAULT_TARGET_DELIM, ordered=True):
def has_value(key):
'''
Determine whether a named value exists in the grains dictionary.
Determine whether a key exists in the grains dictionary.
Given a grains dictionary that contains the following structure::
@ -134,7 +134,10 @@ def has_value(key):
salt '*' grains.has_value pkg:apache
'''
return True if salt.utils.traverse_dict_and_list(__grains__, key, False) else False
return salt.utils.traverse_dict_and_list(
__grains__,
key,
KeyError) is not KeyError
def items(sanitize=False):

View file

@ -123,7 +123,16 @@ def available():
salt '*' kmod.available
'''
ret = []
mod_dir = os.path.join('/lib/modules/', os.uname()[2])
built_in_file = os.path.join(mod_dir, 'modules.builtin')
if os.path.exists(built_in_file):
with salt.utils.fopen(built_in_file, 'r') as f:
for line in f:
# Strip .ko from the basename
ret.append(os.path.basename(line)[:-4])
for root, dirs, files in os.walk(mod_dir):
for fn_ in files:
if '.ko' in fn_:

View file

@ -363,7 +363,8 @@ def statelist(states_dict, sid_excludes=frozenset(['include', 'exclude'])):
REQUISITES = set([
'require', 'require_in', 'watch', 'watch_in', 'use', 'use_in', 'listen', 'listen_in'
'require', 'require_in', 'watch', 'watch_in', 'use', 'use_in', 'listen', 'listen_in',
'onchanges', 'onchanges_in', 'onfail', 'onfail_in'
])
@ -405,8 +406,8 @@ def rename_state_ids(data, sls, is_extend=False):
del data[sid]
REQUIRE = set(['require', 'watch', 'listen'])
REQUIRE_IN = set(['require_in', 'watch_in', 'listen_in'])
REQUIRE = set(['require', 'watch', 'listen', 'onchanges', 'onfail'])
REQUIRE_IN = set(['require_in', 'watch_in', 'listen_in', 'onchanges_in', 'onfail_in'])
EXTENDED_REQUIRE = {}
EXTENDED_REQUIRE_IN = {}
@ -414,8 +415,8 @@ from itertools import chain
# To avoid cycles among states when each state requires the one before it:
# explicit require/watch/listen can only contain states before it
# explicit require_in/watch_in/listen_in can only contain states after it
# explicit require/watch/listen/onchanges/onfail can only contain states before it
# explicit require_in/watch_in/listen_in/onchanges_in/onfail_in can only contain states after it
def add_implicit_requires(data):
def T(sid, state): # pylint: disable=C0103
@ -449,7 +450,7 @@ def add_implicit_requires(data):
for _, rstate, rsid in reqs:
if T(rsid, rstate) in states_after:
raise SaltRenderError(
'State({0}) can\'t require/watch/listen a state({1}) defined '
'State({0}) can\'t require/watch/listen/onchanges/onfail a state({1}) defined '
'after it!'.format(tag, T(rsid, rstate))
)
@ -459,7 +460,7 @@ def add_implicit_requires(data):
for _, rstate, rsid in reqs:
if T(rsid, rstate) in states_before:
raise SaltRenderError(
'State({0}) can\'t require_in/watch_in/listen_in a state({1}) '
'State({0}) can\'t require_in/watch_in/listen_in/onchanges_in/onfail_in a state({1}) '
'defined before it!'.format(tag, T(rsid, rstate))
)
@ -571,7 +572,7 @@ def extract_state_confs(data, is_extend=False):
if not is_extend and state_id in STATE_CONF_EXT:
extend = STATE_CONF_EXT[state_id]
for requisite in 'require', 'watch', 'listen':
for requisite in 'require', 'watch', 'listen', 'onchanges', 'onfail':
if requisite in extend:
extend[requisite] += to_dict[state_id].get(requisite, [])
to_dict[state_id].update(STATE_CONF_EXT[state_id])

View file

@ -309,7 +309,7 @@ def _format_job_instance(job):
'Arguments': list(job.get('arg', [])),
# unlikely but safeguard from invalid returns
'Target': job.get('tgt', 'unknown-target'),
'Target-type': job.get('tgt_type', []),
'Target-type': job.get('tgt_type', 'list'),
'User': job.get('user', 'root')}
if 'metadata' in job:

View file

@ -180,7 +180,7 @@ def _format_job_instance(job):
'Arguments': json.loads(job.get('arg', '[]')),
# unlikely but safeguard from invalid returns
'Target': job.get('tgt', 'unknown-target'),
'Target-type': job.get('tgt_type', []),
'Target-type': job.get('tgt_type', 'list'),
'User': job.get('user', 'root')}
# TODO: Add Metadata support when it is merged from develop
return ret

View file

@ -98,6 +98,7 @@ from __future__ import absolute_import
# Python
import logging
import re
import copy
# Salt libs
import salt.utils.minions
@ -151,7 +152,7 @@ def targets(tgt, tgt_type='glob', **kwargs): # pylint: disable=W0613
except LookupError:
continue
minion_res = __opts__.get('roster_defaults', {}).copy()
minion_res = copy.deepcopy(__opts__.get('roster_defaults', {}))
for param, order in roster_order.items():
if not isinstance(order, (list, tuple)):
order = [order]

View file

@ -21,6 +21,7 @@ usually located at /etc/salt/cloud. For example, add the following:
# Import python libs
from __future__ import absolute_import
import os
import copy
# Import Salt libs
import salt.loader
@ -63,7 +64,7 @@ def targets(tgt, tgt_type='glob', **kwargs): # pylint: disable=W0613
))
preferred_ip = extract_ipv4(roster_order, ip_list)
ret[minion_id] = __opts__.get('roster_defaults', {})
ret[minion_id] = copy.deepcopy(__opts__.get('roster_defaults', {}))
ret[minion_id].update({'host': preferred_ip})
ssh_username = salt.utils.cloud.ssh_usernames(vm_, cloud_opts)

View file

@ -15,6 +15,7 @@ When you want to use host globs for target matching, use ``--roster clustershell
# Import python libs
from __future__ import absolute_import
import socket
import copy
from salt.ext.six.moves import map # pylint: disable=import-error,redefined-builtin
REQ_ERROR = None
@ -43,7 +44,7 @@ def targets(tgt, tgt_type='glob', **kwargs):
for host, addr in host_addrs.items():
addr = str(addr)
ret[addr] = __opts__.get('roster_defaults', {}).copy()
ret[addr] = copy.deepcopy(__opts__.get('roster_defaults', {}))
for port in ports:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

View file

@ -7,6 +7,7 @@ from __future__ import absolute_import
# Import python libs
import fnmatch
import re
import copy
# Try to import range from https://github.com/ytoolshed/range
HAS_RANGE = False
@ -142,7 +143,7 @@ class RosterMatcher(object):
'''
Return the configured ip
'''
ret = __opts__.get('roster_defaults', {})
ret = copy.deepcopy(__opts__.get('roster_defaults', {}))
if isinstance(self.raw[minion], string_types):
ret.update({'host': self.raw[minion]})
return ret

View file

@ -13,6 +13,7 @@ When you want to use a range query for target matching, use ``--roster range``.
'''
from __future__ import absolute_import
import fnmatch
import copy
import logging
log = logging.getLogger(__name__)
@ -68,7 +69,7 @@ def targets(tgt, tgt_type='range', **kwargs):
def target_range(tgt, hosts):
ret = {}
for host in hosts:
ret[host] = __opts__.get('roster_defaults', {}).copy()
ret[host] = copy.deepcopy(__opts__.get('roster_defaults', {}))
ret[host].update({'host': host})
if __opts__.get('ssh_user'):
ret[host].update({'user': __opts__['ssh_user']})
@ -79,7 +80,7 @@ def target_glob(tgt, hosts):
ret = {}
for host in hosts:
if fnmatch.fnmatch(tgt, host):
ret[host] = __opts__.get('roster_defaults', {}).copy()
ret[host] = copy.deepcopy(__opts__.get('roster_defaults', {}))
ret[host].update({'host': host})
if __opts__.get('ssh_user'):
ret[host].update({'user': __opts__['ssh_user']})

View file

@ -7,6 +7,7 @@ Scan a netmask or ipaddr for open ssh ports
from __future__ import absolute_import
import socket
import logging
import copy
# Import salt libs
import salt.utils.network
@ -55,7 +56,7 @@ class RosterMatcher(object):
pass
for addr in addrs:
addr = str(addr)
ret[addr] = __opts__.get('roster_defaults', {}).copy()
ret[addr] = copy.deepcopy(__opts__.get('roster_defaults', {}))
log.trace('Scanning host: {0}'.format(addr))
for port in ports:
log.trace('Scanning port: {0}'.format(port))

View file

@ -542,7 +542,7 @@ def _format_job_instance(job):
'Arguments': list(job.get('arg', [])),
# unlikely but safeguard from invalid returns
'Target': job.get('tgt', 'unknown-target'),
'Target-type': job.get('tgt_type', []),
'Target-type': job.get('tgt_type', 'list'),
'User': job.get('user', 'root')}
if 'metadata' in job:

View file

@ -33,7 +33,7 @@ Ensure an ACL does not exist
.. code-block:: yaml
removeAcl:
removeAcl:
win_dacl.absent:
- name: HKEY_LOCAL_MACHINE\\SOFTWARE\\mykey
- objectType: Registry
@ -50,11 +50,11 @@ Ensure an object is inheriting permissions
.. code-block:: yaml
eInherit:
win_dacl.enableinheritance:
- name: HKEY_LOCAL_MACHINE\\SOFTWARE\\mykey
- objectType: Registry
- clear_existing_acl: True
eInherit:
win_dacl.enableinheritance:
- name: HKEY_LOCAL_MACHINE\\SOFTWARE\\mykey
- objectType: Registry
- clear_existing_acl: True
Ensure an object is not inheriting permissions
parameters:
@ -62,13 +62,13 @@ Ensure an object is not inheriting permissions
objectType - Registry/File/Directory
copy_inherited_acl - True/False - if inheritance is enabled, should the inherited permissions be copied to the ACL when inheritance is disabled
.. code-block:: yaml
.. code-block:: yaml
dInherit:
win_dacl.disableinheritance:
- name: HKEY_LOCAL_MACHINE\\SOFTWARE\\mykey
- objectType: Registry
- copy_inherited_acl: False
dInherit:
win_dacl.disableinheritance:
- name: HKEY_LOCAL_MACHINE\\SOFTWARE\\mykey
- objectType: Registry
- copy_inherited_acl: False
'''
@ -119,7 +119,7 @@ def present(name, objectType, user, permission, acetype, propagation):
def absent(name, objectType, user, permission, acetype, propagation):
'''
Ensure a Linux ACL does not exist
Ensure an ACL does not exist
'''
ret = {'name': name,
'result': True,

View file

@ -6,7 +6,7 @@ State to manage monitoring in Zenoss.
This state module depends on the 'zenoss' Salt execution module.
Allows for setting a state of minions in Zenoss using the Zenoss API. Currently Zenoss 4.x is supported.
Allows for setting a state of minions in Zenoss using the Zenoss API. Currently Zenoss 4.x and 5.x are supported.
.. code-block:: yaml
@ -30,6 +30,8 @@ def __virtual__():
'''
if 'zenoss.add_device' in __salt__:
return 'zenoss'
else:
return False, "The zenoss execution module is not available"
def monitored(name, device_class=None, collector='localhost', prod_state=None):
@ -57,21 +59,28 @@ def monitored(name, device_class=None, collector='localhost', prod_state=None):
ret['comment'] = '{0} is already monitored'.format(name)
# if prod_state is set, ensure it matches with the current state
if prod_state:
if device['productionState'] != prod_state:
if prod_state is not None and device['productionState'] != prod_state:
if __opts__['test']:
ret['comment'] = '{0} is already monitored but prodState will be updated'.format(name)
ret['result'] = None
else:
__salt__['zenoss.set_prod_state'](prod_state, name)
ret['changes'] = {'old': 'prodState == {0}'.format(device['productionState']), 'new': 'prodState == {0}'.format(prod_state)}
ret['comment'] = '{0} is already monitored but prodState was incorrect, setting to Production'.format(name)
ret['comment'] = '{0} is already monitored but prodState was updated'.format(name)
ret['changes'] = {
'old': 'prodState == {0}'.format(device['productionState']),
'new': 'prodState == {0}'.format(prod_state)
}
return ret
# Device not yet in Zenoss
if __opts__['test']:
ret['comment'] = 'The state of "{0}" will be changed.'.format(name)
ret['changes'] = {'old': 'monitored == False', 'new': 'monitored == True'}
ret['result'] = None
return ret
# Device not yet in Zenoss. Add and check result
# Add and check result
if __salt__['zenoss.add_device'](name, device_class, collector, prod_state):
ret['result'] = True
ret['changes'] = {'old': 'monitored == False', 'new': 'monitored == True'}

View file

@ -623,6 +623,17 @@ class TCPReqServerChannel(salt.transport.mixins.auth.AESReqServerMixin, salt.tra
'payload and load must be a dict', header=header))
raise tornado.gen.Return()
try:
id_ = payload['load'].get('id', '')
if '\0' in id_:
log.error('Payload contains an id with a null byte: %s', payload)
stream.send(self.serial.dumps('bad load: id contains a null byte'))
raise tornado.gen.Return()
except TypeError:
log.error('Payload contains non-string id: %s', payload)
stream.send(self.serial.dumps('bad load: id {0} is not a string'.format(id_)))
raise tornado.gen.Return()
# intercept the "_auth" commands, since the main daemon shouldn't know
# anything about our key auth
if payload['enc'] == 'clear' and payload.get('load', {}).get('cmd') == '_auth':

View file

@ -596,6 +596,17 @@ class ZeroMQReqServerChannel(salt.transport.mixins.auth.AESReqServerMixin, salt.
stream.send(self.serial.dumps('payload and load must be a dict'))
raise tornado.gen.Return()
try:
id_ = payload['load'].get('id', '')
if '\0' in id_:
log.error('Payload contains an id with a null byte: %s', payload)
stream.send(self.serial.dumps('bad load: id contains a null byte'))
raise tornado.gen.Return()
except TypeError:
log.error('Payload contains non-string id: %s', payload)
stream.send(self.serial.dumps('bad load: id {0} is not a string'.format(id_)))
raise tornado.gen.Return()
# intercept the "_auth" commands, since the main daemon shouldn't know
# anything about our key auth
if payload['enc'] == 'clear' and payload.get('load', {}).get('cmd') == '_auth':

View file

@ -65,7 +65,7 @@ def format_job_instance(job):
'Arguments': list(job.get('arg', [])),
# unlikely but safeguard from invalid returns
'Target': job.get('tgt', 'unknown-target'),
'Target-type': job.get('tgt_type', []),
'Target-type': job.get('tgt_type', 'list'),
'User': job.get('user', 'root')}
if 'metadata' in job:

View file

@ -480,22 +480,15 @@ def clean_path(root, path, subdir=False):
return ''
def clean_id(id_):
'''
Returns if the passed id is clean.
'''
if re.search(r'\.\.\{sep}'.format(sep=os.sep), id_):
return False
return True
def valid_id(opts, id_):
'''
Returns if the passed id is valid
'''
try:
return bool(clean_path(opts['pki_dir'], id_)) and clean_id(id_)
except (AttributeError, KeyError, TypeError) as e:
if any(x in id_ for x in ('/', '\\', '\0')):
return False
return bool(clean_path(opts['pki_dir'], id_))
except (AttributeError, KeyError, TypeError):
return False

View file

@ -183,17 +183,23 @@ class WriteSaltVersion(Command):
'''
def run(self):
if not os.path.exists(SALT_VERSION_HARDCODED):
if not os.path.exists(SALT_VERSION_HARDCODED) or self.distribution.with_salt_version:
# Write the version file
if getattr(self.distribution, 'salt_version_hardcoded_path', None) is None:
print('This command is not meant to be called on it\'s own')
exit(1)
if not self.distribution.with_salt_version:
salt_version = __saltstack_version__ # pylint: disable=undefined-variable
else:
from salt.version import SaltStackVersion
salt_version = SaltStackVersion.parse(self.distribution.with_salt_version)
# pylint: disable=E0602
open(self.distribution.salt_version_hardcoded_path, 'w').write(
INSTALL_VERSION_TEMPLATE.format(
date=DATE,
full_version_info=__saltstack_version__.full_info
full_version_info=salt_version.full_info
)
)
# pylint: enable=E0602
@ -731,6 +737,13 @@ class Build(build):
def run(self):
# Run build.run function
build.run(self)
if getattr(self.distribution, 'with_salt_version', False):
# Write the hardcoded salt version module salt/_version.py
self.distribution.salt_version_hardcoded_path = os.path.join(
self.build_lib, 'salt', '_version.py'
)
self.run_command('write_salt_version')
if getattr(self.distribution, 'running_salt_install', False):
# If our install attribute is present and set to True, we'll go
# ahead and write our install time python modules.
@ -839,6 +852,7 @@ class SaltDistribution(distutils.dist.Distribution):
('ssh-packaging', None, 'Run in SSH packaging mode'),
('salt-transport=', None, 'The transport to prepare salt for. Choices are \'zeromq\' '
'\'raet\' or \'both\'. Defaults to \'zeromq\'', 'zeromq')] + [
('with-salt-version=', None, 'Set a fixed version for Salt instead calculating it'),
# Salt's Paths Configuration Settings
('salt-root-dir=', None,
'Salt\'s pre-configured root directory'),
@ -893,6 +907,9 @@ class SaltDistribution(distutils.dist.Distribution):
self.salt_spm_pillar_dir = None
self.salt_spm_reactor_dir = None
# Salt version
self.with_salt_version = None
self.name = 'salt-ssh' if PACKAGED_FOR_SALT_SSH else 'salt'
self.salt_version = __version__ # pylint: disable=undefined-variable
self.description = 'Portable, distributed, remote execution and configuration management system'

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Import python libs
from __future__ import absolute_import
# Import Salt Testing libs
from tests.support.case import ShellCase
class SPMTest(ShellCase):
'''
Test spm script
'''
def test_spm_help(self):
'''
test --help argument for spm
'''
expected_args = ['--version', '--assume-yes', '--help']
output = self.run_spm('--help')
for arg in expected_args:
self.assertIn(arg, ''.join(output))
def test_spm_bad_arg(self):
'''
test correct output when bad argument passed
'''
expected_args = ['--version', '--assume-yes', '--help']
output = self.run_spm('doesnotexist')
for arg in expected_args:
self.assertIn(arg, ''.join(output))

View file

@ -442,6 +442,16 @@ class ShellCase(ShellTestCase, AdaptedConfigurationTestCaseMixin, ScriptPathMixi
catch_stderr=catch_stderr,
timeout=timeout)
def run_spm(self, arg_str, with_retcode=False, catch_stderr=False, timeout=60): # pylint: disable=W0221
'''
Execute spm
'''
return self.run_script('spm',
arg_str,
with_retcode=with_retcode,
catch_stderr=catch_stderr,
timeout=timeout)
def run_ssh(self, arg_str, with_retcode=False, catch_stderr=False, timeout=60): # pylint: disable=W0221
'''
Execute salt-ssh

View file

@ -63,6 +63,16 @@ class TestVerify(TestCase):
opts = {'pki_dir': '/tmp/whatever'}
self.assertFalse(valid_id(opts, None))
def test_valid_id_pathsep(self):
'''
Path separators in id should make it invalid
'''
opts = {'pki_dir': '/tmp/whatever'}
# We have to test both path separators because os.path.normpath will
# convert forward slashes to backslashes on Windows.
for pathsep in ('/', '\\'):
self.assertFalse(valid_id(opts, pathsep.join(('..', 'foobar'))))
def test_zmq_verify(self):
self.assertTrue(zmq_version())