mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch '2018.3' into optimize-firewalld-state
This commit is contained in:
commit
f7299b9196
8 changed files with 226 additions and 61 deletions
6
doc/topics/releases/2018.3.4.rst
Normal file
6
doc/topics/releases/2018.3.4.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
========================================
|
||||
In Progress: Salt 2018.3.4 Release Notes
|
||||
========================================
|
||||
|
||||
Version 2018.3.4 is an **unreleased** bugfix release for :ref:`2018.3.0 <release-2018-3-0>`.
|
||||
This release is still in progress and has not been released yet.
|
|
@ -1467,6 +1467,34 @@ def _parse_os_release(*os_release_files):
|
|||
return ret
|
||||
|
||||
|
||||
def _parse_cpe_name(cpe):
|
||||
'''
|
||||
Parse CPE_NAME data from the os-release
|
||||
|
||||
Info: https://csrc.nist.gov/projects/security-content-automation-protocol/scap-specifications/cpe
|
||||
|
||||
:param cpe:
|
||||
:return:
|
||||
'''
|
||||
part = {
|
||||
'o': 'operating system',
|
||||
'h': 'hardware',
|
||||
'a': 'application',
|
||||
}
|
||||
ret = {}
|
||||
cpe = (cpe or '').split(':')
|
||||
if len(cpe) > 4 and cpe[0] == 'cpe':
|
||||
if cpe[1].startswith('/'): # WFN to URI
|
||||
ret['vendor'], ret['product'], ret['version'] = cpe[2:5]
|
||||
ret['phase'] = cpe[5] if len(cpe) > 5 else None
|
||||
ret['part'] = part.get(cpe[1][1:])
|
||||
elif len(cpe) == 13 and cpe[1] == '2.3': # WFN to a string
|
||||
ret['vendor'], ret['product'], ret['version'], ret['phase'] = [x if x != '*' else None for x in cpe[3:7]]
|
||||
ret['part'] = part.get(cpe[2])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def os_data():
|
||||
'''
|
||||
Return grains pertaining to the operating system
|
||||
|
@ -1658,13 +1686,20 @@ def os_data():
|
|||
codename = codename_match.group(1)
|
||||
grains['lsb_distrib_codename'] = codename
|
||||
if 'CPE_NAME' in os_release:
|
||||
if ":suse:" in os_release['CPE_NAME'] or ":opensuse:" in os_release['CPE_NAME']:
|
||||
cpe = _parse_cpe_name(os_release['CPE_NAME'])
|
||||
if not cpe:
|
||||
log.error('Broken CPE_NAME format in /etc/os-release!')
|
||||
elif cpe.get('vendor', '').lower() in ['suse', 'opensuse']:
|
||||
grains['os'] = "SUSE"
|
||||
# openSUSE `osfullname` grain normalization
|
||||
if os_release.get("NAME") == "openSUSE Leap":
|
||||
grains['osfullname'] = "Leap"
|
||||
elif os_release.get("VERSION") == "Tumbleweed":
|
||||
grains['osfullname'] = os_release["VERSION"]
|
||||
# Override VERSION_ID, if CPE_NAME around
|
||||
if cpe.get('version') and cpe.get('vendor') == 'opensuse': # Keep VERSION_ID for SLES
|
||||
grains['lsb_distrib_release'] = cpe['version']
|
||||
|
||||
elif os.path.isfile('/etc/SuSE-release'):
|
||||
log.trace('Parsing distrib info from /etc/SuSE-release')
|
||||
grains['lsb_distrib_id'] = 'SUSE'
|
||||
|
@ -1770,8 +1805,7 @@ def os_data():
|
|||
# Commit introducing this comment should be reverted after the upstream bug is released.
|
||||
if 'CentOS Linux 7' in grains.get('lsb_distrib_codename', ''):
|
||||
grains.pop('lsb_distrib_release', None)
|
||||
grains['osrelease'] = \
|
||||
grains.get('lsb_distrib_release', osrelease).strip()
|
||||
grains['osrelease'] = grains.get('lsb_distrib_release', osrelease).strip()
|
||||
grains['oscodename'] = grains.get('lsb_distrib_codename', '').strip() or oscodename
|
||||
if 'Red Hat' in grains['oscodename']:
|
||||
grains['oscodename'] = oscodename
|
||||
|
@ -1809,8 +1843,7 @@ def os_data():
|
|||
r'((?:Open|Oracle )?Solaris|OpenIndiana|OmniOS) (Development)?'
|
||||
r'\s*(\d+\.?\d*|v\d+)\s?[A-Z]*\s?(r\d+|\d+\/\d+|oi_\S+|snv_\S+)?'
|
||||
)
|
||||
osname, development, osmajorrelease, osminorrelease = \
|
||||
release_re.search(rel_data).groups()
|
||||
osname, development, osmajorrelease, osminorrelease = release_re.search(rel_data).groups()
|
||||
except AttributeError:
|
||||
# Set a blank osrelease grain and fallback to 'Solaris'
|
||||
# as the 'os' grain.
|
||||
|
@ -2516,7 +2549,7 @@ def _hw_data(osdata):
|
|||
break
|
||||
elif osdata['kernel'] == 'AIX':
|
||||
cmd = salt.utils.path.which('prtconf')
|
||||
if data:
|
||||
if cmd:
|
||||
data = __salt__['cmd.run']('{0}'.format(cmd)) + os.linesep
|
||||
for dest, regstring in (('serialnumber', r'(?im)^\s*Machine\s+Serial\s+Number:\s+(\S+)'),
|
||||
('systemfirmware', r'(?im)^\s*Firmware\s+Version:\s+(.*)')):
|
||||
|
|
|
@ -85,7 +85,7 @@ def _enable_atrun():
|
|||
Enable and start the atrun daemon
|
||||
'''
|
||||
name = 'com.apple.atrun'
|
||||
services = salt.utils.mac_utils.available_services()
|
||||
services = __utils__['mac_utils.available_services']()
|
||||
label = None
|
||||
path = None
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ Module to provide MySQL compatibility to salt.
|
|||
|
||||
# Import python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
import hashlib
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
|
@ -203,12 +202,6 @@ def __virtual__():
|
|||
return (False, 'The mysql execution module cannot be loaded: neither MySQLdb nor PyMySQL is available.')
|
||||
|
||||
|
||||
def __mysql_hash_password(password):
|
||||
_password = hashlib.sha1(password).digest()
|
||||
_password = '*{0}'.format(hashlib.sha1(_password).hexdigest().upper())
|
||||
return _password
|
||||
|
||||
|
||||
def __check_table(name, table, **connection_args):
|
||||
dbc = _connect(**connection_args)
|
||||
if dbc is None:
|
||||
|
@ -1210,6 +1203,7 @@ def user_exists(user,
|
|||
salt '*' mysql.user_exists 'username' passwordless=True
|
||||
salt '*' mysql.user_exists 'username' password_column='authentication_string'
|
||||
'''
|
||||
run_verify = False
|
||||
server_version = version(**connection_args)
|
||||
dbc = _connect(**connection_args)
|
||||
# Did we fail to connect with the user we are checking
|
||||
|
@ -1242,18 +1236,19 @@ def user_exists(user,
|
|||
else:
|
||||
qry += ' AND ' + password_column + ' = \'\''
|
||||
elif password:
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') <= 0:
|
||||
# Hash the password before comparing
|
||||
_password = __mysql_hash_password(password)
|
||||
qry += ' AND ' + password_column + ' = %(password)s'
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
|
||||
run_verify = True
|
||||
else:
|
||||
_password = password
|
||||
qry += ' AND ' + password_column + ' = PASSWORD(%(password)s)'
|
||||
args['password'] = six.text_type(_password)
|
||||
args['password'] = six.text_type(_password)
|
||||
elif password_hash:
|
||||
qry += ' AND ' + password_column + ' = %(password)s'
|
||||
args['password'] = password_hash
|
||||
|
||||
if run_verify:
|
||||
if not verify_login(user, host, password):
|
||||
return False
|
||||
try:
|
||||
_execute(cur, qry, args)
|
||||
except MySQLdb.OperationalError as exc:
|
||||
|
@ -1368,7 +1363,7 @@ def user_create(user,
|
|||
qry += ' IDENTIFIED BY %(password)s'
|
||||
args['password'] = six.text_type(password)
|
||||
elif password_hash is not None:
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') <= 0:
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
|
||||
qry += ' IDENTIFIED BY %(password)s'
|
||||
else:
|
||||
qry += ' IDENTIFIED BY PASSWORD %(password)s'
|
||||
|
@ -1454,7 +1449,7 @@ def user_chpass(user,
|
|||
server_version = version(**connection_args)
|
||||
args = {}
|
||||
if password is not None:
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') <= 0:
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
|
||||
password_sql = '%(password)s'
|
||||
else:
|
||||
password_sql = 'PASSWORD(%(password)s)'
|
||||
|
@ -1477,18 +1472,28 @@ def user_chpass(user,
|
|||
password_column = __password_column(**connection_args)
|
||||
|
||||
cur = dbc.cursor()
|
||||
qry = ('UPDATE mysql.user SET ' + password_column + '='
|
||||
+ password_sql +
|
||||
' WHERE User=%(user)s AND Host = %(host)s;')
|
||||
args['user'] = user
|
||||
args['host'] = host
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
|
||||
qry = ("ALTER USER '" + user + "'@'" + host + "'"
|
||||
" IDENTIFIED BY '" + password + "';")
|
||||
args = {}
|
||||
else:
|
||||
qry = ('UPDATE mysql.user SET ' + password_column + '='
|
||||
+ password_sql +
|
||||
' WHERE User=%(user)s AND Host = %(host)s;')
|
||||
args['user'] = user
|
||||
args['host'] = host
|
||||
if salt.utils.data.is_true(allow_passwordless) and \
|
||||
salt.utils.data.is_true(unix_socket):
|
||||
if host == 'localhost':
|
||||
qry = ('UPDATE mysql.user SET ' + password_column + '='
|
||||
+ password_sql + ', plugin=%(unix_socket)s' +
|
||||
' WHERE User=%(user)s AND Host = %(host)s;')
|
||||
args['unix_socket'] = 'unix_socket'
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
|
||||
qry = ("ALTER USER '" + user + "'@'" + host + "'"
|
||||
" IDENTIFIED BY '" + password + "';")
|
||||
args = {}
|
||||
else:
|
||||
qry = ('UPDATE mysql.user SET ' + password_column + '='
|
||||
+ password_sql + ', plugin=%(unix_socket)s' +
|
||||
' WHERE User=%(user)s AND Host = %(host)s;')
|
||||
args['unix_socket'] = 'unix_socket'
|
||||
else:
|
||||
log.error('Auth via unix_socket can be set only for host=localhost')
|
||||
try:
|
||||
|
@ -1499,7 +1504,7 @@ def user_chpass(user,
|
|||
log.error(err)
|
||||
return False
|
||||
|
||||
if result:
|
||||
if salt.utils.versions.version_cmp(server_version, '8.0.11') >= 0:
|
||||
_execute(cur, 'FLUSH PRIVILEGES;')
|
||||
log.info(
|
||||
'Password for user \'%s\'@\'%s\' has been %s',
|
||||
|
@ -1507,6 +1512,15 @@ def user_chpass(user,
|
|||
'changed' if any((password, password_hash)) else 'cleared'
|
||||
)
|
||||
return True
|
||||
else:
|
||||
if result:
|
||||
_execute(cur, 'FLUSH PRIVILEGES;')
|
||||
log.info(
|
||||
'Password for user \'%s\'@\'%s\' has been %s',
|
||||
user, host,
|
||||
'changed' if any((password, password_hash)) else 'cleared'
|
||||
)
|
||||
return True
|
||||
|
||||
log.info(
|
||||
'Password for user \'%s\'@\'%s\' was not %s',
|
||||
|
@ -2216,3 +2230,29 @@ def showglobal(**connection_args):
|
|||
|
||||
log.debug('%s-->%s', mod, len(rtnv[0]))
|
||||
return rtnv
|
||||
|
||||
|
||||
def verify_login(user, host='localhost', password=None, **connection_args):
|
||||
'''
|
||||
Attempt to login using the provided credentials.
|
||||
If successful, return true. Otherwise, return False.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' mysql.verify_login root localhost password
|
||||
'''
|
||||
# Override the connection args
|
||||
connection_args['connection_user'] = user
|
||||
connection_args['connection_host'] = host
|
||||
connection_args['connection_pass'] = password
|
||||
|
||||
dbc = _connect(**connection_args)
|
||||
if dbc is None:
|
||||
# Clear the mysql.error if unable to connect
|
||||
# if the connection fails, we simply return False
|
||||
if 'mysql.error' in __context__:
|
||||
del __context__['mysql.error']
|
||||
return False
|
||||
return True
|
||||
|
|
|
@ -213,7 +213,7 @@ class CkMinions(object):
|
|||
return {'minions': fnmatch.filter(self._pki_minions(), expr),
|
||||
'missing': []}
|
||||
|
||||
def _check_list_minions(self, expr, greedy): # pylint: disable=unused-argument
|
||||
def _check_list_minions(self, expr, greedy, ignore_missing=False): # pylint: disable=unused-argument
|
||||
'''
|
||||
Return the minions found by looking via a list
|
||||
'''
|
||||
|
@ -221,7 +221,7 @@ class CkMinions(object):
|
|||
expr = [m for m in expr.split(',') if m]
|
||||
minions = self._pki_minions()
|
||||
return {'minions': [x for x in expr if x in minions],
|
||||
'missing': [x for x in expr if x not in minions]}
|
||||
'missing': [] if ignore_missing else [x for x in expr if x not in minions]}
|
||||
|
||||
def _check_pcre_minions(self, expr, greedy): # pylint: disable=unused-argument
|
||||
'''
|
||||
|
@ -572,6 +572,10 @@ class CkMinions(object):
|
|||
engine_args.append(target_info['delimiter'] or ':')
|
||||
engine_args.append(greedy)
|
||||
|
||||
# ignore missing minions for lists if we exclude them with
|
||||
# a 'not'
|
||||
if 'L' == target_info['engine']:
|
||||
engine_args.append(results and results[-1] == '-')
|
||||
_results = engine(*engine_args)
|
||||
results.append(six.text_type(set(_results['minions'])))
|
||||
missing.extend(_results['missing'])
|
||||
|
|
|
@ -41,7 +41,7 @@ class BatchTest(ShellCase):
|
|||
min_ret = "Executing run on [{0}]".format(repr('minion'))
|
||||
|
||||
for item in self.run_salt('minion grains.get os'):
|
||||
if item != 'minion':
|
||||
if item != 'minion:':
|
||||
os_grain = item
|
||||
|
||||
os_grain = os_grain.strip()
|
||||
|
|
|
@ -84,6 +84,50 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin):
|
|||
"UBUNTU_CODENAME": "artful",
|
||||
})
|
||||
|
||||
def test_parse_cpe_name_wfn(self):
|
||||
'''
|
||||
Parse correct CPE_NAME data WFN formatted
|
||||
:return:
|
||||
'''
|
||||
for cpe, cpe_ret in [('cpe:/o:opensuse:leap:15.0',
|
||||
{'phase': None, 'version': '15.0', 'product': 'leap',
|
||||
'vendor': 'opensuse', 'part': 'operating system'}),
|
||||
('cpe:/o:vendor:product:42:beta',
|
||||
{'phase': 'beta', 'version': '42', 'product': 'product',
|
||||
'vendor': 'vendor', 'part': 'operating system'})]:
|
||||
ret = core._parse_cpe_name(cpe)
|
||||
for key in cpe_ret:
|
||||
assert key in ret
|
||||
assert cpe_ret[key] == ret[key]
|
||||
|
||||
def test_parse_cpe_name_v23(self):
|
||||
'''
|
||||
Parse correct CPE_NAME data v2.3 formatted
|
||||
:return:
|
||||
'''
|
||||
for cpe, cpe_ret in [('cpe:2.3:o:microsoft:windows_xp:5.1.601:beta:*:*:*:*:*:*',
|
||||
{'phase': 'beta', 'version': '5.1.601', 'product': 'windows_xp',
|
||||
'vendor': 'microsoft', 'part': 'operating system'}),
|
||||
('cpe:2.3:h:corellian:millenium_falcon:1.0:*:*:*:*:*:*:*',
|
||||
{'phase': None, 'version': '1.0', 'product': 'millenium_falcon',
|
||||
'vendor': 'corellian', 'part': 'hardware'}),
|
||||
('cpe:2.3:*:dark_empire:light_saber:3.0:beta:*:*:*:*:*:*',
|
||||
{'phase': 'beta', 'version': '3.0', 'product': 'light_saber',
|
||||
'vendor': 'dark_empire', 'part': None})]:
|
||||
ret = core._parse_cpe_name(cpe)
|
||||
for key in cpe_ret:
|
||||
assert key in ret
|
||||
assert cpe_ret[key] == ret[key]
|
||||
|
||||
def test_parse_cpe_name_broken(self):
|
||||
'''
|
||||
Parse broken CPE_NAME data
|
||||
:return:
|
||||
'''
|
||||
for cpe in ['cpe:broken', 'cpe:broken:in:all:ways:*:*:*:*',
|
||||
'cpe:x:still:broken:123', 'who:/knows:what:is:here']:
|
||||
assert core._parse_cpe_name(cpe) == {}
|
||||
|
||||
def test_missing_os_release(self):
|
||||
with patch('salt.utils.files.fopen', mock_open(read_data={})):
|
||||
os_release = core._parse_os_release('/etc/os-release', '/usr/lib/os-release')
|
||||
|
|
|
@ -38,29 +38,53 @@ class MySQLTestCase(TestCase, LoaderModuleMockMixin):
|
|||
|
||||
Do it before test_user_create_when_user_exists mocks the user_exists call
|
||||
'''
|
||||
self._test_call(mysql.user_exists,
|
||||
{'sql': ('SELECT User,Host FROM mysql.user WHERE '
|
||||
'User = %(user)s AND Host = %(host)s AND '
|
||||
'Password = PASSWORD(%(password)s)'),
|
||||
'sql_args': {'host': 'localhost',
|
||||
'password': 'BLUECOW',
|
||||
'user': 'mytestuser'
|
||||
}
|
||||
},
|
||||
user='mytestuser',
|
||||
host='localhost',
|
||||
password='BLUECOW'
|
||||
)
|
||||
with patch.object(mysql, 'version', return_value='8.0.10'):
|
||||
self._test_call(mysql.user_exists,
|
||||
{'sql': ('SELECT User,Host FROM mysql.user WHERE '
|
||||
'User = %(user)s AND Host = %(host)s AND '
|
||||
'Password = PASSWORD(%(password)s)'),
|
||||
'sql_args': {'host': 'localhost',
|
||||
'password': 'BLUECOW',
|
||||
'user': 'mytestuser'
|
||||
}
|
||||
},
|
||||
user='mytestuser',
|
||||
host='localhost',
|
||||
password='BLUECOW'
|
||||
)
|
||||
|
||||
with patch.object(mysql, 'version', return_value='8.0.11'):
|
||||
self._test_call(mysql.user_exists,
|
||||
{'sql': ('SELECT User,Host FROM mysql.user WHERE '
|
||||
'User = %(user)s AND Host = %(host)s'),
|
||||
'sql_args': {'host': 'localhost',
|
||||
'user': 'mytestuser'
|
||||
}
|
||||
},
|
||||
user='mytestuser',
|
||||
host='localhost',
|
||||
password='BLUECOW'
|
||||
)
|
||||
|
||||
# test_user_create_when_user_exists(self):
|
||||
# ensure we don't try to create a user when one already exists
|
||||
# mock the version of MySQL
|
||||
with patch.object(mysql, 'version', MagicMock(return_value='8.0.10')):
|
||||
with patch.object(mysql, 'version', return_value='8.0.10'):
|
||||
with patch.object(mysql, 'user_exists', MagicMock(return_value=True)):
|
||||
with patch.dict(mysql.__salt__, {'config.option': MagicMock()}):
|
||||
ret = mysql.user_create('testuser')
|
||||
self.assertEqual(False, ret)
|
||||
|
||||
# test_user_create_when_user_exists(self):
|
||||
# ensure we don't try to create a user when one already exists
|
||||
# mock the version of MySQL
|
||||
with patch.object(mysql, 'version', return_value='8.0.11'):
|
||||
with patch.object(mysql, 'user_exists', MagicMock(return_value=True)):
|
||||
with patch.object(mysql, 'verify_login', MagicMock(return_value=True)):
|
||||
with patch.dict(mysql.__salt__, {'config.option': MagicMock()}):
|
||||
ret = mysql.user_create('testuser')
|
||||
self.assertEqual(False, ret)
|
||||
|
||||
def test_user_create(self):
|
||||
'''
|
||||
Test the creation of a MySQL user in mysql exec module
|
||||
|
@ -82,19 +106,33 @@ class MySQLTestCase(TestCase, LoaderModuleMockMixin):
|
|||
'''
|
||||
connect_mock = MagicMock()
|
||||
with patch.object(mysql, '_connect', connect_mock):
|
||||
with patch.dict(mysql.__salt__, {'config.option': MagicMock()}):
|
||||
mysql.user_chpass('testuser', password='BLUECOW')
|
||||
calls = (
|
||||
call().cursor().execute(
|
||||
'UPDATE mysql.user SET Password=PASSWORD(%(password)s) WHERE User=%(user)s AND Host = %(host)s;',
|
||||
{'password': 'BLUECOW',
|
||||
'user': 'testuser',
|
||||
'host': 'localhost',
|
||||
}
|
||||
),
|
||||
call().cursor().execute('FLUSH PRIVILEGES;'),
|
||||
)
|
||||
connect_mock.assert_has_calls(calls, any_order=True)
|
||||
with patch.object(mysql, 'version', return_value='8.0.10'):
|
||||
with patch.dict(mysql.__salt__, {'config.option': MagicMock()}):
|
||||
mysql.user_chpass('testuser', password='BLUECOW')
|
||||
calls = (
|
||||
call().cursor().execute(
|
||||
'UPDATE mysql.user SET Password=PASSWORD(%(password)s) WHERE User=%(user)s AND Host = %(host)s;',
|
||||
{'password': 'BLUECOW',
|
||||
'user': 'testuser',
|
||||
'host': 'localhost',
|
||||
}
|
||||
),
|
||||
call().cursor().execute('FLUSH PRIVILEGES;'),
|
||||
)
|
||||
connect_mock.assert_has_calls(calls, any_order=True)
|
||||
|
||||
connect_mock = MagicMock()
|
||||
with patch.object(mysql, '_connect', connect_mock):
|
||||
with patch.object(mysql, 'version', return_value='8.0.11'):
|
||||
with patch.dict(mysql.__salt__, {'config.option': MagicMock()}):
|
||||
mysql.user_chpass('testuser', password='BLUECOW')
|
||||
calls = (
|
||||
call().cursor().execute(
|
||||
"ALTER USER 'testuser'@'localhost' IDENTIFIED BY 'BLUECOW';"
|
||||
),
|
||||
call().cursor().execute('FLUSH PRIVILEGES;'),
|
||||
)
|
||||
connect_mock.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_user_remove(self):
|
||||
'''
|
||||
|
|
Loading…
Add table
Reference in a new issue