From 0c358bb3040ea1f1b0b33b52892e37978105b9ff Mon Sep 17 00:00:00 2001 From: Daniel Wozniak Date: Wed, 24 Mar 2021 07:52:25 -0700 Subject: [PATCH] Merge 3003 changes forward to the master branch (#59879) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merge 3002.6 bugfix changes (#59822) * Pass `CI_RUN` as an environment variable to the test run. This allows us to know if we're running the test suite under a CI environment or not and adapt/adjust if needed * Migrate `unit.setup` to PyTest * Backport ae36b15 just for test_install.py * Only skip tests on CI runs * Always store git sha in _version.py during installation * Fix PEP440 compliance. The wheel metadata version 1.2 states that the package version MUST be PEP440 compliant. This means that instead of `3002.2-511-g033c53eccb`, the salt version string should look like `3002.2+511.g033c53eccb`, a post release of `3002.2` ahead by 511 commits with the git sha `033c53eccb` * Fix and migrate `tests/unit/test_version.py` to PyTest * Skip test if `easy_install` is not available * We also need to be PEP440 compliant when there's no git history * Allow extra_filerefs as sanitized kwargs for SSH client * Fix regression on cmd.run when passing tuples as cmd Co-authored-by: Alexander Graul * Add unit tests to ensure cmd.run accepts tuples * Add unit test to check for extra_filerefs on SSH opts * Add changelog file * Fix comment for test case * Fix unit test to avoid failing on Windows * Skip failing test on windows * Fix test to work on Windows * Add all ssh kwargs to sanitize_kwargs method * Run pre-commit * Fix pylint * Fix cmdmod loglevel and module_names tests * Fix pre-commit * Skip ssh tests if binary does not exist * Use setup_loader for cmdmod test * Prevent argument injection in restartcheck * Add changelog for restartcheck fix * docs_3002.6 * Add back tests removed in merge Co-authored-by: Pedro Algarvio Co-authored-by: Megan Wilhite Co-authored-by: Bryce Larson Co-authored-by: Pablo Suárez Hernández Co-authored-by: Alexander Graul Co-authored-by: Frode Gundersen * Remove glance state module in favor of glance_image * update wording in changelog * bump deprecation warning to Silicon. * Updating warnutil version to Phosphorous. * Update salt/modules/keystone.py Co-authored-by: Megan Wilhite * Check $HOMEBREW_PREFIX when linking against libcrypto When loading `libcrypto`, Salt checks for a Homebrew installation of `openssl` at Homebrew's default prefix of `/usr/local`. However, on Apple Silicon Macs, Homebrew's default installation prefix is `/opt/homebrew`. On all platforms, the prefix is configurable. If Salt doesn't find one of those `libcrypto`s, it will fall back on the un-versioned `/usr/lib/libcrypto.dylib`, which will cause the following crash: Application Specific Information: /usr/lib/libcrypto.dylib abort() called Invalid dylib load. Clients should not load the unversioned libcrypto dylib as it does not have a stable ABI. This commit checks $HOMEBREW_PREFIX instead of hard-coding `/usr/local`. * Add test case * Add changelog for 59808 * Add changelog entry * Make _find_libcrypto fail on Big Sur if it can't find a library Right now, if `_find_libcrypto` can't find any externally-managed versions of libcrypto, it will fall back on the pre-Catalina un-versioned system libcrypto. This does not exist on Big Sur and it would be better to raise an exception here rather than crashing later when trying to open it. * Update _find_libcrypto tests This commit simplifies the unit tests for _find_libcrypto by mocking out the host's filesystem and testing the common libcrypto installations (brew, ports, etc.) on Big Sur. It simplifies the tests for falling back on system versions of libcrypto on previous versions of macOS. * Fix description of test_find_libcrypto_with_system_before_catalina * Patch sys.platform for test_rsax931 tests * modules/match: add missing "minion_id" in Pillar example The documented Pillar example for `match.filter_by` lacks the `minion_id` parameter. Without it, the assignment won't work as expected. - fix documentation - add tests: - to prove the misbehavior of the documented example - to prove the proper behaviour when supplying `minion_id` - to ensure some misbehaviour observed with compound matchers doesn't occur * Fix for issue #59773 - When instantiating the loader grab values of grains and pillars if they are NamedLoaderContext instances. - The loader uses a copy of opts. - Impliment deepcopy on NamedLoaderContext instances. * Add changelog for #59773 * _get_initial_pillar function returns pillar * Fix linter issues * Clean up test * Bump deprecation release for neutron * Uncomment Sulfur release name * Removing the _ext_nodes deprecation warning and alias. * Adding changelog. * Renaming changelog file. * Update 59804.removed * Initial pass at fips_mode config option * Fix pre-commit * Fix tests and add changelog * update docs 3003 * update docs 3003 - newline * Fix warts in changelog Co-authored-by: Pedro Algarvio Co-authored-by: Megan Wilhite Co-authored-by: Bryce Larson Co-authored-by: Pablo Suárez Hernández Co-authored-by: Alexander Graul Co-authored-by: Frode Gundersen Co-authored-by: Gareth J. Greenaway Co-authored-by: Gareth J. Greenaway Co-authored-by: Hoa-Long Tam Co-authored-by: krionbsd Co-authored-by: Elias Probst Co-authored-by: Frode Gundersen --- CHANGELOG.md | 50 ++++++++- doc/topics/releases/3000.9.rst | 17 +++ doc/topics/releases/3001.7.rst | 17 +++ doc/topics/releases/3002.6.rst | 24 +++++ doc/topics/releases/3003.rst | 9 +- salt/client/ssh/client.py | 23 ++++ salt/config/__init__.py | 3 + salt/fileclient.py | 10 +- salt/loader.py | 6 ++ salt/loader_context.py | 5 + salt/master.py | 4 - salt/modules/nacl.py | 6 +- salt/modules/restartcheck.py | 11 +- salt/modules/state.py | 2 +- salt/modules/x509.py | 4 +- salt/pillar/nacl.py | 11 +- salt/runners/nacl.py | 6 +- salt/states/x509.py | 5 +- salt/utils/nacl.py | 36 +++---- salt/utils/openstack/neutron.py | 13 +-- salt/utils/rsax931.py | 16 ++- salt/version.py | 2 +- tests/pytests/unit/client/test_ssh.py | 76 ++++++++++++++ tests/pytests/unit/modules/test_nacl.py | 16 +++ tests/pytests/unit/modules/test_state.py | 21 ++++ tests/pytests/unit/pillar/__init__.py | 0 tests/pytests/unit/pillar/test_nacl.py | 16 +++ tests/pytests/unit/runners/__init__.py | 0 tests/pytests/unit/runners/test_nacl.py | 16 +++ tests/pytests/unit/test_loader.py | 21 ++++ tests/pytests/unit/test_loader_context.py | 14 +++ tests/pytests/unit/utils/test_nacl.py | 16 +++ tests/unit/modules/test_restartcheck.py | 47 +++++++++ tests/unit/modules/test_x509.py | 76 +++++++++++--- tests/unit/states/test_x509.py | 122 ++++++++++++++++++++++ tests/unit/test_module_names.py | 1 + tests/unit/utils/test_rsax931.py | 108 ++++++++++++------- 37 files changed, 707 insertions(+), 123 deletions(-) create mode 100644 doc/topics/releases/3000.9.rst create mode 100644 doc/topics/releases/3001.7.rst create mode 100644 doc/topics/releases/3002.6.rst create mode 100644 tests/pytests/unit/modules/test_nacl.py create mode 100644 tests/pytests/unit/modules/test_state.py create mode 100644 tests/pytests/unit/pillar/__init__.py create mode 100644 tests/pytests/unit/pillar/test_nacl.py create mode 100644 tests/pytests/unit/runners/__init__.py create mode 100644 tests/pytests/unit/runners/test_nacl.py create mode 100644 tests/pytests/unit/utils/test_nacl.py create mode 100644 tests/unit/states/test_x509.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 56880880f41c..deab909de786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ Salt 3003 (2021-03-05) Removed ------- +- Removed the deprecated glance state and execution module in favor of the glance_image + state module and the glanceng execution module. (#59079) +- Removing the _ext_nodes deprecation warning and alias to the master_tops function. This change will break compatibility with a Salt master running versions 2017.7.8 and older and Salt minions running versions 3003 and newer. (#59804) - removed the arg `managed_private_key` from 'salt.states.x509.certificate_managed' (#59247) - Drop support for python 3.5 on Windows (#59479) @@ -22,7 +25,6 @@ Deprecated - Added deprecation warning for grains.get_or_set_hash (#59425) - Changed ------- @@ -35,6 +37,9 @@ Changed Fixed ----- +- When instantiating the loader grab values of grains and pillars if + they are NamedLoaderContext instances. (#59773) +- Fixed installation on Apple Silicon Macs by checking $HOMEBREW_PREFIX for `libcrypto` instead of assuming /usr/local. (#59808) - Fix incorrect documentation for pillar_source_merging_strategy (#26396) - Don't iterate through cloud map errors (#34033) - Supress noisy warnings when very old pyzmq is used. (#50327) @@ -135,6 +140,7 @@ Fixed Added ----- +- Added "fips_mode" config option to master and minion configs. (#59427) - Adding the ability to clear and show the pillar cache enabled when pillar_cache is True. (#37080) - SCRAM-SHA-256 support for PostgreSQL passwords. Pass encrypted=scram-sha-256 to the postgres_user.present (or postgres_group.present) state. (#51271) @@ -169,6 +175,24 @@ Added binary ELF files in the package. (#59569) +Salt 3002.6 (2021-03-10) +======================== + +Changed +------- + +- Store git sha in salt/_version.py when installing from a tag so it can be found if needed later. (#59137) + +Fixed +----- + +- Fix argument injection bug in restartcheck.restartcheck. This change hardens + the fix for CVE-2020-28243. (#200) +- Allow "extra_filerefs" as sanitized kwargs for SSH client. + Fix regression on "cmd.run" when passing tuples as cmd. (#59664) +- Allow all ssh kwargs as sanitized kwargs for SSH client. (#59748) + + Salt 3002.5 (2021-02-25) ======================== @@ -480,6 +504,18 @@ Added This flag will be deprecated in the Phosphorus release when this functionality becomes the default. (#58652) +Salt 3001.7 (2021-03-10) +======================== + +Fixed +----- + +- Fix argument injection bug in restartcheck.restartcheck. This change hardens + the fix for CVE-2020-28243. (#200) +- Allow "extra_filerefs" as sanitized kwargs for SSH client. + Fix regression on "cmd.run" when passing tuples as cmd. (#59664) +- Allow all ssh kwargs as sanitized kwargs for SSH client. (#59748) + Salt 3001.6 (2021-02-09) ======================== @@ -971,6 +1007,18 @@ Added - [#56637](https://github.com/saltstack/salt/pull/56637) - Add ``win_wua.installed`` to the ``win_wua`` execution module - Clarify how to get the master fingerprint (#54699) +Salt 3000.9 (2021-03-10) +======================== + +Fixed +----- + +- Allow "extra_filerefs" as sanitized kwargs for SSH client. + Fix regression on "cmd.run" when passing tuples as cmd. (#59664) +- Allow all ssh kwargs as sanitized kwargs for SSH client. (#59748) +- Fix argument injection bug in restartcheck.restartcheck. This change hardens + the fix for CVE-2020-28243. + Salt 3000.8 (2021-02-09) ======================== diff --git a/doc/topics/releases/3000.9.rst b/doc/topics/releases/3000.9.rst new file mode 100644 index 000000000000..9e542f655bb1 --- /dev/null +++ b/doc/topics/releases/3000.9.rst @@ -0,0 +1,17 @@ +.. _release-3000-9: + +=========================== +Salt 3000.9 Release Notes +=========================== + +Version 3000.9 is a bug fix release for :ref:`3000 `. + + +Fixed +----- + +- Allow "extra_filerefs" as sanitized kwargs for SSH client. + Fix regression on "cmd.run" when passing tuples as cmd. (#59664) +- Allow all ssh kwargs as sanitized kwargs for SSH client. (#59748) +- Fix argument injection bug in restartcheck.restartcheck. This change hardens + the fix for CVE-2020-28243. diff --git a/doc/topics/releases/3001.7.rst b/doc/topics/releases/3001.7.rst new file mode 100644 index 000000000000..aa2a7ffc116b --- /dev/null +++ b/doc/topics/releases/3001.7.rst @@ -0,0 +1,17 @@ +.. _release-3001-7: + +========================= +Salt 3001.7 Release Notes +========================= + +Version 3001.7 is a bug fix release for :ref:`3001 `. + + +Fixed +----- + +- Allow "extra_filerefs" as sanitized kwargs for SSH client. + Fix regression on "cmd.run" when passing tuples as cmd. (#59664) +- Allow all ssh kwargs as sanitized kwargs for SSH client. (#59748) +- Fix argument injection bug in restartcheck.restartcheck. This change hardens + the fix for CVE-2020-28243. diff --git a/doc/topics/releases/3002.6.rst b/doc/topics/releases/3002.6.rst new file mode 100644 index 000000000000..1f5f2683d94b --- /dev/null +++ b/doc/topics/releases/3002.6.rst @@ -0,0 +1,24 @@ +.. _release-3002-6: + +========================= +Salt 3002.6 Release Notes +========================= + +Version 3002.6 is a bug fix release for :ref:`3002 `. + + +Changed +------- + +- Store git sha in salt/_version.py when installing from a tag so it can be found if needed later. (#59137) + + +Fixed +----- + +- Fix argument injection bug in restartcheck.restartcheck. This change hardens + the fix for CVE-2020-28243. (#200) +- Allow "extra_filerefs" as sanitized kwargs for SSH client. + Fix regression on "cmd.run" when passing tuples as cmd. (#59664) +- Allow all ssh kwargs as sanitized kwargs for SSH client. (#59748) + diff --git a/doc/topics/releases/3003.rst b/doc/topics/releases/3003.rst index fca5c0bc83de..bd54a75725c2 100644 --- a/doc/topics/releases/3003.rst +++ b/doc/topics/releases/3003.rst @@ -4,8 +4,6 @@ Salt 3003 Release Notes - Codename Aluminium ============================================ -Salt 3003 is an *unreleased* upcoming feature release. - New Features ============ @@ -22,6 +20,9 @@ previous storage methods. Removed ------- +- Removed the deprecated glance state and execution module in favor of the glance_image + state module and the glanceng execution module. (#59079) +- Removing the _ext_nodes deprecation warning and alias to the master_tops function. This change will break compatibility with a Salt master running versions 2017.7.8 and older and Salt minions running versions 3003 and newer. (#59804) - removed the arg `managed_private_key` from 'salt.states.x509.certificate_managed' (#59247) - Drop support for python 3.5 on Windows (#59479) @@ -50,6 +51,9 @@ Changed Fixed ----- +- When instantiating the loader grab values of grains and pillars if + they are NamedLoaderContext instances. (#59773) +- Fixed installation on Apple Silicon Macs by checking $HOMEBREW_PREFIX for `libcrypto` instead of assuming /usr/local. (#59808) - The Google Cloud Engine salt-cloud provider now requires `apache-libcloud>=2.5.0`. Service account authentication is broken on older versions. - Fix incorrect documentation for pillar_source_merging_strategy (#26396) - Don't iterate through cloud map errors (#34033) @@ -151,6 +155,7 @@ Fixed Added ----- +- Added "fips_mode" config option to master and minion configs. (#59427) - Firewall groups support to Vultr Salt Cloud provider - Adding the ability to clear and show the pillar cache enabled when pillar_cache is True. (#37080) - SCRAM-SHA-256 support for PostgreSQL passwords. diff --git a/salt/client/ssh/client.py b/salt/client/ssh/client.py index bd9be2c399cd..ef4287463bd0 100644 --- a/salt/client/ssh/client.py +++ b/salt/client/ssh/client.py @@ -52,11 +52,34 @@ def sanitize_kwargs(self, kwargs): ("ssh_identities_only", bool), ("ssh_remote_port_forwards", str), ("ssh_options", list), + ("ssh_max_procs", int), + ("ssh_askpass", bool), + ("ssh_key_deploy", bool), + ("ssh_update_roster", bool), + ("ssh_scan_ports", str), + ("ssh_scan_timeout", int), + ("ssh_timeout", int), + ("ssh_log_file", str), + ("raw_shell", bool), + ("refresh_cache", bool), + ("roster", str), ("roster_file", str), ("rosters", list), ("ignore_host_keys", bool), ("raw_shell", bool), ("extra_filerefs", str), + ("min_extra_mods", str), + ("thin_extra_mods", str), + ("verbose", bool), + ("static", bool), + ("ssh_wipe", bool), + ("rand_thin_dir", bool), + ("regen_thin", bool), + ("python2_bin", str), + ("python3_bin", str), + ("ssh_run_pre_flight", bool), + ("no_host_keys", bool), + ("saltfile", str), ] sane_kwargs = {} for name, kind in roster_vals: diff --git a/salt/config/__init__.py b/salt/config/__init__.py index ee112147fb2b..e63489cbce08 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -952,6 +952,7 @@ def _gather_buffer_space(): "disabled_requisites": (str, list), # Feature flag config "features": dict, + "fips_mode": bool, } ) @@ -1254,6 +1255,7 @@ def _gather_buffer_space(): "ssh_merge_pillar": True, "disabled_requisites": [], "reactor_niceness": None, + "fips_mode": False, } ) @@ -1590,6 +1592,7 @@ def _gather_buffer_space(): "minion_data_cache_events": True, "enable_ssh_minions": False, "netapi_allow_raw_shell": False, + "fips_mode": False, } ) diff --git a/salt/fileclient.py b/salt/fileclient.py index dd3bd97ec01d..822e0cae18ec 100644 --- a/salt/fileclient.py +++ b/salt/fileclient.py @@ -1404,15 +1404,7 @@ def master_tops(self): """ Return the metadata derived from the master_tops system """ - log.debug( - "The _ext_nodes master function has been renamed to _master_tops. " - "To ensure compatibility when using older Salt masters we will " - "continue to invoke the function as _ext_nodes until the " - "3002 release." - ) - # TODO: Change back to _master_tops - # for 3002 release - load = {"cmd": "_ext_nodes", "id": self.opts["id"], "opts": self.opts} + load = {"cmd": "_master_tops", "id": self.opts["id"], "opts": self.opts} if self.auth: load["tok"] = self.auth.gen_token(b"salt") return self.channel.send(load) diff --git a/salt/loader.py b/salt/loader.py index 1e1f2f7305d9..196ebeb0aa1c 100644 --- a/salt/loader.py +++ b/salt/loader.py @@ -1314,6 +1314,12 @@ def __init__( self.pack[i] = self.pack[i].value() if opts is None: opts = {} + opts = copy.deepcopy(opts) + for i in ["pillar", "grains"]: + if i in opts and isinstance( + opts[i], salt.loader_context.NamedLoaderContext + ): + opts[i] = opts[i].value() threadsafety = not opts.get("multiprocessing") self.context_dict = salt.utils.context.ContextDict(threadsafe=threadsafety) self.opts = self.__prep_mod_opts(opts) diff --git a/salt/loader_context.py b/salt/loader_context.py index 4d2488c38747..c0df778d1aa4 100644 --- a/salt/loader_context.py +++ b/salt/loader_context.py @@ -4,6 +4,7 @@ import collections.abc import contextlib import contextvars +import copy DEFAULT_CTX_VAR = "loader_ctxvar" @@ -112,6 +113,10 @@ def __setstate__(self, state): def __getattr__(self, name): return getattr(self.value(), name) + def __deepcopy__(self, memo): + default = copy.deepcopy(self.default) + return self.__class__(self.name, self.loader_context, default) + def missing_fun_string(self, name): return self.loader().missing_fun_string(name) diff --git a/salt/master.py b/salt/master.py index cddc12efdd69..e33d76905d54 100644 --- a/salt/master.py +++ b/salt/master.py @@ -1204,7 +1204,6 @@ class AESFuncs(TransportMethods): expose_methods = ( "verify_minion", "_master_tops", - "_ext_nodes", "_master_opts", "_mine_get", "_mine", @@ -1429,9 +1428,6 @@ def _master_tops(self, load): return {} return self.masterapi._master_tops(load, skip_verify=True) - # Needed so older minions can request master_tops - _ext_nodes = _master_tops - def _master_opts(self, load): """ Return the master options to the minion diff --git a/salt/modules/nacl.py b/salt/modules/nacl.py index 827e7e763bfa..6e2a54e516ab 100644 --- a/salt/modules/nacl.py +++ b/salt/modules/nacl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ This module helps include encrypted passwords in pillars, grains and salt state files. @@ -150,16 +149,15 @@ """ -# Import Python libs -from __future__ import absolute_import, print_function, unicode_literals -# Import Salt libs import salt.utils.nacl __virtualname__ = "nacl" def __virtual__(): + if __opts__["fips_mode"] is True: + return False, "nacl module not available in FIPS mode" return salt.utils.nacl.check_requirements() diff --git a/salt/modules/restartcheck.py b/salt/modules/restartcheck.py index 9e964031be57..0bf89ba2e0e5 100644 --- a/salt/modules/restartcheck.py +++ b/salt/modules/restartcheck.py @@ -11,7 +11,6 @@ """ import os import re -import shlex import subprocess import sys import time @@ -495,17 +494,17 @@ def restartcheck(ignorelist=None, blacklist=None, excludepid=None, **kwargs): verbose = kwargs.pop("verbose", True) timeout = kwargs.pop("timeout", 5) if __grains__.get("os_family") == "Debian": - cmd_pkg_query = "dpkg-query --listfiles " + cmd_pkg_query = ["dpkg-query", "--listfiles"] systemd_folder = "/lib/systemd/system/" systemd = "/bin/systemd" kernel_versions = _kernel_versions_debian() elif __grains__.get("os_family") == "RedHat": - cmd_pkg_query = "repoquery -l " + cmd_pkg_query = ["repoquery", "-l"] systemd_folder = "/usr/lib/systemd/system/" systemd = "/usr/bin/systemctl" kernel_versions = _kernel_versions_redhat() elif __grains__.get("os_family") == NILRT_FAMILY_NAME: - cmd_pkg_query = "opkg files " + cmd_pkg_query = ["opkg", "files"] systemd = "" kernel_versions = _kernel_versions_nilrt() else: @@ -613,8 +612,8 @@ def restartcheck(ignorelist=None, blacklist=None, excludepid=None, **kwargs): for package in packages: _check_timeout(start_time, timeout) - cmd = cmd_pkg_query + package - cmd = shlex.split(cmd) + cmd = cmd_pkg_query[:] + cmd.append(package) paths = subprocess.Popen(cmd, stdout=subprocess.PIPE) while True: diff --git a/salt/modules/state.py b/salt/modules/state.py index 202c929f3bd4..ff6998a0e337 100644 --- a/salt/modules/state.py +++ b/salt/modules/state.py @@ -425,7 +425,7 @@ def _check_queue(queue, kwargs): def _get_initial_pillar(opts): return ( - __pillar__ + __pillar__.value() if __opts__.get("__cli", None) == "salt-call" and opts["pillarenv"] == __opts__["pillarenv"] else None diff --git a/salt/modules/x509.py b/salt/modules/x509.py index ec9c92c00851..43a134625fc9 100644 --- a/salt/modules/x509.py +++ b/salt/modules/x509.py @@ -568,7 +568,6 @@ def read_certificate(certificate): "Key Size": cert.get_pubkey().size() * 8, "Serial Number": _dec2hex(cert.get_serial_number()), "SHA-256 Finger Print": _pretty_hex(cert.get_fingerprint(md="sha256")), - "MD5 Finger Print": _pretty_hex(cert.get_fingerprint(md="md5")), "SHA1 Finger Print": _pretty_hex(cert.get_fingerprint(md="sha1")), "Subject": _parse_subject(cert.get_subject()), "Subject Hash": _dec2hex(cert.get_subject().as_hash()), @@ -580,7 +579,8 @@ def read_certificate(certificate): "Not After": cert.get_not_after().get_datetime().strftime("%Y-%m-%d %H:%M:%S"), "Public Key": get_public_key(cert), } - + if __opts__["fips_mode"] is False: + ret["MD5 Finger Print"] = _pretty_hex(cert.get_fingerprint(md="md5")) exts = OrderedDict() for ext_index in range(0, cert.get_ext_count()): ext = cert.get_ext_at(ext_index) diff --git a/salt/pillar/nacl.py b/salt/pillar/nacl.py index 49f8d1529a4b..67f8c6fd25aa 100644 --- a/salt/pillar/nacl.py +++ b/salt/pillar/nacl.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ Decrypt pillar data through the builtin NACL renderer @@ -20,10 +18,17 @@ """ -from __future__ import absolute_import, print_function, unicode_literals import salt +__virtualname__ = "nacl" + + +def __virtual__(): + if __opts__["fips_mode"] is True: + return False, "nacl pillar data not available in FIPS mode" + return __virtualname__ + def ext_pillar(minion_id, pillar, *args, **kwargs): render_function = salt.loader.render(__opts__, __salt__).get("nacl") diff --git a/salt/runners/nacl.py b/salt/runners/nacl.py index ebd2151878d2..6e5ddea382f9 100644 --- a/salt/runners/nacl.py +++ b/salt/runners/nacl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ This module helps include encrypted passwords in pillars, grains and salt state files. @@ -112,16 +111,15 @@ """ -# Import Python libs -from __future__ import absolute_import, print_function, unicode_literals -# Import Salt libs import salt.utils.nacl __virtualname__ = "nacl" def __virtual__(): + if __opts__["fips_mode"] is True: + return False, "nacl runner not available in FIPS mode" return salt.utils.nacl.check_requirements() diff --git a/salt/states/x509.py b/salt/states/x509.py index 92b504724690..80e1ecdd6dad 100644 --- a/salt/states/x509.py +++ b/salt/states/x509.py @@ -321,7 +321,7 @@ def private_key_managed( name, bits=bits, passphrase=passphrase, new=new, overwrite=overwrite ): file_args["contents"] = __salt__["x509.get_pem_entry"]( - name, pem_type="RSA PRIVATE KEY" + name, pem_type="(?:RSA )?PRIVATE KEY" ) else: new_key = True @@ -399,12 +399,13 @@ def _certificate_info_matches(cert_info, required_cert_info, check_serial=False) ignored_keys = [ "Not Before", "Not After", - "MD5 Finger Print", "SHA1 Finger Print", "SHA-256 Finger Print", # The integrity of the issuer is checked elsewhere "Issuer Public Key", ] + if __opts__["fips_mode"] is False: + ignored_keys.append("MD5 Finger Print") for key in ignored_keys: cert_info.pop(key, None) required_cert_info.pop(key, None) diff --git a/salt/utils/nacl.py b/salt/utils/nacl.py index 91840ee4f321..a37279449eac 100644 --- a/salt/utils/nacl.py +++ b/salt/utils/nacl.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- """ Common code shared between the nacl module and runner. """ -# Import Python libs -from __future__ import absolute_import, print_function, unicode_literals import base64 import logging @@ -18,9 +15,6 @@ import salt.utils.win_dacl import salt.utils.win_functions -# Import Salt libs -from salt.ext import six - log = logging.getLogger(__name__) REQ_ERROR = None @@ -36,6 +30,8 @@ def __virtual__(): + if __opts__["fips_mode"] is True: + return False, "nacl utils not available in FIPS mode" return check_requirements() @@ -66,7 +62,7 @@ def _get_config(**kwargs): "pk_file": pk_file, } - config_key = "{0}.config".format(__virtualname__) + config_key = "{}.config".format(__virtualname__) try: config.update(__salt__["config.get"](config_key, {})) except (NameError, KeyError) as e: @@ -91,7 +87,7 @@ def _get_sk(**kwargs): try: with salt.utils.files.fopen(sk_file, "rb") as keyf: key = salt.utils.stringutils.to_unicode(keyf.read()).rstrip("\n") - except (IOError, OSError): + except OSError: raise Exception("no key or sk_file found") return base64.b64decode(key) @@ -109,9 +105,9 @@ def _get_pk(**kwargs): try: with salt.utils.files.fopen(pk_file, "rb") as keyf: pubkey = salt.utils.stringutils.to_unicode(keyf.read()).rstrip("\n") - except (IOError, OSError): + except OSError: raise Exception("no pubkey or pk_file found") - pubkey = six.text_type(pubkey) + pubkey = str(pubkey) return base64.b64decode(pubkey) @@ -151,7 +147,7 @@ def keygen(sk_file=None, pk_file=None, **kwargs): return {"sk": base64.b64encode(kp.sk), "pk": base64.b64encode(kp.pk)} if pk_file is None: - pk_file = "{0}.pub".format(sk_file) + pk_file = "{}.pub".format(sk_file) if sk_file and pk_file is None: if not os.path.isfile(sk_file): @@ -172,16 +168,16 @@ def keygen(sk_file=None, pk_file=None, **kwargs): else: # chmod 0600 file os.chmod(sk_file, 1536) - return "saved sk_file: {0}".format(sk_file) + return "saved sk_file: {}".format(sk_file) else: - raise Exception("sk_file:{0} already exist.".format(sk_file)) + raise Exception("sk_file:{} already exist.".format(sk_file)) if sk_file is None and pk_file: raise Exception("sk_file: Must be set inorder to generate a public key.") if os.path.isfile(sk_file) and os.path.isfile(pk_file): raise Exception( - "sk_file:{0} and pk_file:{1} already exist.".format(sk_file, pk_file) + "sk_file:{} and pk_file:{} already exist.".format(sk_file, pk_file) ) if os.path.isfile(sk_file) and not os.path.isfile(pk_file): @@ -192,7 +188,7 @@ def keygen(sk_file=None, pk_file=None, **kwargs): kp = libnacl.public.SecretKey(sk) with salt.utils.files.fopen(pk_file, "wb") as keyf: keyf.write(base64.b64encode(kp.pk)) - return "saved pk_file: {0}".format(pk_file) + return "saved pk_file: {}".format(pk_file) kp = libnacl.public.SecretKey() with salt.utils.files.fopen(sk_file, "wb") as keyf: @@ -208,7 +204,7 @@ def keygen(sk_file=None, pk_file=None, **kwargs): os.chmod(sk_file, 1536) with salt.utils.files.fopen(pk_file, "wb") as keyf: keyf.write(base64.b64encode(kp.pk)) - return "saved sk_file:{0} pk_file: {1}".format(sk_file, pk_file) + return "saved sk_file:{} pk_file: {}".format(sk_file, pk_file) def enc(data, **kwargs): @@ -275,10 +271,10 @@ def enc_file(name, out=None, **kwargs): d = enc(data, **kwargs) if out: if os.path.isfile(out): - raise Exception("file:{0} already exist.".format(out)) + raise Exception("file:{} already exist.".format(out)) with salt.utils.files.fopen(out, "wb") as f: f.write(salt.utils.stringutils.to_bytes(d)) - return "Wrote: {0}".format(out) + return "Wrote: {}".format(out) return d @@ -346,10 +342,10 @@ def dec_file(name, out=None, **kwargs): d = dec(data, **kwargs) if out: if os.path.isfile(out): - raise Exception("file:{0} already exist.".format(out)) + raise Exception("file:{} already exist.".format(out)) with salt.utils.files.fopen(out, "wb") as f: f.write(salt.utils.stringutils.to_bytes(d)) - return "Wrote: {0}".format(out) + return "Wrote: {}".format(out) return d diff --git a/salt/utils/openstack/neutron.py b/salt/utils/openstack/neutron.py index b7a8d3c80339..9d267be40342 100644 --- a/salt/utils/openstack/neutron.py +++ b/salt/utils/openstack/neutron.py @@ -1,22 +1,13 @@ -# -*- coding: utf-8 -*- """ Neutron class """ -# Import python libs -from __future__ import absolute_import, print_function, unicode_literals, with_statement - import logging import salt.utils.versions - -# Import salt libs from salt import exceptions -# Import third party libs -from salt.ext import six - # pylint: disable=import-error HAS_NEUTRON = False try: @@ -73,7 +64,7 @@ def sanitize_neutronclient(kwargs): "auth", ) ret = {} - for var in six.iterkeys(kwargs): + for var in kwargs.keys(): if var in variables: ret[var] = kwargs[var] @@ -103,7 +94,7 @@ def __init__( Set up neutron credentials """ salt.utils.versions.warn_until( - "Aluminium", + "Sulfur", ( "The neutron module has been deprecated and will be removed in {version}. " "Please update to using the neutronng module" diff --git a/salt/utils/rsax931.py b/salt/utils/rsax931.py index a23136fe01ca..9b9d43cc1ee5 100644 --- a/salt/utils/rsax931.py +++ b/salt/utils/rsax931.py @@ -32,17 +32,23 @@ def _find_libcrypto(): # look in salts pkg install location. lib = glob.glob("/opt/salt/lib/libcrypto.dylib") # Find library symlinks in Homebrew locations. - lib = lib or glob.glob("/usr/local/opt/openssl/lib/libcrypto.dylib") - lib = lib or glob.glob("/usr/local/opt/openssl@*/lib/libcrypto.dylib") + brew_prefix = os.getenv("HOMEBREW_PREFIX", "/usr/local") + lib = lib or glob.glob( + os.path.join(brew_prefix, "opt/openssl/lib/libcrypto.dylib") + ) + lib = lib or glob.glob( + os.path.join(brew_prefix, "opt/openssl@*/lib/libcrypto.dylib") + ) # look in macports. lib = lib or glob.glob("/opt/local/lib/libcrypto.dylib") # check if 10.15, regular libcrypto.dylib is just a false pointer. if platform.mac_ver()[0].split(".")[:2] == ["10", "15"]: lib = lib or glob.glob("/usr/lib/libcrypto.*.dylib") lib = list(reversed(sorted(lib))) - # last but not least all the other macOS versions should work here. - # including Big Sur. - lib = lib[0] if lib else "/usr/lib/libcrypto.dylib" + elif int(platform.mac_ver()[0].split(".")[0]) < 11: + # Fall back on system libcrypto (only works before Big Sur) + lib = lib or ["/usr/lib/libcrypto.dylib"] + lib = lib[0] if lib else None elif getattr(sys, "frozen", False) and salt.utils.platform.is_smartos(): lib = glob.glob(os.path.join(os.path.dirname(sys.executable), "libcrypto.so*")) lib = lib[0] if lib else None diff --git a/salt/version.py b/salt/version.py index 808cbf20c8a4..3d3e87555c4b 100644 --- a/salt/version.py +++ b/salt/version.py @@ -96,8 +96,8 @@ class SaltStackVersion: "Aluminium": (3003,), "Silicon": (MAX_SIZE - 95, 0), "Phosphorus": (MAX_SIZE - 94, 0), + "Sulfur": (MAX_SIZE - 93, 0), # pylint: disable=E8265 - #'Sulfur' : (MAX_SIZE - 93, 0), #'Chlorine' : (MAX_SIZE - 92, 0), #'Argon' : (MAX_SIZE - 91, 0), #'Potassium' : (MAX_SIZE - 90, 0), diff --git a/tests/pytests/unit/client/test_ssh.py b/tests/pytests/unit/client/test_ssh.py index b006e7e02f15..a8d18b2195ab 100644 --- a/tests/pytests/unit/client/test_ssh.py +++ b/tests/pytests/unit/client/test_ssh.py @@ -1,8 +1,11 @@ import pytest +import salt.client.ssh.client import salt.utils.msgpack from salt.client import ssh from tests.support.mock import MagicMock, patch +pytestmark = [pytest.mark.skip_if_binaries_missing("ssh", "ssh-keygen", check_all=True)] + @pytest.fixture def ssh_target(tmpdir): @@ -57,3 +60,76 @@ def test_cmd_block_python_version_error(ssh_target): with patch_shim: ret = single.cmd_block() assert "ERROR: Python version error. Recommendation(s) follow:" in ret[0] + + +@pytest.mark.parametrize( + "test_opts", + [ + ("extra_filerefs", "salt://foobar", True), + ("host", "testhost", False), + ("ssh_user", "testuser", True), + ("ssh_passwd", "testpasswd", True), + ("ssh_port", 23, False), + ("ssh_sudo", True, True), + ("ssh_sudo_user", "sudouser", False), + ("ssh_priv", "test_priv", True), + ("ssh_priv_passwd", "sshpasswd", True), + ("ssh_identities_only", True, True), + ("ssh_remote_port_forwards", "test", True), + ("ssh_options", ["test1", "test2"], True), + ("ssh_max_procs", 2, True), + ("ssh_askpass", True, True), + ("ssh_key_deploy", True, True), + ("ssh_update_roster", True, True), + ("ssh_scan_ports", "test", True), + ("ssh_scan_timeout", 1.0, True), + ("ssh_timeout", 1, False), + ("ssh_log_file", "/tmp/test", True), + ("raw_shell", True, True), + ("refresh_cache", True, True), + ("roster", "/test", True), + ("roster_file", "/test1", True), + ("rosters", ["test1"], False), + ("ignore_host_keys", True, True), + ("min_extra_mods", "test", True), + ("thin_extra_mods", "test1", True), + ("verbose", True, True), + ("static", True, True), + ("ssh_wipe", True, True), + ("rand_thin_dir", True, True), + ("regen_thin", True, True), + ("python2_bin", "python2", True), + ("python3_bin", "python3", True), + ("ssh_run_pre_flight", True, True), + ("no_host_keys", True, True), + ("saltfile", "/tmp/test", True), + ("doesnotexist", None, False), + ], +) +def test_ssh_kwargs(test_opts): + """ + test all ssh kwargs are not excluded from kwargs + when preparing the SSH opts + """ + opt_key = test_opts[0] + opt_value = test_opts[1] + # Is the kwarg in salt.utils.parsers? + in_parser = test_opts[2] + + opts = { + "eauth": "auto", + "username": "test", + "password": "test", + "client": "ssh", + "tgt": "localhost", + "fun": "test.ping", + opt_key: opt_value, + } + client = salt.client.ssh.client.SSHClient(disable_custom_roster=True) + if in_parser: + ssh_kwargs = salt.utils.parsers.SaltSSHOptionParser().defaults + assert opt_key in ssh_kwargs + + with patch("salt.roster.get_roster_file", MagicMock(return_value="")): + ssh_obj = client._prep_ssh(**opts) + assert ssh_obj.opts.get(opt_key, None) == opt_value diff --git a/tests/pytests/unit/modules/test_nacl.py b/tests/pytests/unit/modules/test_nacl.py new file mode 100644 index 000000000000..b60dabb3018e --- /dev/null +++ b/tests/pytests/unit/modules/test_nacl.py @@ -0,0 +1,16 @@ +""" + Unit tests for the salt.modules.nacl module +""" + +import salt.modules.nacl +from tests.support.mock import patch + + +def test_fips_mode(): + """ + Nacl module does not load when fips_mode is True + """ + opts = {"fips_mode": True} + with patch("salt.modules.nacl.__opts__", opts, create=True): + ret = salt.modules.nacl.__virtual__() + assert ret == (False, "nacl module not available in FIPS mode") diff --git a/tests/pytests/unit/modules/test_state.py b/tests/pytests/unit/modules/test_state.py new file mode 100644 index 000000000000..e22331d4912f --- /dev/null +++ b/tests/pytests/unit/modules/test_state.py @@ -0,0 +1,21 @@ +""" + Unit tests for the salt.modules.state module +""" + +import salt.loader_context +import salt.modules.state +from tests.support.mock import patch + + +def test_get_initial_pillar(): + """ + _get_initial_pillar returns pillar data not named context + """ + ctx = salt.loader_context.LoaderContext() + pillar_data = {"foo": "bar"} + named_ctx = ctx.named_context("__pillar__", pillar_data) + opts = {"__cli": "salt-call", "pillarenv": "base"} + with patch("salt.modules.state.__pillar__", named_ctx, create=True): + with patch("salt.modules.state.__opts__", opts, create=True): + pillar = salt.modules.state._get_initial_pillar(opts) + assert pillar == pillar_data diff --git a/tests/pytests/unit/pillar/__init__.py b/tests/pytests/unit/pillar/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/pytests/unit/pillar/test_nacl.py b/tests/pytests/unit/pillar/test_nacl.py new file mode 100644 index 000000000000..e71988baf9b3 --- /dev/null +++ b/tests/pytests/unit/pillar/test_nacl.py @@ -0,0 +1,16 @@ +""" + Unit tests for the salt.pillar.nacl module +""" + +import salt.pillar.nacl +from tests.support.mock import patch + + +def test_fips_mode(): + """ + Nacl pillar doesn't load when fips_mode is True + """ + opts = {"fips_mode": True} + with patch("salt.pillar.nacl.__opts__", opts, create=True): + ret = salt.pillar.nacl.__virtual__() + assert ret == (False, "nacl pillar data not available in FIPS mode") diff --git a/tests/pytests/unit/runners/__init__.py b/tests/pytests/unit/runners/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/pytests/unit/runners/test_nacl.py b/tests/pytests/unit/runners/test_nacl.py new file mode 100644 index 000000000000..b7be697abd76 --- /dev/null +++ b/tests/pytests/unit/runners/test_nacl.py @@ -0,0 +1,16 @@ +""" + Unit tests for the salt.runners.nacl module +""" + +import salt.runners.nacl +from tests.support.mock import patch + + +def test_fips_mode(): + """ + Nacl runner doesn't load when fips_mode is True + """ + opts = {"fips_mode": True} + with patch("salt.runners.nacl.__opts__", opts, create=True): + ret = salt.runners.nacl.__virtual__() + assert ret == (False, "nacl runner not available in FIPS mode") diff --git a/tests/pytests/unit/test_loader.py b/tests/pytests/unit/test_loader.py index cc18f33b8bdb..dcca8a7bf77c 100644 --- a/tests/pytests/unit/test_loader.py +++ b/tests/pytests/unit/test_loader.py @@ -93,3 +93,24 @@ def test_loaders_create_named_loader_contexts(loader_dir): module_name = func.func.__module__ module = sys.modules[module_name] assert isinstance(module.__context__, salt.loader_context.NamedLoaderContext) + + +def test_loaders_convert_context_to_values(loader_dir): + """ + LazyLoaders convert NamedLoaderContexts to values when instantiated. + """ + loader_context = salt.loader_context.LoaderContext() + grains_default = { + "os": "linux", + } + grains = salt.loader_context.NamedLoaderContext( + "grains", loader_context, grains_default + ) + opts = { + "optimization_order": [0, 1, 2], + "grains": grains, + } + loader_1 = salt.loader.LazyLoader([loader_dir], opts,) + assert loader_1.opts["grains"] == grains_default + # The loader's opts is a copy + assert opts["grains"] == grains diff --git a/tests/pytests/unit/test_loader_context.py b/tests/pytests/unit/test_loader_context.py index 1cf5ad97b8b1..f888a7986dda 100644 --- a/tests/pytests/unit/test_loader_context.py +++ b/tests/pytests/unit/test_loader_context.py @@ -1,6 +1,8 @@ """ Tests for salt.loader_context """ +import copy + import salt.loader import salt.loader_context @@ -31,3 +33,15 @@ def test_named_loader_default(): # The loader's value is the same object as default assert named_context.value() is default assert named_context["foo"] == "bar" + + +def test_named_loader_context_deepcopy(): + loader_context = salt.loader_context.LoaderContext() + default_data = {"foo": "bar"} + named_context = salt.loader_context.NamedLoaderContext( + "__test__", loader_context, default_data + ) + coppied = copy.deepcopy(named_context) + assert coppied.name == named_context.name + assert id(coppied.loader_context) == id(named_context.loader_context) + assert id(coppied.default) != id(named_context.default) diff --git a/tests/pytests/unit/utils/test_nacl.py b/tests/pytests/unit/utils/test_nacl.py new file mode 100644 index 000000000000..baf4024b8193 --- /dev/null +++ b/tests/pytests/unit/utils/test_nacl.py @@ -0,0 +1,16 @@ +""" + Unit tests for the salt.utils.nacl module +""" + +import salt.utils.nacl +from tests.support.mock import patch + + +def test_fips_mode(): + """ + Nacl pillar doesn't load when fips_mode is True + """ + opts = {"fips_mode": True} + with patch("salt.utils.nacl.__opts__", opts, create=True): + ret = salt.utils.nacl.__virtual__() + assert ret == (False, "nacl utils not available in FIPS mode") diff --git a/tests/unit/modules/test_restartcheck.py b/tests/unit/modules/test_restartcheck.py index 1fc4ea7f6f9a..0aafcb093e9f 100644 --- a/tests/unit/modules/test_restartcheck.py +++ b/tests/unit/modules/test_restartcheck.py @@ -381,3 +381,50 @@ def test_valid_command(self): "Found 1 processes using old versions of upgraded files", ret ) self.assertFalse(os.path.exists(create_file)) + + def test_valid_command_b(self): + """ + test for CVE-2020-28243 + """ + create_file = os.path.join(RUNTIME_VARS.TMP, "created_file") + + patch_kernel = patch( + "salt.modules.restartcheck._kernel_versions_redhat", + return_value=["3.10.0-1127.el7.x86_64"], + ) + services = { + "NetworkManager": {"ExecMainPID": 123}, + "auditd": {"ExecMainPID": 456}, + "crond": {"ExecMainPID": 789}, + } + + patch_salt = patch.dict( + restartcheck.__salt__, + { + "cmd.run": MagicMock( + return_value="Linux localhost.localdomain 3.10.0-1127.el7.x86_64" + ), + "service.get_running": MagicMock(return_value=list(services.keys())), + "service.show": MagicMock(side_effect=list(services.values())), + "pkg.owner": MagicMock(return_value=""), + "service.available": MagicMock(return_value=True), + }, + ) + + patch_deleted = patch( + "salt.modules.restartcheck._deleted_files", + MagicMock(return_value=[("--admindir tmp dpkg", 123, "/root/ (deleted)")]), + ) + + patch_readlink = patch("os.readlink", return_value="--admindir tmp dpkg") + + popen_mock = MagicMock() + popen_mock.return_value.stdout.readline.side_effect = ["/usr/bin\n", ""] + patch_popen = patch("subprocess.Popen", popen_mock) + + patch_grains = patch.dict(restartcheck.__grains__, {"os_family": "RedHat"}) + with patch_kernel, patch_salt, patch_deleted, patch_readlink, patch_grains, patch_popen: + ret = restartcheck.restartcheck() + self.assertIn("Found 1 processes using old versions of upgraded files", ret) + args, kwargs = popen_mock.call_args + assert args[0] == ["repoquery", "-l", "--admindir tmp dpkg"] diff --git a/tests/unit/modules/test_x509.py b/tests/unit/modules/test_x509.py index 40aea122728f..20ca0d679aef 100644 --- a/tests/unit/modules/test_x509.py +++ b/tests/unit/modules/test_x509.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Author: Bo Maryniuk # @@ -15,8 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Import Salt Testing Libs -from __future__ import absolute_import, print_function, unicode_literals import datetime import os @@ -25,16 +22,11 @@ import salt.utils.files import salt.utils.stringutils from salt.modules import x509 +from tests.support.helpers import dedent from tests.support.mixins import LoaderModuleMockMixin from tests.support.mock import MagicMock, patch from tests.support.unit import TestCase, skipIf -try: - import pytest -except ImportError as import_error: - pytest = None - - try: import M2Crypto # pylint: disable=unused-import @@ -99,10 +91,9 @@ } -@skipIf(not bool(pytest), False) class X509TestCase(TestCase, LoaderModuleMockMixin): def setup_loader_modules(self): - return {x509: {}} + return {x509: {"__opts__": {"fips_mode": False}}} @patch("salt.modules.x509.log", MagicMock()) def test_private_func__parse_subject(self): @@ -111,7 +102,7 @@ def test_private_func__parse_subject(self): :return: """ - class FakeSubject(object): + class FakeSubject: """ Class for faking x509'th subject. """ @@ -436,3 +427,64 @@ def test_revoke_certificate_with_crl(self): # Ensure that the correct server cert serial is amongst # the revoked certificates self.assertIn(serial_number, crl) + + @skipIf(not HAS_M2CRYPTO, "Skipping, M2Crypto is unavailable") + def test_read_certificate(self): + """ + :return: + """ + cet = dedent( + """ + -----BEGIN CERTIFICATE----- + MIICdDCCAd2gAwIBAgIUH6g+PC0bGKSY4LMq7PISP09M5B4wDQYJKoZIhvcNAQEL + BQAwTDELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0FyaXpvbmExEzARBgNVBAcMClNj + b3R0c2RhbGUxFjAUBgNVBAoMDVN1cGVyIFdpZGdpdHMwHhcNMjEwMzIzMDExNDE2 + WhcNMjIwMzIzMDExNDE2WjBMMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHQXJpem9u + YTETMBEGA1UEBwwKU2NvdHRzZGFsZTEWMBQGA1UECgwNU3VwZXIgV2lkZ2l0czCB + nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvtFFZP47UkzyAmVWtBnVHuXwe7iK + yu19c3qx59KPVAMHkMKgCew4S2KBMDHySBVnspiEz1peP1ywozcP1tIeWHG6aY/7 + j2ewzl5bJ4HZPDBnEOYzGsC/NM8YY3qFlrteda/awvwoF99MkpVlrcLBMJzjt/c8 + HjuBb0zTlnm4r7ECAwEAAaNTMFEwHQYDVR0OBBYEFJwdb0PKsvu3dU0j3kx3uP4B + NGpfMB8GA1UdIwQYMBaAFJwdb0PKsvu3dU0j3kx3uP4BNGpfMA8GA1UdEwEB/wQF + MAMBAf8wDQYJKoZIhvcNAQELBQADgYEAZblVv70rSk6+7ti3mYxVo48VLf3hG5R/ + rMd434WYTeDOWlvl5GSklrBc4ToBW5GsJe/+JaFbUFo9YB+a0K0xjyNZ5CWWiaxg + 3lwqTx6vwK1ucS18B+nt2qqyq9hL0UvpSB7gH4KeCwCMDIfRMsrPi32jg1RyKftD + B+O0S5LeuJw= + -----END CERTIFICATE----- + """ + ) + ret = x509.read_certificate(cet) + assert "MD5 Finger Print" in ret + + +class X509FipsTestCase(TestCase, LoaderModuleMockMixin): + def setup_loader_modules(self): + return {x509: {"__opts__": {"fips_mode": True}}} + + @skipIf(not HAS_M2CRYPTO, "Skipping, M2Crypto is unavailable") + def test_read_certificate(self): + """ + :return: + """ + cet = dedent( + """ + -----BEGIN CERTIFICATE----- + MIICdDCCAd2gAwIBAgIUH6g+PC0bGKSY4LMq7PISP09M5B4wDQYJKoZIhvcNAQEL + BQAwTDELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0FyaXpvbmExEzARBgNVBAcMClNj + b3R0c2RhbGUxFjAUBgNVBAoMDVN1cGVyIFdpZGdpdHMwHhcNMjEwMzIzMDExNDE2 + WhcNMjIwMzIzMDExNDE2WjBMMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHQXJpem9u + YTETMBEGA1UEBwwKU2NvdHRzZGFsZTEWMBQGA1UECgwNU3VwZXIgV2lkZ2l0czCB + nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvtFFZP47UkzyAmVWtBnVHuXwe7iK + yu19c3qx59KPVAMHkMKgCew4S2KBMDHySBVnspiEz1peP1ywozcP1tIeWHG6aY/7 + j2ewzl5bJ4HZPDBnEOYzGsC/NM8YY3qFlrteda/awvwoF99MkpVlrcLBMJzjt/c8 + HjuBb0zTlnm4r7ECAwEAAaNTMFEwHQYDVR0OBBYEFJwdb0PKsvu3dU0j3kx3uP4B + NGpfMB8GA1UdIwQYMBaAFJwdb0PKsvu3dU0j3kx3uP4BNGpfMA8GA1UdEwEB/wQF + MAMBAf8wDQYJKoZIhvcNAQELBQADgYEAZblVv70rSk6+7ti3mYxVo48VLf3hG5R/ + rMd434WYTeDOWlvl5GSklrBc4ToBW5GsJe/+JaFbUFo9YB+a0K0xjyNZ5CWWiaxg + 3lwqTx6vwK1ucS18B+nt2qqyq9hL0UvpSB7gH4KeCwCMDIfRMsrPi32jg1RyKftD + B+O0S5LeuJw= + -----END CERTIFICATE----- + """ + ) + ret = x509.read_certificate(cet) + assert "MD5 Finger Print" not in ret diff --git a/tests/unit/states/test_x509.py b/tests/unit/states/test_x509.py new file mode 100644 index 000000000000..b928802d13ba --- /dev/null +++ b/tests/unit/states/test_x509.py @@ -0,0 +1,122 @@ +import tempfile + +import salt.utils.files +from salt.modules import x509 as x509_mod +from salt.states import x509 +from tests.support.helpers import dedent +from tests.support.mixins import LoaderModuleMockMixin +from tests.support.mock import MagicMock +from tests.support.unit import TestCase, skipIf + +try: + import M2Crypto # pylint: disable=unused-import + + HAS_M2CRYPTO = True +except ImportError: + HAS_M2CRYPTO = False + + +class X509TestCase(TestCase, LoaderModuleMockMixin): + def setup_loader_modules(self): + return {x509: {"__opts__": {"fips_mode": False}}} + + def test_certificate_info_matches(self): + cert_info = {"MD5 Finger Print": ""} + required_info = {"MD5 Finger Print": ""} + ret = x509._certificate_info_matches(cert_info, required_info) + assert ret == (True, []) + + +class X509FipsTestCase(TestCase, LoaderModuleMockMixin): + def setup_loader_modules(self): + self.file_managed_mock = MagicMock() + self.file_managed_mock.return_value = {"changes": True} + + return { + x509: { + "__opts__": {"fips_mode": True}, + "__salt__": { + "x509.get_pem_entry": x509_mod.get_pem_entry, + "x509.get_private_key_size": x509_mod.get_private_key_size, + }, + "__states__": {"file.managed": self.file_managed_mock}, + } + } + + @skipIf(not HAS_M2CRYPTO, "Skipping, M2Crypto is unavailable") + def test_private_key_fips_mode(self): + """ + :return: + """ + test_key = dedent( + """ + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDx7UUt0cPi5G51 + FmRBhAZtZb5x6P0PFn7GwnLmSvLNhCsOcD/vq/yBUU62pknzmOjM5pgWTACZj66O + GOFmWBg06v8+sqUbaF9PZ/CxQD5MogmQhYNgfyuopHWWgLXMub2hlP+15qGohkzg + Tr/mXp2ohVAb6ihjqb7XV9MiZaLNVX+XWauM8SlhqXMiJyDUopEGbg2pLsHhIMcX + 1twLlyDja+uDbCMZ4jDNB+wsWxTaPRH8KizfEabB1Cl+fdyD10pSAYcodOAnlkW+ + G/DX2hwb/ZAM9B1SXTfZ3gzaIIbqXBEHcZQNXxHL7szBTVcOmfx/RPfOeRncytb9 + Mit7RIBxAgMBAAECggEAD4Pi+uRIBsYVm2a7OURpURzEUPPbPtt3d/HCgqht1+ZR + CJUEVK+X+wcm4Cnb9kZpL7LeMBfhtfdz/2LzGagurT4g7nlwg0h3TFVjJ0ryc+G0 + cVNOsKKXPzKE5AkPH7kNw04V9Cl9Vpx+U6hZQEHzJHqgP5oNyw540cCtJriT700b + fG1q3PYKWSkDwTiUnJTnVLybFIKQC6urxTeT2UWeiBadfDY7DjI4USfrQsqCfGMO + uWPpOOJk5RIvw5r0Of2xvxV76xCgzVTkgtWjBRMTEkfeYx3019xKlQtAKoGbZd1T + tF8DH0cDlnri4nG7YT8yYvx/LWVDg12E6IZij1X60QKBgQD7062JuQGEmTd99a7o + 5TcgWYqDrmE9AEgJZjN+gnEPcsxc50HJaTQgrkV0oKrS8CMbStIymbzMKWifOj7o + gvQBVecydq1AaXePt3gRe8vBFiP4cHjFcSegs9FDvdfJR36iHOBIgEp4DWvV1vgs + +z82LT6Qy5kxUQvnlQ4dEaGdrQKBgQD175f0H4enRJ3BoWTrqt2mTAwtJcPsKmGD + 9YfFB3H4+O2rEKP4FpBO5PFXZ0dqm54hDtxqyC/lSXorFCUjVUBero1ECGt6Gnn2 + TSnhgk0VMxvhnc0GReIt4K9WrXGd0CMUDwIhFHj8kbb1X1yqt2hwyw7b10xFVStl + sGv8CQB+VQKBgAF9q1VZZwzl61Ivli2CzeS/IvbMnX7C9ao4lK13EDxLLbKPG/CZ + UtmurnKWUOyWx15t/viVuGxtAlWO/rhZriAj5g6CbVwoQ7DyIR/ZX8dw3h2mbNCe + buGgruh7wz9J0RIcoadMOySiz7SgZS++/QzRD8HDstB77loco8zAQfixAoGBALDO + FbTocfKbjrpkmBQg24YxR9OxQb/n3AEtI/VO2+38r4h6xxaUyhwd1S9bzWjkBXOI + poeR8XTqNQ0BR422PTeUT3SohPPcUu/yG3jG3zmta47wjjPDS85lqEgtGvA0cPN7 + srErcatJ6nlOnGUSw9/K65y6lFeH2lIZ2hfwNM2dAoGBAMVCc7i3AIhLp6UrGzjP + 0ioCHCakpxfl8s1VQp55lhHlP6Y4RfqT72Zq7ScteTrisIAQyI9ot0gsuct2miQM + nyDdyKGki/MPduGTzzWlBA7GZEHnxbAILH8kWJ7eE/Nh7zdF1CRts8utEO9L9S+0 + lVz1j/xGOseQk4cVos681Wpw + -----END PRIVATE KEY-----""" + ) + test_cert = dedent( + """ + -----BEGIN CERTIFICATE----- + MIIDazCCAlOgAwIBAgIUAfATs1aodKw11Varh55msmU0LoowDQYJKoZIhvcNAQEL + BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTAzMjMwMTM4MzdaFw0yMjAz + MjMwMTM4MzdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw + HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB + AQUAA4IBDwAwggEKAoIBAQDx7UUt0cPi5G51FmRBhAZtZb5x6P0PFn7GwnLmSvLN + hCsOcD/vq/yBUU62pknzmOjM5pgWTACZj66OGOFmWBg06v8+sqUbaF9PZ/CxQD5M + ogmQhYNgfyuopHWWgLXMub2hlP+15qGohkzgTr/mXp2ohVAb6ihjqb7XV9MiZaLN + VX+XWauM8SlhqXMiJyDUopEGbg2pLsHhIMcX1twLlyDja+uDbCMZ4jDNB+wsWxTa + PRH8KizfEabB1Cl+fdyD10pSAYcodOAnlkW+G/DX2hwb/ZAM9B1SXTfZ3gzaIIbq + XBEHcZQNXxHL7szBTVcOmfx/RPfOeRncytb9Mit7RIBxAgMBAAGjUzBRMB0GA1Ud + DgQWBBT0qx4KLhozvuWAI9peT/utYV9FITAfBgNVHSMEGDAWgBT0qx4KLhozvuWA + I9peT/utYV9FITAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDx + tWvUyGfEwJJg1ViBa10nVhg5sEc6KfqcPzc2GvatIGJlAbc3b1AYu6677X04SQNA + dYRA2jcZcKudy6eolPJow6SDpkt66IqciZYdbQE5h9elnwpZxmXlJTQTB9cEwyIk + 2em5DKpdIwa9rRDlbAjAVJb3015MtpKRu2gsQ7gl5X2U3K+DFsWtBPf+0xiJqUiq + rd7tiHF/zylubSyH/LVONJZ6+/oT/qzJfxfpvygtQWcu4b2zzME/FPenMA8W6Rau + ZYycQfpMVc7KwqF5/wfjnkmfxoFKnkD7WQ3qFCJ/xULk/Yn1hrvNeIr+khX3qKQi + Y3BMA5m+J+PZrNy7EQSa + -----END CERTIFICATE----- + """ + ) + fp, name = tempfile.mkstemp() + with salt.utils.files.fopen(name, "w") as fd: + fd.write(test_key) + fd.write(test_cert) + ret = x509.private_key_managed(name) + self.file_managed_mock.assert_called_once() + assert ( + self.file_managed_mock.call_args.kwargs["contents"].strip() + == test_key.strip() + ) + + def test_certificate_info_matches(self): + cert_info = {"MD5 Finger Print": ""} + required_info = {"MD5 Finger Print": ""} + ret = x509._certificate_info_matches(cert_info, required_info) + assert ret == (False, ["MD5 Finger Print"]) diff --git a/tests/unit/test_module_names.py b/tests/unit/test_module_names.py index 632cd0ab3a1c..52ed9fb2751a 100644 --- a/tests/unit/test_module_names.py +++ b/tests/unit/test_module_names.py @@ -200,6 +200,7 @@ def test_module_name_source_match(self): "unit.utils.scheduler.test_run_job", "unit.utils.scheduler.test_schedule", "unit.utils.scheduler.test_skip", + "unit.auth.test_auth", ) errors = [] diff --git a/tests/unit/utils/test_rsax931.py b/tests/unit/utils/test_rsax931.py index 311d3d6694e4..ea1364ffbf2e 100644 --- a/tests/unit/utils/test_rsax931.py +++ b/tests/unit/utils/test_rsax931.py @@ -1,11 +1,7 @@ -# coding: utf-8 """ Test the RSA ANSI X9.31 signer and verifier """ -# python libs -from __future__ import absolute_import, print_function, unicode_literals - import ctypes import ctypes.util import fnmatch @@ -174,49 +170,89 @@ def test_find_libcrypto_aix(self): fnmatch.fnmatch(lib_path, "/opt/freeware/lib/libcrypto.so*") ) - @skipIf(not salt.utils.platform.is_darwin(), "Host OS is not Darwin-like or macOS.") + @patch.object(salt.utils.platform, "is_darwin", lambda: True) @patch.object(platform, "mac_ver", lambda: ("10.14.2", (), "")) @patch.object(glob, "glob", lambda _: []) - def test_find_libcrypto_with_system_and_not_catalina(self): + @patch.object(sys, "platform", "macosx") + def test_find_libcrypto_with_system_before_catalina(self): """ - Test _find_libcrypto on a Catalina-like macOS host, simulate - not finding any other libcryptos and just defaulting to system. + Test _find_libcrypto on a pre-Catalina macOS host by simulating not + finding any other libcryptos and verifying that it defaults to system. """ lib_path = _find_libcrypto() - passed = False - for i in ( - "/opt/salt/lib/libcrypto.dylib", - "/usr/local/opt/openssl/lib/libcrypto.dylib", - "/usr/local/opt/openssl@*/lib/libcrypto.dylib", - "/opt/local/lib/libcrypto.dylib", - "/usr/lib/libcrypto.*.dylib", - ): - if fnmatch.fnmatch(lib_path, i): - passed = True - break - self.assertFalse(passed) self.assertEqual(lib_path, "/usr/lib/libcrypto.dylib") - @skipIf(not salt.utils.platform.is_darwin(), "Host OS is not Darwin-like or macOS.") + @patch.object(salt.utils.platform, "is_darwin", lambda: True) @patch.object(platform, "mac_ver", lambda: ("10.15.2", (), "")) + @patch.object(sys, "platform", "macosx") def test_find_libcrypto_darwin_catalina(self): """ - Test _find_libcrypto on a Darwin-like macOS host where there isn't a - lacation returned by ctypes.util.find_library() + Test _find_libcrypto on a macOS Catalina host where there are no custom + libcryptos and defaulting to the versioned system libraries. """ - lib_path = _find_libcrypto() - passed = False - for i in ( - "/opt/salt/lib/libcrypto.dylib", - "/usr/local/opt/openssl/lib/libcrypto.dylib", - "/usr/local/opt/openssl@*/lib/libcrypto.dylib", - "/opt/local/lib/libcrypto.dylib", - "/usr/lib/libcrypto.*.dylib", - ): - if fnmatch.fnmatch(lib_path, i): - passed = True - break - self.assertTrue(passed) + available = [ + "/usr/lib/libcrypto.0.9.7.dylib", + "/usr/lib/libcrypto.0.9.8.dylib", + "/usr/lib/libcrypto.35.dylib", + "/usr/lib/libcrypto.41.dylib", + "/usr/lib/libcrypto.42.dylib", + "/usr/lib/libcrypto.44.dylib", + "/usr/lib/libcrypto.dylib", + ] + + def test_glob(pattern): + return [lib for lib in available if fnmatch.fnmatch(lib, pattern)] + + with patch.object(glob, "glob", test_glob): + lib_path = _find_libcrypto() + self.assertEqual("/usr/lib/libcrypto.44.dylib", lib_path) + + @patch.object(salt.utils.platform, "is_darwin", lambda: True) + @patch.object(platform, "mac_ver", lambda: ("11.2.2", (), "")) + @patch.object(sys, "platform", "macosx") + def test_find_libcrypto_darwin_bigsur_packaged(self): + """ + Test _find_libcrypto on a Darwin-like macOS host where there isn't a + lacation returned by ctypes.util.find_library() and the libcrypto + installation comes from a package manager (ports, brew, salt). + """ + managed_paths = { + "salt": "/opt/salt/lib/libcrypto.dylib", + "brew": "/test/homebrew/prefix/opt/openssl/lib/libcrypto.dylib", + "port": "/opt/local/lib/libcrypto.dylib", + } + + saved_getenv = os.getenv + + def mock_getenv(env): + def test_getenv(var, default=None): + return env.get(var, saved_getenv(var, default)) + + return test_getenv + + def mock_glob(expected_lib): + def test_glob(pattern): + if fnmatch.fnmatch(expected_lib, pattern): + return [expected_lib] + return [] + + return test_glob + + for package_manager, expected_lib in managed_paths.items(): + if package_manager == "brew": + env = {"HOMEBREW_PREFIX": "/test/homebrew/prefix"} + else: + env = {"HOMEBREW_PREFIX": ""} + with patch.object(os, "getenv", mock_getenv(env)): + with patch.object(glob, "glob", mock_glob(expected_lib)): + lib_path = _find_libcrypto() + + self.assertEqual(expected_lib, lib_path) + + # On Big Sur, there's nothing else to fall back on. + with patch.object(glob, "glob", lambda _: []): + with self.assertRaises(OSError): + lib_path = _find_libcrypto() @patch.object(ctypes.util, "find_library", lambda a: None) @patch.object(glob, "glob", lambda a: [])