Hello community, here is the log from the commit of package salt for openSUSE:Factory checked in at 2020-11-06 23:43:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/salt (Old) and /work/SRC/openSUSE:Factory/.salt.new.11331 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "salt" Fri Nov 6 23:43:24 2020 rev:111 rq:846425 version:3000.3 Changes: -------- --- /work/SRC/openSUSE:Factory/salt/salt.changes 2020-10-18 16:26:06.288615897 +0200 +++ /work/SRC/openSUSE:Factory/.salt.new.11331/salt.changes 2020-11-06 23:43:27.719452453 +0100 @@ -1,0 +2,34 @@ +Fri Nov 6 09:19:22 UTC 2020 - Pablo Suárez Hernández <pablo.suarezhernan...@suse.com> + +- Set passphrase for salt-ssh keys to empty string (bsc#1178485) + +- Added: + * set-passphrase-for-salt-ssh-keys-to-empty-string-293.patch + +------------------------------------------------------------------- +Wed Nov 4 10:54:32 UTC 2020 - Pablo Suárez Hernández <pablo.suarezhernan...@suse.com> + +- Properly validate eauth credentials and tokens on SSH calls made by Salt API + (bsc#1178319) (bsc#1178362) (bsc#1178361) + (CVE-2020-25592) (CVE-2020-17490) (CVE-2020-16846) + +- Added: + * fix-cve-2020-25592-and-add-tests-bsc-1178319.patch + +------------------------------------------------------------------- +Tue Oct 27 15:23:12 UTC 2020 - Pablo Suárez Hernández <pablo.suarezhernan...@suse.com> + +- Fix novendorchange handling in zypperpkg module + +- Added: + * fix-novendorchange-option-284.patch + +------------------------------------------------------------------- +Tue Oct 20 11:43:49 UTC 2020 - Pablo Suárez Hernández <pablo.suarezhernan...@suse.com> + +- Fix disk.blkid to avoid unexpected keyword argument '__pub_user' (bsc#1177867) + +- Added: + * path-replace-functools.wraps-with-six.wraps-bsc-1177.patch + +------------------------------------------------------------------- New: ---- fix-cve-2020-25592-and-add-tests-bsc-1178319.patch fix-novendorchange-option-284.patch path-replace-functools.wraps-with-six.wraps-bsc-1177.patch set-passphrase-for-salt-ssh-keys-to-empty-string-293.patch ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ salt.spec ++++++ --- /var/tmp/diff_new_pack.I9n5XI/_old 2020-11-06 23:43:30.679446765 +0100 +++ /var/tmp/diff_new_pack.I9n5XI/_new 2020-11-06 23:43:30.683446758 +0100 @@ -363,6 +363,14 @@ Patch139: drop-wrong-mock-from-chroot-unit-test.patch # PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/280 Patch140: ensure-virt.update-stop_on_reboot-is-updated-with-it.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/281 +Patch141: path-replace-functools.wraps-with-six.wraps-bsc-1177.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/58560 +Patch142: fix-novendorchange-option-284.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/58871 +Patch143: fix-cve-2020-25592-and-add-tests-bsc-1178319.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/293 +Patch144: set-passphrase-for-salt-ssh-keys-to-empty-string-293.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: logrotate @@ -1010,6 +1018,10 @@ %patch138 -p1 %patch139 -p1 %patch140 -p1 +%patch141 -p1 +%patch142 -p1 +%patch143 -p1 +%patch144 -p1 %build # Putting /usr/bin at the front of $PATH is needed for RHEL/RES 7. Without this ++++++ _lastrevision ++++++ --- /var/tmp/diff_new_pack.I9n5XI/_old 2020-11-06 23:43:30.751446627 +0100 +++ /var/tmp/diff_new_pack.I9n5XI/_new 2020-11-06 23:43:30.755446619 +0100 @@ -1 +1 @@ -3ce95a1b386927b6f8cb27d1a6421018bebccd9a \ No newline at end of file +24bd64b440c2c3f0f154a1b7f7216de20dc07df8 \ No newline at end of file ++++++ fix-cve-2020-25592-and-add-tests-bsc-1178319.patch ++++++ >From e7514afcba4f57c5cb8599f561fcefdcc3db7314 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" <dwozn...@saltstack.com> Date: Wed, 16 Sep 2020 00:25:10 +0000 Subject: [PATCH] Fix CVE-2020-25592 and add tests (bsc#1178319) Properly validate eauth credentials and tokens on SSH calls made by Salt API (bsc#1178319) (bsc#1178362) (bsc#1178361) (CVE-2020-25592) (CVE-2020-17490) (CVE-2020-16846) --- salt/client/ssh/shell.py | 26 ++- salt/modules/tls.py | 18 +- salt/netapi/__init__.py | 67 ++++++ tests/integration/netapi/test_client.py | 296 +++++++++++++++++++++++- 4 files changed, 388 insertions(+), 19 deletions(-) diff --git a/salt/client/ssh/shell.py b/salt/client/ssh/shell.py index bd55c514ee..27aba7b382 100644 --- a/salt/client/ssh/shell.py +++ b/salt/client/ssh/shell.py @@ -8,6 +8,7 @@ from __future__ import absolute_import, print_function, unicode_literals import re import os import sys +import shlex import time import logging import subprocess @@ -43,10 +44,10 @@ def gen_key(path): ''' Generate a key for use with salt-ssh ''' - cmd = 'ssh-keygen -P "" -f {0} -t rsa -q'.format(path) + cmd = ["ssh-keygen", "-P", '""', "-f", path, "-t", "rsa", "-q"] if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) - subprocess.call(cmd, shell=True) + subprocess.call(cmd) def gen_shell(opts, **kwargs): @@ -289,8 +290,7 @@ class Shell(object): ''' try: proc = salt.utils.nb_popen.NonBlockingPopen( - cmd, - shell=True, + self._split_cmd(cmd), stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) @@ -369,6 +369,21 @@ class Shell(object): return self._run_cmd(cmd) + def _split_cmd(self, cmd): + """ + Split a command string so that it is suitable to pass to Popen without + shell=True. This prevents shell injection attacks in the options passed + to ssh or some other command. + """ + try: + ssh_part, cmd_part = cmd.split("/bin/sh") + except ValueError: + cmd_lst = shlex.split(cmd) + else: + cmd_lst = shlex.split(ssh_part) + cmd_lst.append("/bin/sh {}".format(cmd_part)) + return cmd_lst + def _run_cmd(self, cmd, key_accept=False, passwd_retries=3): ''' Execute a shell command via VT. This is blocking and assumes that ssh @@ -378,8 +393,7 @@ class Shell(object): return '', 'No command or passphrase', 245 term = salt.utils.vt.Terminal( - cmd, - shell=True, + self._split_cmd(cmd), log_stdout=True, log_stdout_level='trace', log_stderr=True, diff --git a/salt/modules/tls.py b/salt/modules/tls.py index af845621a3..116b5fe379 100644 --- a/salt/modules/tls.py +++ b/salt/modules/tls.py @@ -798,12 +798,13 @@ def create_ca(ca_name, if old_key.strip() == keycontent.strip(): write_key = False else: - log.info('Saving old CA ssl key in %s', bck) - with salt.utils.files.fopen(bck, 'w') as bckf: + log.info('Saving old CA ssl key in {0}'.format(bck)) + fp = os.open(bck, os.O_CREAT | os.O_RDWR, 0o600) + with os.fdopen(fp, 'w') as bckf: bckf.write(old_key) - os.chmod(bck, 0o600) if write_key: - with salt.utils.files.fopen(ca_keyp, 'wb') as ca_key: + fp = os.open(ca_keyp, os.O_CREAT | os.O_RDWR, 0o600) + with os.fdopen(fp, 'wb') as ca_key: ca_key.write(salt.utils.stringutils.to_bytes(keycontent)) with salt.utils.files.fopen(certp, 'wb') as ca_crt: @@ -1115,9 +1116,9 @@ def create_csr(ca_name, req.sign(key, salt.utils.stringutils.to_str(digest)) # Write private key and request - with salt.utils.files.fopen('{0}/{1}.key'.format(csr_path, - csr_filename), - 'wb+') as priv_key: + priv_keyp = '{0}/{1}.key'.format(csr_path, csr_filename) + fp = os.open(priv_keyp, os.O_CREAT | os.O_RDWR, 0o600) + with os.fdopen(fp, 'wb+') as priv_key: priv_key.write( salt.utils.stringutils.to_bytes( OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, @@ -1266,7 +1267,8 @@ def create_self_signed_cert(tls_dir='tls', priv_key_path = '{0}/{1}/certs/{2}.key'.format(cert_base_path(), tls_dir, cert_filename) - with salt.utils.files.fopen(priv_key_path, 'wb+') as priv_key: + fp = os.open(priv_key_path, os.O_CREAT | os.O_RDWR, 0o600) + with os.fdopen(fp, 'wb+') as priv_key: priv_key.write( salt.utils.stringutils.to_bytes( OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, diff --git a/salt/netapi/__init__.py b/salt/netapi/__init__.py index 31a24bb420..4e5b6b093a 100644 --- a/salt/netapi/__init__.py +++ b/salt/netapi/__init__.py @@ -3,24 +3,36 @@ Make api awesomeness ''' from __future__ import absolute_import, print_function, unicode_literals + +import copy + # Import Python libs import inspect +import logging import os # Import Salt libs import salt.log # pylint: disable=W0611 +import salt.auth import salt.client import salt.config +import salt.daemons.masterapi import salt.runner import salt.syspaths import salt.wheel import salt.utils.args import salt.client.ssh.client import salt.exceptions +import salt.utils.args +import salt.utils.minions +import salt.wheel +from salt.defaults import DEFAULT_TARGET_DELIM # Import third party libs from salt.ext import six +log = logging.getLogger(__name__) + class NetapiClient(object): ''' @@ -34,6 +46,15 @@ class NetapiClient(object): def __init__(self, opts): self.opts = opts + apiopts = copy.deepcopy(self.opts) + apiopts["enable_ssh_minions"] = True + apiopts["cachedir"] = os.path.join(opts["cachedir"], "saltapi") + if not os.path.exists(apiopts["cachedir"]): + os.makedirs(apiopts["cachedir"]) + self.resolver = salt.auth.Resolver(apiopts) + self.loadauth = salt.auth.LoadAuth(apiopts) + self.key = salt.daemons.masterapi.access_keys(apiopts) + self.ckminions = salt.utils.minions.CkMinions(apiopts) def _is_master_running(self): ''' @@ -55,6 +76,49 @@ class NetapiClient(object): self.opts['sock_dir'], ipc_file)) + def _prep_auth_info(self, clear_load): + sensitive_load_keys = [] + key = None + if "token" in clear_load: + auth_type = "token" + err_name = "TokenAuthenticationError" + sensitive_load_keys = ["token"] + return auth_type, err_name, key, sensitive_load_keys + elif "eauth" in clear_load: + auth_type = "eauth" + err_name = "EauthAuthenticationError" + sensitive_load_keys = ["username", "password"] + return auth_type, err_name, key, sensitive_load_keys + raise salt.exceptions.EauthAuthenticationError( + "No authentication credentials given" + ) + + def _authorize_ssh(self, low): + auth_type, err_name, key, sensitive_load_keys = self._prep_auth_info(low) + auth_check = self.loadauth.check_authentication(low, auth_type, key=key) + auth_list = auth_check.get("auth_list", []) + error = auth_check.get("error") + if error: + raise salt.exceptions.EauthAuthenticationError(error) + delimiter = low.get("kwargs", {}).get("delimiter", DEFAULT_TARGET_DELIM) + _res = self.ckminions.check_minions( + low["tgt"], low.get("tgt_type", "glob"), delimiter + ) + minions = _res.get("minions", list()) + missing = _res.get("missing", list()) + authorized = self.ckminions.auth_check( + auth_list, + low["fun"], + low.get("arg", []), + low["tgt"], + low.get("tgt_type", "glob"), + minions=minions, + ) + if not authorized: + raise salt.exceptions.EauthAuthenticationError( + "Authorization error occurred." + ) + def run(self, low): ''' Execute the specified function in the specified client by passing the @@ -80,6 +144,9 @@ class NetapiClient(object): raise salt.exceptions.EauthAuthenticationError( 'Raw shell option not allowed.') + if low['client'] == 'ssh': + self._authorize_ssh(low) + l_fun = getattr(self, low['client']) f_call = salt.utils.args.format_call(l_fun, low) return l_fun(*f_call.get('args', ()), **f_call.get('kwargs', {})) diff --git a/tests/integration/netapi/test_client.py b/tests/integration/netapi/test_client.py index 08030f31ec..b99bdfe313 100644 --- a/tests/integration/netapi/test_client.py +++ b/tests/integration/netapi/test_client.py @@ -1,26 +1,30 @@ # encoding: utf-8 - # Import Python libs from __future__ import absolute_import, print_function, unicode_literals +import copy import logging import os import time +import salt.config +import salt.netapi +import salt.utils.files +import salt.utils.platform +import salt.utils.pycrypto + # Import Salt Testing libs from tests.support.paths import TMP_CONF_DIR, TMP from tests.support.runtests import RUNTIME_VARS from tests.support.unit import TestCase, skipIf from tests.support.mock import patch -from tests.support.case import SSHCase +from tests.support.case import ModuleCase, SSHCase +from salt.exceptions import EauthAuthenticationError from tests.support.helpers import ( Webserver, SaveRequestsPostHandler, requires_sshd_server ) -# Import Salt libs -import salt.config -import salt.netapi from salt.exceptions import ( EauthAuthenticationError @@ -174,6 +178,10 @@ class NetapiSSHClientTest(SSHCase): ''' opts = salt.config.client_config(os.path.join(TMP_CONF_DIR, 'master')) self.netapi = salt.netapi.NetapiClient(opts) + opts = salt.config.client_config(os.path.join(TMP_CONF_DIR, "master")) + naopts = copy.deepcopy(opts) + naopts["ignore_host_keys"] = True + self.netapi = salt.netapi.NetapiClient(naopts) self.priv_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'key_test') self.rosters = os.path.join(RUNTIME_VARS.TMP_CONF_DIR) @@ -271,3 +279,281 @@ class NetapiSSHClientTest(SSHCase): self.assertEqual(ret, None) self.assertFalse(os.path.exists('badfile.txt')) + + @staticmethod + def cleanup_file(path): + try: + os.remove(path) + except OSError: + pass + + @staticmethod + def cleanup_dir(path): + try: + salt.utils.files.rm_rf(path) + except OSError: + pass + + def test_shell_inject_ssh_priv(self): + """ + Verify CVE-2020-16846 for ssh_priv variable + """ + # ZDI-CAN-11143 + path = "/tmp/test-11143" + self.addCleanup(self.cleanup_file, path) + self.addCleanup(self.cleanup_file, "aaa") + self.addCleanup(self.cleanup_file, "aaa.pub") + self.addCleanup(self.cleanup_dir, "aaa|id>") + low = { + "roster": "cache", + "client": "ssh", + "tgt": "www.zerodayinitiative.com", + "ssh_priv": "aaa|id>{} #".format(path), + "fun": "test.ping", + "eauth": "auto", + "username": "saltdev_auto", + "password": "saltdev", + } + ret = self.netapi.run(low) + self.assertFalse(os.path.exists(path)) + + def test_shell_inject_tgt(self): + """ + Verify CVE-2020-16846 for tgt variable + """ + # ZDI-CAN-11167 + path = "/tmp/test-11167" + self.addCleanup(self.cleanup_file, path) + low = { + "roster": "cache", + "client": "ssh", + "tgt": "root|id>{} #@127.0.0.1".format(path), + "roster_file": "/tmp/salt-tests-tmpdir/config/roaster", + "rosters": "/", + "fun": "test.ping", + "eauth": "auto", + "username": "saltdev_auto", + "password": "saltdev", + } + ret = self.netapi.run(low) + self.assertFalse(os.path.exists(path)) + + def test_shell_inject_ssh_options(self): + """ + Verify CVE-2020-16846 for ssh_options + """ + # ZDI-CAN-11169 + path = "/tmp/test-11169" + self.addCleanup(self.cleanup_file, path) + low = { + "roster": "cache", + "client": "ssh", + "tgt": "127.0.0.1", + "renderer": "cheetah", + "fun": "test.ping", + "eauth": "auto", + "username": "saltdev_auto", + "password": "saltdev", + "roster_file": "/tmp/salt-tests-tmpdir/config/roaster", + "rosters": "/", + "ssh_options": ["|id>{} #".format(path), "lol"], + } + ret = self.netapi.run(low) + self.assertFalse(os.path.exists(path)) + + def test_shell_inject_ssh_port(self): + """ + Verify CVE-2020-16846 for ssh_port variable + """ + # ZDI-CAN-11172 + path = "/tmp/test-11172" + self.addCleanup(self.cleanup_file, path) + low = { + "roster": "cache", + "client": "ssh", + "tgt": "127.0.0.1", + "renderer": "cheetah", + "fun": "test.ping", + "eauth": "auto", + "username": "saltdev_auto", + "password": "saltdev", + "roster_file": "/tmp/salt-tests-tmpdir/config/roaster", + "rosters": "/", + "ssh_port": "hhhhh|id>{} #".format(path), + } + ret = self.netapi.run(low) + self.assertFalse(os.path.exists(path)) + + def test_shell_inject_remote_port_forwards(self): + """ + Verify CVE-2020-16846 for remote_port_forwards variable + """ + # ZDI-CAN-11173 + path = "/tmp/test-1173" + self.addCleanup(self.cleanup_file, path) + low = { + "roster": "cache", + "client": "ssh", + "tgt": "127.0.0.1", + "renderer": "cheetah", + "fun": "test.ping", + "roster_file": "/tmp/salt-tests-tmpdir/config/roaster", + "rosters": "/", + "ssh_remote_port_forwards": "hhhhh|id>{} #, lol".format(path), + "eauth": "auto", + "username": "saltdev_auto", + "password": "saltdev", + } + ret = self.netapi.run(low) + self.assertFalse(os.path.exists(path)) + + +@requires_sshd_server +class NetapiSSHClientAuthTest(SSHCase): + + USERA = "saltdev" + USERA_PWD = "saltdev" + + def setUp(self): + """ + Set up a NetapiClient instance + """ + opts = salt.config.client_config(os.path.join(TMP_CONF_DIR, "master")) + naopts = copy.deepcopy(opts) + naopts["ignore_host_keys"] = True + self.netapi = salt.netapi.NetapiClient(naopts) + + self.priv_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "key_test") + self.rosters = os.path.join(RUNTIME_VARS.TMP_CONF_DIR) + # Initialize salt-ssh + self.run_function("test.ping") + self.mod_case = ModuleCase() + try: + add_user = self.mod_case.run_function( + "user.add", [self.USERA], createhome=False + ) + self.assertTrue(add_user) + if salt.utils.platform.is_darwin(): + hashed_password = self.USERA_PWD + else: + hashed_password = salt.utils.pycrypto.gen_hash(password=self.USERA_PWD) + add_pwd = self.mod_case.run_function( + "shadow.set_password", [self.USERA, hashed_password], + ) + self.assertTrue(add_pwd) + except AssertionError: + self.mod_case.run_function("user.delete", [self.USERA], remove=True) + self.skipTest("Could not add user or password, skipping test") + + def tearDown(self): + del self.netapi + self.mod_case.run_function("user.delete", [self.USERA], remove=True) + + @classmethod + def setUpClass(cls): + cls.post_webserver = Webserver(handler=SaveRequestsPostHandler) + cls.post_webserver.start() + cls.post_web_root = cls.post_webserver.web_root + cls.post_web_handler = cls.post_webserver.handler + + @classmethod + def tearDownClass(cls): + cls.post_webserver.stop() + del cls.post_webserver + + def test_ssh_auth_bypass(self): + """ + CVE-2020-25592 - Bogus eauth raises exception. + """ + low = { + "roster": "cache", + "client": "ssh", + "tgt": "127.0.0.1", + "renderer": "cheetah", + "fun": "test.ping", + "roster_file": "/tmp/salt-tests-tmpdir/config/roaster", + "rosters": "/", + "eauth": "xx", + } + with self.assertRaises(salt.exceptions.EauthAuthenticationError): + ret = self.netapi.run(low) + + def test_ssh_auth_valid(self): + """ + CVE-2020-25592 - Valid eauth works as expected. + """ + low = { + "client": "ssh", + "tgt": "localhost", + "fun": "test.ping", + "roster_file": "roster", + "rosters": [self.rosters], + "ssh_priv": self.priv_file, + "eauth": "pam", + "username": "saltdev", + "password": "saltdev", + } + ret = self.netapi.run(low) + assert "localhost" in ret + assert ret["localhost"]["return"] is True + + def test_ssh_auth_invalid(self): + """ + CVE-2020-25592 - Wrong password raises exception. + """ + low = { + "client": "ssh", + "tgt": "localhost", + "fun": "test.ping", + "roster_file": "roster", + "rosters": [self.rosters], + "ssh_priv": self.priv_file, + "eauth": "pam", + "username": "saltdev", + "password": "notvalidpassword", + } + with self.assertRaises(salt.exceptions.EauthAuthenticationError): + ret = self.netapi.run(low) + + def test_ssh_auth_invalid_acl(self): + """ + CVE-2020-25592 - Eauth ACL enforced. + """ + low = { + "client": "ssh", + "tgt": "localhost", + "fun": "at.at", + "args": ["12:05am", "echo foo"], + "roster_file": "roster", + "rosters": [self.rosters], + "ssh_priv": self.priv_file, + "eauth": "pam", + "username": "saltdev", + "password": "notvalidpassword", + } + with self.assertRaises(salt.exceptions.EauthAuthenticationError): + ret = self.netapi.run(low) + + def test_ssh_auth_token(self): + """ + CVE-2020-25592 - Eauth tokens work as expected. + """ + low = { + "eauth": "pam", + "username": "saltdev", + "password": "saltdev", + } + ret = self.netapi.loadauth.mk_token(low) + assert "token" in ret and ret["token"] + low = { + "client": "ssh", + "tgt": "localhost", + "fun": "test.ping", + "roster_file": "roster", + "rosters": [self.rosters], + "ssh_priv": self.priv_file, + "token": ret["token"], + } + ret = self.netapi.run(low) + assert "localhost" in ret + assert ret["localhost"]["return"] is True -- 2.28.0 ++++++ fix-novendorchange-option-284.patch ++++++ >From f69c1178de003866af412e61e0146597974eec0d Mon Sep 17 00:00:00 2001 From: Martin Seidl <mse...@suse.de> Date: Tue, 27 Oct 2020 16:12:29 +0100 Subject: [PATCH] Fix novendorchange option (#284) * Fixed novendorchange handling in zypperpkg * refactor handling of novendorchange and fix tests --- salt/modules/zypperpkg.py | 19 ++-- tests/unit/modules/test_zypperpkg.py | 150 ++++++++++++++++++++++++--- 2 files changed, 148 insertions(+), 21 deletions(-) diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py index ad11da4ad1..d84a6af6e0 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py @@ -1617,7 +1617,7 @@ def upgrade(refresh=True, dryrun=False, dist_upgrade=False, fromrepo=None, - novendorchange=False, + novendorchange=True, skip_verify=False, no_recommends=False, root=None, @@ -1701,13 +1701,18 @@ def upgrade(refresh=True, log.info('Targeting repos: %s', fromrepo) if dist_upgrade: - if novendorchange: - # TODO: Grains validation should be moved to Zypper class - if __grains__['osrelease_info'][0] > 11: - cmd_update.append('--no-allow-vendor-change') - log.info('Disabling vendor changes') + # TODO: Grains validation should be moved to Zypper class + if __grains__["osrelease_info"][0] > 11: + if novendorchange: + cmd_update.append("--no-allow-vendor-change") + log.info("Disabling vendor changes") else: - log.warning('Disabling vendor changes is not supported on this Zypper version') + cmd_update.append("--allow-vendor-change") + log.info("Enabling vendor changes") + else: + log.warning( + "Enabling/Disabling vendor changes is not supported on this Zypper version" + ) if no_recommends: cmd_update.append('--no-recommends') diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py index a3d20f66d5..8cc84485b5 100644 --- a/tests/unit/modules/test_zypperpkg.py +++ b/tests/unit/modules/test_zypperpkg.py @@ -480,7 +480,11 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): with patch('salt.modules.zypperpkg.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}])): ret = zypper.upgrade(dist_upgrade=True) self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.2"}}) - zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses') + zypper_mock.assert_any_call( + "dist-upgrade", + "--auto-agree-with-licenses", + "--no-allow-vendor-change", + ) with patch('salt.modules.zypperpkg.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}])): ret = zypper.upgrade(dist_upgrade=True, dryrun=True) @@ -488,25 +492,138 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses', '--dry-run', '--debug-solver') - with patch('salt.modules.zypperpkg.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}])): - ret = zypper.upgrade(dist_upgrade=True, dryrun=True, - fromrepo=["Dummy", "Dummy2"], novendorchange=True) - zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses', '--dry-run', - '--from', "Dummy", '--from', 'Dummy2', '--no-allow-vendor-change') - zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses', '--dry-run', - '--from', "Dummy", '--from', 'Dummy2', '--no-allow-vendor-change', - '--debug-solver') - with patch('salt.modules.zypperpkg.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}])): ret = zypper.upgrade(dist_upgrade=False, fromrepo=["Dummy", "Dummy2"], dryrun=False) zypper_mock.assert_any_call('update', '--auto-agree-with-licenses', '--repo', "Dummy", '--repo', 'Dummy2') with patch('salt.modules.zypperpkg.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}])): ret = zypper.upgrade(dist_upgrade=True, fromrepo=["Dummy", "Dummy2"], novendorchange=True) + zypper_mock.assert_any_call( + "dist-upgrade", + "--auto-agree-with-licenses", + "--dry-run", + "--no-allow-vendor-change", + ) + zypper_mock.assert_any_call( + "dist-upgrade", + "--auto-agree-with-licenses", + "--dry-run", + "--no-allow-vendor-change", + ) + + with patch( + "salt.modules.zypperpkg.list_pkgs", + MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), + ): + ret = zypper.upgrade( + dist_upgrade=True, + dryrun=True, + fromrepo=["Dummy", "Dummy2"], + novendorchange=False, + ) + zypper_mock.assert_any_call( + "dist-upgrade", + "--auto-agree-with-licenses", + "--dry-run", + "--from", + "Dummy", + "--from", + "Dummy2", + "--allow-vendor-change", + ) + zypper_mock.assert_any_call( + "dist-upgrade", + "--auto-agree-with-licenses", + "--dry-run", + "--from", + "Dummy", + "--from", + "Dummy2", + "--allow-vendor-change", + "--debug-solver", + ) + + + with patch( + "salt.modules.zypperpkg.list_pkgs", + MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), + ): + ret = zypper.upgrade( + dist_upgrade=True, + dryrun=True, + fromrepo=["Dummy", "Dummy2"], + novendorchange=True, + ) + zypper_mock.assert_any_call( + "dist-upgrade", + "--auto-agree-with-licenses", + "--dry-run", + "--from", + "Dummy", + "--from", + "Dummy2", + "--no-allow-vendor-change", + ) + zypper_mock.assert_any_call( + "dist-upgrade", + "--auto-agree-with-licenses", + "--dry-run", + "--from", + "Dummy", + "--from", + "Dummy2", + "--no-allow-vendor-change", + "--debug-solver", + ) + + with patch( + "salt.modules.zypperpkg.list_pkgs", + MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), + ): + ret = zypper.upgrade( + dist_upgrade=False, fromrepo=["Dummy", "Dummy2"], dryrun=False + ) + zypper_mock.assert_any_call( + "update", + "--auto-agree-with-licenses", + "--repo", + "Dummy", + "--repo", + "Dummy2", + ) + + with patch( + "salt.modules.zypperpkg.list_pkgs", + MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}]), + ): + ret = zypper.upgrade( + dist_upgrade=True, + fromrepo=["Dummy", "Dummy2"], + novendorchange=True, + ) self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.2"}}) zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses', '--from', "Dummy", '--from', 'Dummy2', '--no-allow-vendor-change') + with patch( + "salt.modules.zypperpkg.list_pkgs", + MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}]), + ): + ret = zypper.upgrade( + dist_upgrade=True, + fromrepo=["Dummy", "Dummy2"], + novendorchange=False, + ) + self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.2"}}) + zypper_mock.assert_any_call( + "dist-upgrade", + "--auto-agree-with-licenses", + "--from", + "Dummy", + "--from", + "Dummy2", + "--allow-vendor-change", + ) def test_upgrade_kernel(self): ''' Test kernel package upgrade success. @@ -558,10 +675,15 @@ Repository 'DUMMY' not found by its alias, number, or URI. with patch('salt.modules.zypperpkg.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}])): with self.assertRaises(CommandExecutionError) as cmd_exc: ret = zypper.upgrade(dist_upgrade=True, fromrepo=["DUMMY"]) - self.assertEqual(cmd_exc.exception.info['changes'], {}) - self.assertEqual(cmd_exc.exception.info['result']['stdout'], zypper_out) - zypper_mock.noraise.call.assert_called_with('dist-upgrade', '--auto-agree-with-licenses', - '--from', 'DUMMY') + self.assertEqual(cmd_exc.exception.info["changes"], {}) + self.assertEqual(cmd_exc.exception.info["result"]["stdout"], zypper_out) + zypper_mock.noraise.call.assert_called_with( + "dist-upgrade", + "--auto-agree-with-licenses", + "--from", + "DUMMY", + "--no-allow-vendor-change", + ) def test_upgrade_available(self): ''' -- 2.28.0 ++++++ path-replace-functools.wraps-with-six.wraps-bsc-1177.patch ++++++ >From 9707eab7452a94e64f77ece707c31c37e43e47f2 Mon Sep 17 00:00:00 2001 From: Alberto Planas <apla...@suse.com> Date: Tue, 20 Oct 2020 13:13:24 +0200 Subject: [PATCH] path: replace functools.wraps with six.wraps (bsc#1177867) (#283) Python 2.7 functools.wraps decorator do not add the `__wrapped__` attribute to decorated functions, that is used by Salt to access the original function and deduce the parameters from the signature. This patch uses six.wraps, that add this extra attribute. --- salt/utils/decorators/path.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/salt/utils/decorators/path.py b/salt/utils/decorators/path.py index 8ee7fb1d11..a763c92d22 100644 --- a/salt/utils/decorators/path.py +++ b/salt/utils/decorators/path.py @@ -4,11 +4,10 @@ Decorators for salt.utils.path ''' from __future__ import absolute_import, print_function, unicode_literals -import functools - # Import Salt libs import salt.utils.path from salt.exceptions import CommandNotFoundError +import salt.ext.six def which(exe): @@ -16,7 +15,7 @@ def which(exe): Decorator wrapper for salt.utils.path.which ''' def wrapper(function): - @functools.wraps(function) + @salt.ext.six.wraps(function) def wrapped(*args, **kwargs): if salt.utils.path.which(exe) is None: raise CommandNotFoundError( @@ -34,7 +33,7 @@ def which_bin(exes): Decorator wrapper for salt.utils.path.which_bin ''' def wrapper(function): - @functools.wraps(function) + @salt.ext.six.wraps(function) def wrapped(*args, **kwargs): if salt.utils.path.which_bin(exes) is None: raise CommandNotFoundError( -- 2.28.0 ++++++ set-passphrase-for-salt-ssh-keys-to-empty-string-293.patch ++++++ >From 677b7a8881a2e9ebab58cead29b1a6d83850c888 Mon Sep 17 00:00:00 2001 From: Alexander Graul <m...@agraul.de> Date: Thu, 5 Nov 2020 16:54:44 +0100 Subject: [PATCH] Set passphrase for salt-ssh keys to empty string (#293) Since the cmd is not passed to a shell anymore, the "" are taken literally and not as an empty string. Bugzilla report: https://bugzilla.suse.com/show_bug.cgi?id=1178485 --- salt/client/ssh/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/client/ssh/shell.py b/salt/client/ssh/shell.py index 27aba7b382..27ab9f4f1b 100644 --- a/salt/client/ssh/shell.py +++ b/salt/client/ssh/shell.py @@ -44,7 +44,7 @@ def gen_key(path): ''' Generate a key for use with salt-ssh ''' - cmd = ["ssh-keygen", "-P", '""', "-f", path, "-t", "rsa", "-q"] + cmd = ["ssh-keygen", "-P", "", "-f", path, "-t", "rsa", "-q"] if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) subprocess.call(cmd) -- 2.28.0