diff --git a/0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch b/0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch new file mode 100644 index 0000000..8caac82 --- /dev/null +++ b/0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch @@ -0,0 +1,734 @@ +From 2220c5a0ae800988bf83c39b458a8747f01186c0 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Fri, 12 Feb 2016 16:16:12 +0100 +Subject: [PATCH 29/29] Make use of checksum configurable (defaults to MD5, + SHA256 suggested) + +Set config hash_type to SHA1 +Set default hash as SHA1 in config and explain why. +Use hash_type configuration for the Cloud +Use configurable hash_type for general Key fingerprinting +Use SHA1 hash by default +Use SHA1 hash by default in Tomcat module, refactor for support different algorithms +Use SHA1 by default instead of MD5 +Remove SHA1 to SHA265 by default +Add note to the Tomcat module for SHA256 +Remove sha1 to sha265 +Remove SHA1 for SHA256 +Remove SHA1 in favor of SHA256 +Use MD5 hash algorithm by default (until deprecated) +Create a mixin class that will be reused in the similar instances (daemons) +Use mixin for the daemon classes +Report environment failure, if any +Verify if hash_type is using vulnerable algorithms +Standardize logging +Add daemons unit test to verify hash_type settings +Fix PyLint +--- + conf/master | 5 +- + conf/minion | 8 +- + conf/proxy | 9 +- + salt/cli/daemons.py | 83 +++++++++++++----- + salt/cloud/__init__.py | 4 +- + salt/crypt.py | 10 +-- + salt/key.py | 4 +- + salt/modules/key.py | 10 +-- + salt/modules/tomcat.py | 26 ++---- + salt/modules/win_file.py | 2 +- + salt/utils/__init__.py | 13 +-- + salt/utils/cloud.py | 3 +- + tests/unit/daemons_test.py | 209 +++++++++++++++++++++++++++++++++++++++++++++ + 13 files changed, 319 insertions(+), 67 deletions(-) + create mode 100644 tests/unit/daemons_test.py + +diff --git a/conf/master b/conf/master +index 36657e8..cf05ec4 100644 +--- a/conf/master ++++ b/conf/master +@@ -466,9 +466,12 @@ syndic_user: salt + #default_top: base + + # The hash_type is the hash to use when discovering the hash of a file on +-# the master server. The default is md5, but sha1, sha224, sha256, sha384 ++# the master server. The default is md5 but sha1, sha224, sha256, sha384 + # and sha512 are also supported. + # ++# WARNING: While md5 is supported, do not use it due to the high chance ++# of possible collisions and thus security breach. ++# + # Prior to changing this value, the master should be stopped and all Salt + # caches should be cleared. + #hash_type: md5 +diff --git a/conf/minion b/conf/minion +index 2307f70..e17ec61 100644 +--- a/conf/minion ++++ b/conf/minion +@@ -440,12 +440,14 @@ + #fileserver_limit_traversal: False + + # The hash_type is the hash to use when discovering the hash of a file in +-# the local fileserver. The default is md5, but sha1, sha224, sha256, sha384 +-# and sha512 are also supported. ++# the local fileserver. The default is sha256, sha224, sha384 and sha512 are also supported. ++# ++# WARNING: While md5 and sha1 are also supported, do not use it due to the high chance ++# of possible collisions and thus security breach. + # + # Warning: Prior to changing this value, the minion should be stopped and all + # Salt caches should be cleared. +-#hash_type: md5 ++#hash_type: sha256 + + # The Salt pillar is searched for locally if file_client is set to local. If + # this is the case, and pillar data is defined, then the pillar_roots need to +diff --git a/conf/proxy b/conf/proxy +index 472df35..0de6af8 100644 +--- a/conf/proxy ++++ b/conf/proxy +@@ -419,12 +419,15 @@ + #fileserver_limit_traversal: False + + # The hash_type is the hash to use when discovering the hash of a file in +-# the local fileserver. The default is md5, but sha1, sha224, sha256, sha384 +-# and sha512 are also supported. ++# the local fileserver. The default is sha256 but sha224, sha384 and sha512 ++# are also supported. ++# ++# WARNING: While md5 and sha1 are also supported, do not use it due to the high chance ++# of possible collisions and thus security breach. + # + # Warning: Prior to changing this value, the minion should be stopped and all + # Salt caches should be cleared. +-#hash_type: md5 ++#hash_type: sha256 + + # The Salt pillar is searched for locally if file_client is set to local. If + # this is the case, and pillar data is defined, then the pillar_roots need to +diff --git a/salt/cli/daemons.py b/salt/cli/daemons.py +index 7f8b8c8..b0e7b20 100644 +--- a/salt/cli/daemons.py ++++ b/salt/cli/daemons.py +@@ -58,7 +58,50 @@ from salt.exceptions import SaltSystemExit + logger = salt.log.setup.logging.getLogger(__name__) + + +-class Master(parsers.MasterOptionParser): ++class DaemonsMixin(object): # pylint: disable=no-init ++ ''' ++ Uses the same functions for all daemons ++ ''' ++ def verify_hash_type(self): ++ ''' ++ Verify and display a nag-messsage to the log if vulnerable hash-type is used. ++ ++ :return: ++ ''' ++ if self.config['hash_type'].lower() in ['md5', 'sha1']: ++ logger.warning('IMPORTANT: Do not use {h_type} hashing algorithm! Please set "hash_type" to ' ++ 'SHA256 in Salt {d_name} config!'.format( ++ h_type=self.config['hash_type'], d_name=self.__class__.__name__)) ++ ++ def start_log_info(self): ++ ''' ++ Say daemon starting. ++ ++ :return: ++ ''' ++ logger.info('The Salt {d_name} is starting up'.format(d_name=self.__class__.__name__)) ++ ++ def shutdown_log_info(self): ++ ''' ++ Say daemon shutting down. ++ ++ :return: ++ ''' ++ logger.info('The Salt {d_name} is shut down'.format(d_name=self.__class__.__name__)) ++ ++ def environment_failure(self, error): ++ ''' ++ Log environment failure for the daemon and exit with the error code. ++ ++ :param error: ++ :return: ++ ''' ++ logger.exception('Failed to create environment for {d_name}: {reason}'.format( ++ d_name=self.__class__.__name__, reason=error.message)) ++ sys.exit(error.errno) ++ ++ ++class Master(parsers.MasterOptionParser, DaemonsMixin): # pylint: disable=no-init + ''' + Creates a master server + ''' +@@ -114,8 +157,7 @@ class Master(parsers.MasterOptionParser): + for syndic_file in os.listdir(self.config['syndic_dir']): + os.remove(os.path.join(self.config['syndic_dir'], syndic_file)) + except OSError as err: +- logger.exception('Failed to prepare salt environment') +- sys.exit(err.errno) ++ self.environment_failure(err) + + self.setup_logfile_logger() + verify_log(self.config) +@@ -153,17 +195,18 @@ class Master(parsers.MasterOptionParser): + ''' + self.prepare() + if check_user(self.config['user']): +- logger.info('The salt master is starting up') ++ self.verify_hash_type() ++ self.start_log_info() + self.master.start() + + def shutdown(self): + ''' + If sub-classed, run any shutdown operations on this method. + ''' +- logger.info('The salt master is shut down') ++ self.shutdown_log_info() + + +-class Minion(parsers.MinionOptionParser): # pylint: disable=no-init ++class Minion(parsers.MinionOptionParser, DaemonsMixin): # pylint: disable=no-init + ''' + Create a minion server + ''' +@@ -226,8 +269,7 @@ class Minion(parsers.MinionOptionParser): # pylint: disable=no-init + verify_files([logfile], self.config['user']) + os.umask(current_umask) + except OSError as err: +- logger.exception('Failed to prepare salt environment') +- sys.exit(err.errno) ++ self.environment_failure(err) + + self.setup_logfile_logger() + verify_log(self.config) +@@ -273,7 +315,8 @@ class Minion(parsers.MinionOptionParser): # pylint: disable=no-init + try: + self.prepare() + if check_user(self.config['user']): +- logger.info('The salt minion is starting up') ++ self.verify_hash_type() ++ self.start_log_info() + self.minion.tune_in() + finally: + self.shutdown() +@@ -310,10 +353,10 @@ class Minion(parsers.MinionOptionParser): # pylint: disable=no-init + ''' + If sub-classed, run any shutdown operations on this method. + ''' +- logger.info('The salt minion is shut down') ++ self.shutdown_log_info() + + +-class ProxyMinion(parsers.ProxyMinionOptionParser): # pylint: disable=no-init ++class ProxyMinion(parsers.ProxyMinionOptionParser, DaemonsMixin): # pylint: disable=no-init + ''' + Create a proxy minion server + ''' +@@ -388,8 +431,7 @@ class ProxyMinion(parsers.ProxyMinionOptionParser): # pylint: disable=no-init + os.umask(current_umask) + + except OSError as err: +- logger.exception('Failed to prepare salt environment') +- sys.exit(err.errno) ++ self.environment_failure(err) + + self.setup_logfile_logger() + verify_log(self.config) +@@ -431,7 +473,8 @@ class ProxyMinion(parsers.ProxyMinionOptionParser): # pylint: disable=no-init + try: + self.prepare() + if check_user(self.config['user']): +- logger.info('The proxy minion is starting up') ++ self.verify_hash_type() ++ self.start_log_info() + self.minion.tune_in() + except (KeyboardInterrupt, SaltSystemExit) as exc: + logger.warn('Stopping the Salt Proxy Minion') +@@ -449,10 +492,10 @@ class ProxyMinion(parsers.ProxyMinionOptionParser): # pylint: disable=no-init + if hasattr(self, 'minion') and 'proxymodule' in self.minion.opts: + proxy_fn = self.minion.opts['proxymodule'].loaded_base_name + '.shutdown' + self.minion.opts['proxymodule'][proxy_fn](self.minion.opts) +- logger.info('The proxy minion is shut down') ++ self.shutdown_log_info() + + +-class Syndic(parsers.SyndicOptionParser): ++class Syndic(parsers.SyndicOptionParser, DaemonsMixin): # pylint: disable=no-init + ''' + Create a syndic server + ''' +@@ -488,8 +531,7 @@ class Syndic(parsers.SyndicOptionParser): + verify_files([logfile], self.config['user']) + os.umask(current_umask) + except OSError as err: +- logger.exception('Failed to prepare salt environment') +- sys.exit(err.errno) ++ self.environment_failure(err) + + self.setup_logfile_logger() + verify_log(self.config) +@@ -521,7 +563,8 @@ class Syndic(parsers.SyndicOptionParser): + ''' + self.prepare() + if check_user(self.config['user']): +- logger.info('The salt syndic is starting up') ++ self.verify_hash_type() ++ self.start_log_info() + try: + self.syndic.tune_in() + except KeyboardInterrupt: +@@ -532,4 +575,4 @@ class Syndic(parsers.SyndicOptionParser): + ''' + If sub-classed, run any shutdown operations on this method. + ''' +- logger.info('The salt syndic is shut down') ++ self.shutdown_log_info() +diff --git a/salt/cloud/__init__.py b/salt/cloud/__init__.py +index 77186a4..733b403 100644 +--- a/salt/cloud/__init__.py ++++ b/salt/cloud/__init__.py +@@ -2036,7 +2036,7 @@ class Map(Cloud): + master_temp_pub = salt.utils.mkstemp() + with salt.utils.fopen(master_temp_pub, 'w') as mtp: + mtp.write(pub) +- master_finger = salt.utils.pem_finger(master_temp_pub) ++ master_finger = salt.utils.pem_finger(master_temp_pub, sum_type=self.opts['hash_type']) + os.unlink(master_temp_pub) + + if master_profile.get('make_minion', True) is True: +@@ -2121,7 +2121,7 @@ class Map(Cloud): + # mitigate man-in-the-middle attacks + master_pub = os.path.join(self.opts['pki_dir'], 'master.pub') + if os.path.isfile(master_pub): +- master_finger = salt.utils.pem_finger(master_pub) ++ master_finger = salt.utils.pem_finger(master_pub, sum_type=self.opts['hash_type']) + + opts = self.opts.copy() + if self.opts['parallel']: +diff --git a/salt/crypt.py b/salt/crypt.py +index 907ec0c..eaf6d72 100644 +--- a/salt/crypt.py ++++ b/salt/crypt.py +@@ -558,11 +558,11 @@ class AsyncAuth(object): + if self.opts.get('syndic_master', False): # Is syndic + syndic_finger = self.opts.get('syndic_finger', self.opts.get('master_finger', False)) + if syndic_finger: +- if salt.utils.pem_finger(m_pub_fn) != syndic_finger: ++ if salt.utils.pem_finger(m_pub_fn, sum_type=self.opts['hash_type']) != syndic_finger: + self._finger_fail(syndic_finger, m_pub_fn) + else: + if self.opts.get('master_finger', False): +- if salt.utils.pem_finger(m_pub_fn) != self.opts['master_finger']: ++ if salt.utils.pem_finger(m_pub_fn, sum_type=self.opts['hash_type']) != self.opts['master_finger']: + self._finger_fail(self.opts['master_finger'], m_pub_fn) + auth['publish_port'] = payload['publish_port'] + raise tornado.gen.Return(auth) +@@ -1071,11 +1071,11 @@ class SAuth(AsyncAuth): + if self.opts.get('syndic_master', False): # Is syndic + syndic_finger = self.opts.get('syndic_finger', self.opts.get('master_finger', False)) + if syndic_finger: +- if salt.utils.pem_finger(m_pub_fn) != syndic_finger: ++ if salt.utils.pem_finger(m_pub_fn, sum_type=self.opts['hash_type']) != syndic_finger: + self._finger_fail(syndic_finger, m_pub_fn) + else: + if self.opts.get('master_finger', False): +- if salt.utils.pem_finger(m_pub_fn) != self.opts['master_finger']: ++ if salt.utils.pem_finger(m_pub_fn, sum_type=self.opts['hash_type']) != self.opts['master_finger']: + self._finger_fail(self.opts['master_finger'], m_pub_fn) + auth['publish_port'] = payload['publish_port'] + return auth +@@ -1089,7 +1089,7 @@ class SAuth(AsyncAuth): + 'this minion is not subject to a man-in-the-middle attack.' + .format( + finger, +- salt.utils.pem_finger(master_key) ++ salt.utils.pem_finger(master_key, sum_type=self.opts['hash_type']) + ) + ) + sys.exit(42) +diff --git a/salt/key.py b/salt/key.py +index 08086a0..e4cb4eb 100644 +--- a/salt/key.py ++++ b/salt/key.py +@@ -933,7 +933,7 @@ class Key(object): + path = os.path.join(self.opts['pki_dir'], key) + else: + path = os.path.join(self.opts['pki_dir'], status, key) +- ret[status][key] = salt.utils.pem_finger(path) ++ ret[status][key] = salt.utils.pem_finger(path, sum_type=self.opts['hash_type']) + return ret + + def finger_all(self): +@@ -948,7 +948,7 @@ class Key(object): + path = os.path.join(self.opts['pki_dir'], key) + else: + path = os.path.join(self.opts['pki_dir'], status, key) +- ret[status][key] = salt.utils.pem_finger(path) ++ ret[status][key] = salt.utils.pem_finger(path, sum_type=self.opts['hash_type']) + return ret + + +diff --git a/salt/modules/key.py b/salt/modules/key.py +index 12762df..3e16c2d 100644 +--- a/salt/modules/key.py ++++ b/salt/modules/key.py +@@ -21,9 +21,8 @@ def finger(): + + salt '*' key.finger + ''' +- return salt.utils.pem_finger( +- os.path.join(__opts__['pki_dir'], 'minion.pub') +- ) ++ return salt.utils.pem_finger(os.path.join(__opts__['pki_dir'], 'minion.pub'), ++ sum_type=__opts__.get('hash_type', 'md5')) + + + def finger_master(): +@@ -36,6 +35,5 @@ def finger_master(): + + salt '*' key.finger_master + ''' +- return salt.utils.pem_finger( +- os.path.join(__opts__['pki_dir'], 'minion_master.pub') +- ) ++ return salt.utils.pem_finger(os.path.join(__opts__['pki_dir'], 'minion_master.pub'), ++ sum_type=__opts__.get('hash_type', 'md5')) +diff --git a/salt/modules/tomcat.py b/salt/modules/tomcat.py +index d3df2dc..4a7f0eb 100644 +--- a/salt/modules/tomcat.py ++++ b/salt/modules/tomcat.py +@@ -610,7 +610,7 @@ def deploy_war(war, + + def passwd(passwd, + user='', +- alg='md5', ++ alg='sha1', + realm=None): + ''' + This function replaces the $CATALINA_HOME/bin/digest.sh script +@@ -625,23 +625,15 @@ def passwd(passwd, + salt '*' tomcat.passwd secret tomcat sha1 + salt '*' tomcat.passwd secret tomcat sha1 'Protected Realm' + ''' +- if alg == 'md5': +- m = hashlib.md5() +- elif alg == 'sha1': +- m = hashlib.sha1() +- else: +- return False +- +- if realm: +- m.update('{0}:{1}:{2}'.format( +- user, +- realm, +- passwd, +- )) +- else: +- m.update(passwd) ++ # Shouldn't it be SHA265 instead of SHA1? ++ digest = hasattr(hashlib, alg) and getattr(hashlib, alg) or None ++ if digest: ++ if realm: ++ digest.update('{0}:{1}:{2}'.format(user, realm, passwd,)) ++ else: ++ digest.update(passwd) + +- return m.hexdigest() ++ return digest and digest.hexdigest() or False + + + # Non-Manager functions +diff --git a/salt/modules/win_file.py b/salt/modules/win_file.py +index 7911bfc..5ea31ae 100644 +--- a/salt/modules/win_file.py ++++ b/salt/modules/win_file.py +@@ -842,7 +842,7 @@ def chgrp(path, group): + return None + + +-def stats(path, hash_type='md5', follow_symlinks=True): ++def stats(path, hash_type='sha256', follow_symlinks=True): + ''' + Return a dict containing the stats for a given file + +diff --git a/salt/utils/__init__.py b/salt/utils/__init__.py +index c6a3fd3..4e40caf 100644 +--- a/salt/utils/__init__.py ++++ b/salt/utils/__init__.py +@@ -858,10 +858,11 @@ def path_join(*parts): + )) + + +-def pem_finger(path=None, key=None, sum_type='md5'): ++def pem_finger(path=None, key=None, sum_type='sha256'): + ''' + Pass in either a raw pem string, or the path on disk to the location of a +- pem file, and the type of cryptographic hash to use. The default is md5. ++ pem file, and the type of cryptographic hash to use. The default is SHA256. ++ + The fingerprint of the pem will be returned. + + If neither a key nor a path are passed in, a blank string will be returned. +@@ -1979,7 +1980,7 @@ def safe_walk(top, topdown=True, onerror=None, followlinks=True, _seen=None): + yield top, dirs, nondirs + + +-def get_hash(path, form='md5', chunk_size=65536): ++def get_hash(path, form='sha256', chunk_size=65536): + ''' + Get the hash sum of a file + +@@ -1989,10 +1990,10 @@ def get_hash(path, form='md5', chunk_size=65536): + ``get_sum`` cannot really be trusted since it is vulnerable to + collisions: ``get_sum(..., 'xyz') == 'Hash xyz not supported'`` + ''' +- try: +- hash_type = getattr(hashlib, form) +- except (AttributeError, TypeError): ++ hash_type = hasattr(hashlib, form) and getattr(hashlib, form) or None ++ if hash_type is None: + raise ValueError('Invalid hash type: {0}'.format(form)) ++ + with salt.utils.fopen(path, 'rb') as ifile: + hash_obj = hash_type() + # read the file in in chunks, not the entire file +diff --git a/salt/utils/cloud.py b/salt/utils/cloud.py +index d546e51..7a21166 100644 +--- a/salt/utils/cloud.py ++++ b/salt/utils/cloud.py +@@ -2421,6 +2421,7 @@ def init_cachedir(base=None): + + def request_minion_cachedir( + minion_id, ++ opts=None, + fingerprint='', + pubkey=None, + provider=None, +@@ -2440,7 +2441,7 @@ def request_minion_cachedir( + + if not fingerprint: + if pubkey is not None: +- fingerprint = salt.utils.pem_finger(key=pubkey) ++ fingerprint = salt.utils.pem_finger(key=pubkey, sum_type=(opts and opts.get('hash_type') or 'sha256')) + + init_cachedir(base) + +diff --git a/tests/unit/daemons_test.py b/tests/unit/daemons_test.py +new file mode 100644 +index 0000000..47d5e8a +--- /dev/null ++++ b/tests/unit/daemons_test.py +@@ -0,0 +1,209 @@ ++# -*- coding: utf-8 -*- ++''' ++ :codeauthor: :email:`Bo Maryniuk ` ++''' ++ ++# Import python libs ++from __future__ import absolute_import ++ ++# Import Salt Testing libs ++from salttesting import TestCase, skipIf ++from salttesting.helpers import ensure_in_syspath ++from salttesting.mock import patch, MagicMock, NO_MOCK, NO_MOCK_REASON ++ ++ensure_in_syspath('../') ++ ++# Import Salt libs ++import integration ++from salt.cli import daemons ++ ++ ++class LoggerMock(object): ++ ''' ++ Logger data collector ++ ''' ++ ++ def __init__(self): ++ ''' ++ init ++ :return: ++ ''' ++ self.reset() ++ ++ def reset(self): ++ ''' ++ Reset values ++ ++ :return: ++ ''' ++ self.last_message = self.last_type = None ++ ++ def info(self, data): ++ ''' ++ Collects the data from the logger of info type. ++ ++ :param data: ++ :return: ++ ''' ++ self.last_message = data ++ self.last_type = 'info' ++ ++ def warning(self, data): ++ ''' ++ Collects the data from the logger of warning type. ++ ++ :param data: ++ :return: ++ ''' ++ self.last_message = data ++ self.last_type = 'warning' ++ ++ ++@skipIf(NO_MOCK, NO_MOCK_REASON) ++class DaemonsStarterTestCase(TestCase, integration.SaltClientTestCaseMixIn): ++ ''' ++ Unit test for the daemons starter classes. ++ ''' ++ ++ def test_master_daemon_hash_type_verified(self): ++ ''' ++ Verify if Master is verifying hash_type config option. ++ ++ :return: ++ ''' ++ def _create_master(): ++ ''' ++ Create master instance ++ :return: ++ ''' ++ master = daemons.Master() ++ master.config = {'user': 'dummy', 'hash_type': alg} ++ for attr in ['master', 'start_log_info', 'prepare']: ++ setattr(master, attr, MagicMock()) ++ ++ return master ++ ++ _logger = LoggerMock() ++ with patch('salt.cli.daemons.check_user', MagicMock(return_value=True)): ++ with patch('salt.cli.daemons.logger', _logger): ++ for alg in ['md5', 'sha1']: ++ _create_master().start() ++ self.assertEqual(_logger.last_type, 'warning') ++ self.assertTrue(_logger.last_message) ++ self.assertTrue(_logger.last_message.find('Do not use {alg}'.format(alg=alg)) > -1) ++ ++ _logger.reset() ++ ++ for alg in ['sha224', 'sha256', 'sha384', 'sha512']: ++ _create_master().start() ++ self.assertEqual(_logger.last_type, None) ++ self.assertFalse(_logger.last_message) ++ ++ def test_minion_daemon_hash_type_verified(self): ++ ''' ++ Verify if Minion is verifying hash_type config option. ++ ++ :return: ++ ''' ++ ++ def _create_minion(): ++ ''' ++ Create minion instance ++ :return: ++ ''' ++ obj = daemons.Minion() ++ obj.config = {'user': 'dummy', 'hash_type': alg} ++ for attr in ['minion', 'start_log_info', 'prepare', 'shutdown']: ++ setattr(obj, attr, MagicMock()) ++ ++ return obj ++ ++ _logger = LoggerMock() ++ with patch('salt.cli.daemons.check_user', MagicMock(return_value=True)): ++ with patch('salt.cli.daemons.logger', _logger): ++ for alg in ['md5', 'sha1']: ++ _create_minion().start() ++ self.assertEqual(_logger.last_type, 'warning') ++ self.assertTrue(_logger.last_message) ++ self.assertTrue(_logger.last_message.find('Do not use {alg}'.format(alg=alg)) > -1) ++ ++ _logger.reset() ++ ++ for alg in ['sha224', 'sha256', 'sha384', 'sha512']: ++ _create_minion().start() ++ self.assertEqual(_logger.last_type, None) ++ self.assertFalse(_logger.last_message) ++ ++ def test_proxy_minion_daemon_hash_type_verified(self): ++ ''' ++ Verify if ProxyMinion is verifying hash_type config option. ++ ++ :return: ++ ''' ++ ++ def _create_proxy_minion(): ++ ''' ++ Create proxy minion instance ++ :return: ++ ''' ++ obj = daemons.ProxyMinion() ++ obj.config = {'user': 'dummy', 'hash_type': alg} ++ for attr in ['minion', 'start_log_info', 'prepare', 'shutdown']: ++ setattr(obj, attr, MagicMock()) ++ ++ return obj ++ ++ _logger = LoggerMock() ++ with patch('salt.cli.daemons.check_user', MagicMock(return_value=True)): ++ with patch('salt.cli.daemons.logger', _logger): ++ for alg in ['md5', 'sha1']: ++ _create_proxy_minion().start() ++ self.assertEqual(_logger.last_type, 'warning') ++ self.assertTrue(_logger.last_message) ++ self.assertTrue(_logger.last_message.find('Do not use {alg}'.format(alg=alg)) > -1) ++ ++ _logger.reset() ++ ++ for alg in ['sha224', 'sha256', 'sha384', 'sha512']: ++ _create_proxy_minion().start() ++ self.assertEqual(_logger.last_type, None) ++ self.assertFalse(_logger.last_message) ++ ++ def test_syndic_daemon_hash_type_verified(self): ++ ''' ++ Verify if Syndic is verifying hash_type config option. ++ ++ :return: ++ ''' ++ ++ def _create_syndic(): ++ ''' ++ Create syndic instance ++ :return: ++ ''' ++ obj = daemons.Syndic() ++ obj.config = {'user': 'dummy', 'hash_type': alg} ++ for attr in ['syndic', 'start_log_info', 'prepare', 'shutdown']: ++ setattr(obj, attr, MagicMock()) ++ ++ return obj ++ ++ _logger = LoggerMock() ++ with patch('salt.cli.daemons.check_user', MagicMock(return_value=True)): ++ with patch('salt.cli.daemons.logger', _logger): ++ for alg in ['md5', 'sha1']: ++ _create_syndic().start() ++ self.assertEqual(_logger.last_type, 'warning') ++ self.assertTrue(_logger.last_message) ++ self.assertTrue(_logger.last_message.find('Do not use {alg}'.format(alg=alg)) > -1) ++ ++ _logger.reset() ++ ++ for alg in ['sha224', 'sha256', 'sha384', 'sha512']: ++ _create_syndic().start() ++ self.assertEqual(_logger.last_type, None) ++ self.assertFalse(_logger.last_message) ++ ++if __name__ == '__main__': ++ from integration import run_tests ++ run_tests(DaemonsStarterTestCase, needs_daemon=False) +-- +2.7.2 + diff --git a/0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch b/0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch new file mode 100644 index 0000000..45c7eaa --- /dev/null +++ b/0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch @@ -0,0 +1,228 @@ +From 79d5477cfa5e85d2480bb07e49ecaeff423f5238 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Thu, 10 Mar 2016 13:25:20 +0100 +Subject: [PATCH 30/33] Bugfix: on SLE11 series base product reported as + additional + +Add SLE11 product info snapshot, rename previous + +Update test case to cover SLE11 and SLE12 +--- + salt/modules/zypper.py | 2 +- + .../unit/modules/zypp/zypper-products-sle11sp3.xml | 37 +++++++++++++++ + .../unit/modules/zypp/zypper-products-sle12sp1.xml | 37 +++++++++++++++ + tests/unit/modules/zypp/zypper-products.xml | 37 --------------- + tests/unit/modules/zypper_test.py | 52 +++++++++++++--------- + 5 files changed, 107 insertions(+), 58 deletions(-) + create mode 100644 tests/unit/modules/zypp/zypper-products-sle11sp3.xml + create mode 100644 tests/unit/modules/zypp/zypper-products-sle12sp1.xml + delete mode 100644 tests/unit/modules/zypp/zypper-products.xml + +diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py +index d6628aa..1c6b31d 100644 +--- a/salt/modules/zypper.py ++++ b/salt/modules/zypper.py +@@ -1373,7 +1373,7 @@ def list_products(all=False, refresh=False): + for prd in doc.getElementsByTagName('product-list')[0].getElementsByTagName('product'): + p_nfo = dict() + for k_p_nfo, v_p_nfo in prd.attributes.items(): +- p_nfo[k_p_nfo] = k_p_nfo not in ['isbase', 'installed'] and v_p_nfo or v_p_nfo == 'true' ++ p_nfo[k_p_nfo] = k_p_nfo not in ['isbase', 'installed'] and v_p_nfo or v_p_nfo in ['true', '1'] + p_nfo['eol'] = prd.getElementsByTagName('endoflife')[0].getAttribute('text') + p_nfo['eol_t'] = int(prd.getElementsByTagName('endoflife')[0].getAttribute('time_t')) + p_nfo['description'] = " ".join( +diff --git a/tests/unit/modules/zypp/zypper-products-sle11sp3.xml b/tests/unit/modules/zypp/zypper-products-sle11sp3.xml +new file mode 100644 +index 0000000..89a85e3 +--- /dev/null ++++ b/tests/unit/modules/zypp/zypper-products-sle11sp3.xml +@@ -0,0 +1,37 @@ ++ ++ ++Refreshing service 'nu_novell_com'. ++Loading repository data... ++Reading installed packages... ++ ++0x7ffdb538e948SUSE Linux Enterprise offers a comprehensive ++ suite of products built on a single code base. ++ The platform addresses business needs from ++ the smallest thin-client devices to the world’s ++ most powerful high-performance computing ++ and mainframe servers. SUSE Linux Enterprise ++ offers common management tools and technology ++ certifications across the platform, and ++ each product is enterprise-class. ++0x7ffdb538e948SUSE Linux Enterprise offers a comprehensive ++ suite of products built on a single code base. ++ The platform addresses business needs from ++ the smallest thin-client devices to the world’s ++ most powerful high-performance computing ++ and mainframe servers. SUSE Linux Enterprise ++ offers common management tools and technology ++ certifications across the platform, and ++ each product is enterprise-class. ++0x7ffdb538e948SUSE Linux Enterprise offers a comprehensive ++ suite of products built on a single code base. ++ The platform addresses business needs from ++ the smallest thin-client devices to the world’s ++ most powerful high-performance computing ++ and mainframe servers. SUSE Linux Enterprise ++ offers common management tools and technology ++ certifications across the platform, and ++ each product is enterprise-class. ++0x7ffdb538e948SUSE Manager Server appliance ++0x7ffdb538e948SUSE Manager Server appliance ++ ++ +diff --git a/tests/unit/modules/zypp/zypper-products-sle12sp1.xml b/tests/unit/modules/zypp/zypper-products-sle12sp1.xml +new file mode 100644 +index 0000000..1a50363 +--- /dev/null ++++ b/tests/unit/modules/zypp/zypper-products-sle12sp1.xml +@@ -0,0 +1,37 @@ ++ ++ ++Loading repository data... ++Reading installed packages... ++ ++SUSE Linux Enterprise offers a comprehensive ++ suite of products built on a single code base. ++ The platform addresses business needs from ++ the smallest thin-client devices to the world's ++ most powerful high-performance computing ++ and mainframe servers. SUSE Linux Enterprise ++ offers common management tools and technology ++ certifications across the platform, and ++ each product is enterprise-class. ++extensionSUSE Manager Proxies extend large and/or geographically ++dispersed SUSE Manager environments to reduce load on the SUSE Manager ++Server, lower bandwidth needs, and provide faster local ++updates. ++extensionSUSE Manager lets you efficiently manage physical, virtual, ++and cloud-based Linux systems. It provides automated and cost-effective ++configuration and software management, asset management, and system ++provisioning. ++extension<p> ++ SUSE Manager Tools provide packages required to connect to a ++ SUSE Manager Server. ++ <p> ++SUSE Linux Enterprise offers a comprehensive ++ suite of products built on a single code base. ++ The platform addresses business needs from ++ the smallest thin-client devices to the world's ++ most powerful high-performance computing ++ and mainframe servers. SUSE Linux Enterprise ++ offers common management tools and technology ++ certifications across the platform, and ++ each product is enterprise-class. ++ ++ +diff --git a/tests/unit/modules/zypp/zypper-products.xml b/tests/unit/modules/zypp/zypper-products.xml +deleted file mode 100644 +index 1a50363..0000000 +--- a/tests/unit/modules/zypp/zypper-products.xml ++++ /dev/null +@@ -1,37 +0,0 @@ +- +- +-Loading repository data... +-Reading installed packages... +- +-SUSE Linux Enterprise offers a comprehensive +- suite of products built on a single code base. +- The platform addresses business needs from +- the smallest thin-client devices to the world's +- most powerful high-performance computing +- and mainframe servers. SUSE Linux Enterprise +- offers common management tools and technology +- certifications across the platform, and +- each product is enterprise-class. +-extensionSUSE Manager Proxies extend large and/or geographically +-dispersed SUSE Manager environments to reduce load on the SUSE Manager +-Server, lower bandwidth needs, and provide faster local +-updates. +-extensionSUSE Manager lets you efficiently manage physical, virtual, +-and cloud-based Linux systems. It provides automated and cost-effective +-configuration and software management, asset management, and system +-provisioning. +-extension<p> +- SUSE Manager Tools provide packages required to connect to a +- SUSE Manager Server. +- <p> +-SUSE Linux Enterprise offers a comprehensive +- suite of products built on a single code base. +- The platform addresses business needs from +- the smallest thin-client devices to the world's +- most powerful high-performance computing +- and mainframe servers. SUSE Linux Enterprise +- offers common management tools and technology +- certifications across the platform, and +- each product is enterprise-class. +- +- +diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py +index f89d18f..5c4eb67 100644 +--- a/tests/unit/modules/zypper_test.py ++++ b/tests/unit/modules/zypper_test.py +@@ -150,26 +150,38 @@ class ZypperTestCase(TestCase): + ''' + List products test. + ''' +- ref_out = { +- 'retcode': 0, +- 'stdout': get_test_data('zypper-products.xml') +- } +- with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}): +- products = zypper.list_products() +- self.assertEqual(len(products), 5) +- self.assertEqual(['SLES', 'SLES', 'SUSE-Manager-Proxy', 'SUSE-Manager-Server', 'sle-manager-tools-beta'], +- sorted([prod['name'] for prod in products])) +- self.assertIn('SUSE LLC ', [product['vendor'] for product in products]) +- self.assertEqual([False, False, False, False, True], +- sorted([product['isbase'] for product in products])) +- self.assertEqual([False, False, False, False, True], +- sorted([product['installed'] for product in products])) +- self.assertEqual(['0', '0', '0', '0', '0'], +- sorted([product['release'] for product in products])) +- self.assertEqual([False, False, False, False, u'sles'], +- sorted([product['productline'] for product in products])) +- self.assertEqual([1509408000, 1522454400, 1522454400, 1730332800, 1730332800], +- sorted([product['eol_t'] for product in products])) ++ for filename, test_data in { ++ 'zypper-products-sle12sp1.xml': { ++ 'name': ['SLES', 'SLES', 'SUSE-Manager-Proxy', ++ 'SUSE-Manager-Server', 'sle-manager-tools-beta'], ++ 'vendor': 'SUSE LLC ', ++ 'release': ['0', '0', '0', '0', '0'], ++ 'productline': [False, False, False, False, 'sles'], ++ 'eol_t': [1509408000, 1522454400, 1522454400, 1730332800, 1730332800], ++ 'isbase': [False, False, False, False, True], ++ 'installed': [False, False, False, False, True], ++ }, ++ 'zypper-products-sle11sp3.xml': { ++ 'name': ['SUSE-Manager-Server', 'SUSE-Manager-Server', ++ 'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES-SP4-migration'], ++ 'vendor': 'SUSE LINUX Products GmbH, Nuernberg, Germany', ++ 'release': ['1.138', '1.2', '1.2', '1.201', '1.4'], ++ 'productline': [False, False, False, False, 'manager'], ++ 'eol_t': [0, 0, 0, 0, 0], ++ 'isbase': [False, False, False, False, True], ++ 'installed': [False, False, False, False, True], ++ }}.items(): ++ ref_out = { ++ 'retcode': 0, ++ 'stdout': get_test_data(filename) ++ } ++ ++ with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}): ++ products = zypper.list_products() ++ self.assertEqual(len(products), 5) ++ self.assertIn(test_data['vendor'], [product['vendor'] for product in products]) ++ for kwd in ['name', 'isbase', 'installed', 'release', 'productline', 'eol_t']: ++ self.assertEqual(test_data[kwd], sorted([prod[kwd] for prod in products])) + + def test_refresh_db(self): + ''' +-- +2.1.4 + diff --git a/0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch b/0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch new file mode 100644 index 0000000..f0ae1e6 --- /dev/null +++ b/0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch @@ -0,0 +1,42 @@ +From d3d7d20b569ad1ae5bc8a7ba1ac6652aa2e47ec5 Mon Sep 17 00:00:00 2001 +From: rallytime +Date: Tue, 23 Feb 2016 17:20:47 -0700 +Subject: [PATCH 31/33] Only use LONGSIZE in rpm.info if available. Otherwise, + use SIZE. + +Fixes #31366 +--- + salt/modules/rpm.py | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py +index 51c72c9..cdf91a6 100644 +--- a/salt/modules/rpm.py ++++ b/salt/modules/rpm.py +@@ -422,6 +422,14 @@ def info(*packages, **attr): + salt '*' lowpkg.info apache2 bash attr=version + salt '*' lowpkg.info apache2 bash attr=version,build_date_iso,size + ''' ++ # LONGSIZE is not a valid tag for all versions of rpm. If LONGSIZE isn't ++ # available, then we can just use SIZE for older versions. See Issue #31366. ++ rpm_tags = __salt__['cmd.run_all']('rpm --querytags') ++ rpm_tags = rpm_tags.get('stdout').split('\n') ++ if 'LONGSIZE' in rpm_tags: ++ size_tag = '%{LONGSIZE}' ++ else: ++ size_tag = '%{SIZE}' + + cmd = packages and "rpm -q {0}".format(' '.join(packages)) or "rpm -qa" + +@@ -440,7 +448,7 @@ def info(*packages, **attr): + "build_host": "build_host: %{BUILDHOST}\\n", + "group": "group: %{GROUP}\\n", + "source_rpm": "source_rpm: %{SOURCERPM}\\n", +- "size": "size: %{LONGSIZE}\\n", ++ "size": "size: " + size_tag + "\\n", + "arch": "arch: %{ARCH}\\n", + "license": "%|LICENSE?{license: %{LICENSE}\\n}|", + "signature": "signature: %|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:" +-- +2.1.4 + diff --git a/0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch b/0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch new file mode 100644 index 0000000..ef4c8e7 --- /dev/null +++ b/0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch @@ -0,0 +1,26 @@ +From 5c0c3da01e3e64e7614d9d3cc52d8d9c18a06230 Mon Sep 17 00:00:00 2001 +From: rallytime +Date: Tue, 23 Feb 2016 17:26:52 -0700 +Subject: [PATCH 32/33] Add error check when retcode is 0, but stderr is + present + +--- + salt/modules/rpm.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py +index cdf91a6..00cbd5d 100644 +--- a/salt/modules/rpm.py ++++ b/salt/modules/rpm.py +@@ -485,6 +485,8 @@ def info(*packages, **attr): + if 'stderr' in call: + comment += (call['stderr'] or call['stdout']) + raise CommandExecutionError('{0}'.format(comment)) ++ elif 'error' in call['stderr']: ++ raise CommandExecutionError(call['stderr']) + else: + out = call['stdout'] + +-- +2.1.4 + diff --git a/0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch b/0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch new file mode 100644 index 0000000..19eb63c --- /dev/null +++ b/0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch @@ -0,0 +1,39 @@ +From ba08f6714222622467215c23c8284f992830e047 Mon Sep 17 00:00:00 2001 +From: Richard McIntosh +Date: Thu, 10 Mar 2016 16:46:14 +0100 +Subject: [PATCH 33/33] fixing init system dectection on sles 11, refs #31617 + +--- + salt/modules/rh_service.py | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/salt/modules/rh_service.py b/salt/modules/rh_service.py +index 910a75d..c8ebb52 100644 +--- a/salt/modules/rh_service.py ++++ b/salt/modules/rh_service.py +@@ -60,14 +60,19 @@ def __virtual__(): + if __grains__['os'] in enable: + if __grains__['os'] == 'XenServer': + return __virtualname__ ++ ++ if __grains__['os'] == 'SUSE': ++ if str(__grains__['osrelease']).startswith('11'): ++ return __virtualname__ ++ else: ++ return (False, 'Cannot load rh_service module on SUSE > 11') ++ + try: + osrelease = float(__grains__.get('osrelease', 0)) + except ValueError: + return (False, 'Cannot load rh_service module: ' + 'osrelease grain, {0}, not a float,'.format(osrelease)) +- if __grains__['os'] == 'SUSE': +- if osrelease >= 12: +- return (False, 'Cannot load rh_service module on SUSE >= 12') ++ + if __grains__['os'] == 'Fedora': + if osrelease > 15: + return (False, 'Cannot load rh_service module on Fedora >= 15') +-- +2.1.4 + diff --git a/salt.changes b/salt.changes index 02da558..d69c88b 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,28 @@ +------------------------------------------------------------------- +Sat Mar 12 17:08:03 UTC 2016 - mc@suse.com + +- fix detection of base products in SLE11 + * 0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch +- fix rpm info for SLE11 + * 0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch + * 0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch +- fix init system detection for SLE11 + * 0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch + +------------------------------------------------------------------- +Fri Mar 11 14:42:52 UTC 2016 - bmaryniuk@suse.com + +- Re-add corrected patch: + 0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch + +------------------------------------------------------------------- +Wed Mar 9 09:39:09 UTC 2016 - kkaempf@suse.com + +- Make checksum configurable (upstream still wants md5, we + suggest sha256). bsc#955373 + Add: + 0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch + ------------------------------------------------------------------- Fri Mar 4 10:41:52 UTC 2016 - tampakrap@opensuse.org diff --git a/salt.spec b/salt.spec index 74ff9e3..4b2f0ad 100644 --- a/salt.spec +++ b/salt.spec @@ -96,6 +96,15 @@ Patch26: 0026-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch # PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/31629 Patch27: 0027-make-suse-check-consistent-with-rh_service.patch Patch28: 0028-fix-numerical-check-of-osrelease.patch +# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/31162 +Patch29: 0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch +# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/31786 +Patch30: 0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch +# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/31445 +Patch31: 0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch +Patch32: 0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch +# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/31793 +Patch33: 0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: logrotate @@ -466,6 +475,11 @@ cp %{S:1} . %patch26 -p1 %patch27 -p1 %patch28 -p1 +%patch29 -p1 +%patch30 -p1 +%patch31 -p1 +%patch32 -p1 +%patch33 -p1 %build python setup.py --salt-transport=both build