From b32f09b65516a457e9efd0e66d7d5f4a9873f6b9 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Sat, 25 May 2024 01:27:41 -0700 Subject: [PATCH] Clean up un-needed re-init crypto and test fix --- salt/channel/server.py | 5 +- salt/cloud/__init__.py | 14 - salt/crypt.py | 2 - salt/master.py | 2 - salt/minion.py | 2 - salt/modules/inspectlib/collector.py | 6 +- salt/utils/crypt.py | 43 - tests/conftest.py | 10 + tests/pytests/conftest.py | 7 + tests/pytests/unit/conftest.py | 9 + .../unit/crypt/test_crypt_cryptography.py | 88 ++ tests/pytests/unit/transport/test_zeromq.py | 781 ++++++++++-------- tests/pytests/unit/utils/test_crypt.py | 45 - 13 files changed, 553 insertions(+), 461 deletions(-) diff --git a/salt/channel/server.py b/salt/channel/server.py index 7ffb19dde44..ca5de7bf2d5 100644 --- a/salt/channel/server.py +++ b/salt/channel/server.py @@ -53,7 +53,10 @@ class ReqServerChannel: def __init__(self, opts, transport): self.opts = opts self.transport = transport - self.event = None + self.event = salt.utils.event.get_master_event( + self.opts, self.opts["sock_dir"], listen=False + ) + self.master_key = salt.crypt.MasterKeys(self.opts) def pre_fork(self, process_manager): """ diff --git a/salt/cloud/__init__.py b/salt/cloud/__init__.py index cad276b853a..f205ee6c920 100644 --- a/salt/cloud/__init__.py +++ b/salt/cloud/__init__.py @@ -35,14 +35,6 @@ from salt.exceptions import ( ) from salt.template import compile_template -try: - import Cryptodome.Random -except ImportError: - try: - import Crypto.Random # nosec - except ImportError: - pass # pycrypto < 2.1 - log = logging.getLogger(__name__) @@ -2288,8 +2280,6 @@ def create_multiprocessing(parallel_data, queue=None): This function will be called from another process when running a map in parallel mode. The result from the create is always a json object. """ - salt.utils.crypt.reinit_crypto() - parallel_data["opts"]["output"] = "json" cloud = Cloud(parallel_data["opts"]) try: @@ -2318,8 +2308,6 @@ def destroy_multiprocessing(parallel_data, queue=None): This function will be called from another process when running a map in parallel mode. The result from the destroy is always a json object. """ - salt.utils.crypt.reinit_crypto() - parallel_data["opts"]["output"] = "json" clouds = salt.loader.clouds(parallel_data["opts"]) @@ -2350,8 +2338,6 @@ def run_parallel_map_providers_query(data, queue=None): This function will be called from another process when building the providers map. """ - salt.utils.crypt.reinit_crypto() - cloud = Cloud(data["opts"]) try: with salt.utils.context.func_globals_inject( diff --git a/salt/crypt.py b/salt/crypt.py index deaa871eef7..f4e397a9bba 100644 --- a/salt/crypt.py +++ b/salt/crypt.py @@ -622,8 +622,6 @@ class AsyncAuth: self.get_keys() self.io_loop = io_loop or salt.ext.tornado.ioloop.IOLoop.current() - - salt.utils.crypt.reinit_crypto() key = self.__key(self.opts) # TODO: if we already have creds for this key, lets just re-use if key in AsyncAuth.creds_map: diff --git a/salt/master.py b/salt/master.py index 4937274a41b..e6ba5f1d8d8 100644 --- a/salt/master.py +++ b/salt/master.py @@ -37,7 +37,6 @@ import salt.serializers.msgpack import salt.state import salt.utils.args import salt.utils.atomicfile -import salt.utils.crypt import salt.utils.event import salt.utils.files import salt.utils.gitfs @@ -1156,7 +1155,6 @@ class MWorker(salt.utils.process.SignalHandlingProcess): ) self.clear_funcs.connect() self.aes_funcs = AESFuncs(self.opts) - salt.utils.crypt.reinit_crypto() self.__bind() diff --git a/salt/minion.py b/salt/minion.py index 12c9a86ba2a..cb4ef919b30 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -39,7 +39,6 @@ import salt.syspaths import salt.transport import salt.utils.args import salt.utils.context -import salt.utils.crypt import salt.utils.data import salt.utils.dictdiffer import salt.utils.dictupdate @@ -1811,7 +1810,6 @@ class Minion(MinionBase): name=name, args=(instance, self.opts, data, self.connected, creds_map), ) - process.register_after_fork_method(salt.utils.crypt.reinit_crypto) else: process = threading.Thread( target=self._target, diff --git a/salt/modules/inspectlib/collector.py b/salt/modules/inspectlib/collector.py index d92646c9fc3..9d22808434a 100644 --- a/salt/modules/inspectlib/collector.py +++ b/salt/modules/inspectlib/collector.py @@ -17,7 +17,6 @@ import os import subprocess import sys -import salt.utils.crypt import salt.utils.files import salt.utils.fsutils import salt.utils.path @@ -579,10 +578,9 @@ if __name__ == "__main__": # Double-fork stuff try: if os.fork() > 0: - salt.utils.crypt.reinit_crypto() sys.exit(0) else: - salt.utils.crypt.reinit_crypto() + pass except OSError as ex: sys.exit(1) @@ -592,7 +590,6 @@ if __name__ == "__main__": try: pid = os.fork() if pid > 0: - salt.utils.crypt.reinit_crypto() with salt.utils.files.fopen( os.path.join(pidfile, EnvLoader.PID_FILE), "w" ) as fp_: @@ -601,5 +598,4 @@ if __name__ == "__main__": except OSError as ex: sys.exit(1) - salt.utils.crypt.reinit_crypto() main(dbfile, pidfile, mode) diff --git a/salt/utils/crypt.py b/salt/utils/crypt.py index 044eebe7a77..5505c0eacf0 100644 --- a/salt/utils/crypt.py +++ b/salt/utils/crypt.py @@ -12,35 +12,6 @@ from salt.exceptions import SaltInvocationError log = logging.getLogger(__name__) -try: - import M2Crypto # pylint: disable=unused-import - - Random = None - HAS_M2CRYPTO = True -except ImportError: - HAS_M2CRYPTO = False - -if not HAS_M2CRYPTO: - try: - from Cryptodome import Random - - HAS_CRYPTODOME = True - except ImportError: - HAS_CRYPTODOME = False -else: - HAS_CRYPTODOME = False - -if not HAS_M2CRYPTO and not HAS_CRYPTODOME: - try: - from Crypto import Random # nosec - - HAS_CRYPTO = True - except ImportError: - HAS_CRYPTO = False -else: - HAS_CRYPTO = False - - def decrypt( data, rend, translate_newlines=False, renderers=None, opts=None, valid_rend=None ): @@ -117,20 +88,6 @@ def decrypt( return rend_func(data, translate_newlines=translate_newlines) -def reinit_crypto(): - """ - When a fork arises, pycrypto needs to reinit - From its doc:: - - Caveat: For the random number generator to work correctly, - you must call Random.atfork() in both the parent and - child processes after using os.fork() - - """ - if HAS_CRYPTODOME or HAS_CRYPTO: - Random.atfork() - - 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 diff --git a/tests/conftest.py b/tests/conftest.py index bb37b7b748a..04b885a8f9f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -996,6 +996,9 @@ def salt_syndic_master_factory( config_overrides = { "log_level_logfile": "quiet", "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } ext_pillar = [] if salt.utils.platform.is_windows(): @@ -1112,6 +1115,9 @@ def salt_master_factory( config_overrides = { "log_level_logfile": "quiet", "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ), } ext_pillar = [] if salt.utils.platform.is_windows(): @@ -1221,6 +1227,8 @@ def salt_minion_factory(salt_master_factory): "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "rsa_encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "rsa_signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } virtualenv_binary = get_virtualenv_binary_path() @@ -1253,6 +1261,8 @@ def salt_sub_minion_factory(salt_master_factory): "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "rsa_encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "rsa_signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } virtualenv_binary = get_virtualenv_binary_path() diff --git a/tests/pytests/conftest.py b/tests/pytests/conftest.py index 591f1baafce..fe5f7c755ed 100644 --- a/tests/pytests/conftest.py +++ b/tests/pytests/conftest.py @@ -191,6 +191,9 @@ def salt_master_factory( config_overrides = { "pytest-master": {"log": {"level": "DEBUG"}}, "fips_mode": FIPS_TESTRUN, + "publish_signing_algorithm": ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA224" + ), } ext_pillar = [] if salt.utils.platform.is_windows(): @@ -321,6 +324,8 @@ def salt_minion_factory(salt_master_factory, salt_minion_id, sdb_etcd_port, vaul "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "rsa_encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "rsa_signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } virtualenv_binary = get_virtualenv_binary_path() @@ -352,6 +357,8 @@ def salt_sub_minion_factory(salt_master_factory, salt_sub_minion_id): "file_roots": salt_master_factory.config["file_roots"].copy(), "pillar_roots": salt_master_factory.config["pillar_roots"].copy(), "fips_mode": FIPS_TESTRUN, + "rsa_encryption_algorithm": "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1", + "rsa_signing_algorithm": "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1", } virtualenv_binary = get_virtualenv_binary_path() diff --git a/tests/pytests/unit/conftest.py b/tests/pytests/unit/conftest.py index c7152f3d2d1..444c2a147d4 100644 --- a/tests/pytests/unit/conftest.py +++ b/tests/pytests/unit/conftest.py @@ -3,6 +3,7 @@ import os import pytest import salt.config +from tests.conftest import FIPS_TESTRUN @pytest.fixture @@ -10,6 +11,7 @@ def minion_opts(tmp_path): """ Default minion configuration with relative temporary paths to not require root permissions. """ + print(f"WTF {FIPS_TESTRUN}") root_dir = tmp_path / "minion" opts = salt.config.DEFAULT_MINION_OPTS.copy() opts["__role"] = "minion" @@ -23,6 +25,9 @@ def minion_opts(tmp_path): opts[name] = str(dirpath) opts["log_file"] = "logs/minion.log" opts["conf_file"] = os.path.join(opts["conf_dir"], "minion") + opts["fips_mode"] = FIPS_TESTRUN + opts["encryption_algorithm"] = "OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1" + opts["signing_algorithm"] = "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" return opts @@ -41,6 +46,10 @@ def master_opts(tmp_path): opts[name] = str(dirpath) opts["log_file"] = "logs/master.log" opts["conf_file"] = os.path.join(opts["conf_dir"], "master") + opts["fips_mode"] = FIPS_TESTRUN + opts["publish_signing_algorithm"] = ( + "PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1" + ) return opts diff --git a/tests/pytests/unit/crypt/test_crypt_cryptography.py b/tests/pytests/unit/crypt/test_crypt_cryptography.py index c66c8776d43..f0620f5ee69 100644 --- a/tests/pytests/unit/crypt/test_crypt_cryptography.py +++ b/tests/pytests/unit/crypt/test_crypt_cryptography.py @@ -2,6 +2,7 @@ import hashlib import hmac import os +import cryptography.exceptions import pytest from cryptography.hazmat.backends.openssl import backend from cryptography.hazmat.primitives import serialization @@ -29,6 +30,45 @@ else: from . import AES, PKCS1_OAEP +OPENSSL_ENCRYPTED_KEY = """ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,12E2FE8CA93672B629477073867DE7E4 + +3wgKQlgjT3G+8rIQt97AXMDByh5u9JMZPYOB9/wg3iC3qoJXfoFAsCNUjODJBnkI +j9Zgj/bOCaSM3UshMQXmYY+2Rfi1SVQPnETlqEH/plMZS38tB8mN5pBdthgGTw6c +rhpj/S23eZT5d+z5ODeVYlWCVhx8CtE8OQEzOQk8dxLWbVHhvgC3uJGWOPR3P7VM +BlxH5LxWRCrC8vzbnwwaJnX8BTQ7fc4qeGwHlXBjpnxQhxO27pEj08NQ0/lfKh1b +seX5uiCjuQhHFKNGTuA16rQIe6BkYRHIWCDhySftl/lqSfLQif0OAXaHEdL2EdIS +ySD12RYyNUDotEzYFF+qzJ5OAtraqvc8kYror7oN52bHKCjJyrp4+DWA4/N7FjTV ++FqUyJNKqw1DAQAxlZlq+GgNyi8+g/Zs2TKTc/ZXaPLjtWYOQEQkYaNBoaD3ydY3 +c7x+uQtJLVW9BF9FSl9A7BItpqZQWKiHiGtUdhYOkemlR+zMatjBe/eTq8LrnEDa +IyI+rRo1PSDAz3n1pdEAzGAOeqwT+j7YG9O8+dybMY5FcAtiiPX21nIpmP+Rtx4X +GqzHsT7nM6QG4O8GPKuK6TniG+Q0doNWomwuau/cjQgL4C+yFiX3kIPpHz9kA/aS +NL1SJqSsvc3D/KlRbHXaJZJyhmzDuEbQynkaAdvejiajlJWAwA3BZWw1RUK7Wn8m +XcNPJL3g02uKq8SUDgVQl/cx4QawuWri2Xh8/xakNYWzNU2feoWBmV+gN2qDSxyz +Qi+xu3CzdJVrPs71lW0rEAIQvU3K3Umava9M4CUF6R7N9+Zv+m1EuMQs0IGt8VCY +Wo1KY5PAb/V718d1C3I6kXvLSDXG8xqyEleilPLhKCRGPK+2g0nGYu562EV1i6by +gr+PLnFJTfgHEzwIfsqfNoR8ReQ6AJKJoniQr4vqex9xtifuhes0odpqmUB4/B2C +UfY/SpJR6tzdrGndpB/vb1vjHumHklHHWrLONtz70BhR8Zaisc7SCmL5bFgWqzMC +MJKPulRRGQCPAzy5OI/ZULY8+dzlva1MyoCYlWjeUtcUAy+9dyA8GZv75ez9g71b +10nNINDcvGG7zWShSYrAKrvLlsoE7eZ+flG+XhI2CfiC9/zHBzy/slbaH9H+1tlO +VWKiw6iBb2TEvBk4Wpk2nUFlWKtkkBVAlgbShbE2K8pTHrJeIRv5J897k693NFZE +DjVVJirzMv/OiZTami0qBQ4nDtUZpH8FsFZ8DtREkhROsDmrjq9PGkOVaxEyF/ke +avJT34gp4OoNWj7/Rd1YNbGiWjMEB3zi9y1Q6Ufiod9ZlK3RQb4tNrpzDn/msdJo +pIkuByWjXjF4YQRKtAoeCn+CWiY7L/Qi8X7jmX27JLILlZPtTJ+aNp3eCr6ZX0dW +y0uhN89sgMewlvDA/pduL/VJRHUBZC2eD8FbD7p6K+yRKhdciS9A8F6aIhx615s6 +mngRBfwzh8ST6yQgLwCgle/XaRYTWJKjzAe3lkaIBBhHpeuq1UMAjunoS8JnLNiy +xQJ0PznzY57sYKpIiClwMjfpnX47nTU2DFWuPEXvBtG1xMjacGPbVrUslesY5bii +-----END RSA PRIVATE KEY----- +""" + + +@pytest.fixture +def openssl_encrypted_key(): + return OPENSSL_ENCRYPTED_KEY + + @pytest.fixture def passphrase(): return "pass1234" @@ -284,3 +324,51 @@ def test_sign_message_with_passphrase(signature, signing_algorithm): def test_verify_signature(): with patch("salt.utils.files.fopen", mock_open(read_data=PUBKEY_DATA.encode())): assert salt.crypt.verify_signature("/keydir/keyname.pub", MSG, SIG) + + +def test_loading_encrypted_openssl_format(openssl_encrypted_key, passphrase, tmp_path): + path = tmp_path / "key" + path.write_text(openssl_encrypted_key) + if FIPS_TESTRUN: + with pytest.raises(ValueError): + salt.crypt.get_rsa_key(path, passphrase) + else: + try: + salt.crypt.get_rsa_key(path, passphrase) + # BaseException to catch errors bubbling up from the cryptogrphy's + # rust layer. + except BaseException as exc: # pylint: disable=broad-except + pytest.fail(f"Unexpected exception: {exc}") + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode") +def test_fips_bad_signing_algo(private_key, passphrase): + key = salt.crypt.PrivateKey(private_key, passphrase) + with pytest.raises(cryptography.exceptions.UnsupportedAlgorithm): + key.sign("meh", salt.crypt.PKCS1v15_SHA1) + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode") +def test_fips_bad_signing_algo_verification(private_key, passphrase): + lpriv = LegacyPrivateKey(private_key.encode(), passphrase.encode()) + data = b"meh" + signature = lpriv.sign(data) + pubkey = salt.crypt.PublicKey(private_key.replace(".pem", ".pub")) + # cryptogrpahy silently returns False on unsuppoted algorithm + assert pubkey.verify(signature, salt.crypt.PKCS1v15_SHA1) is False + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode") +def test_fips_bad_encryption_algo(private_key, passphrase): + key = salt.crypt.PublicKey(private_key.replace(".pem", ".pub")) + with pytest.raises(cryptography.exceptions.UnsupportedAlgorithm): + key.encrypt("meh", salt.crypt.OAEP_SHA1) + + +@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode") +def test_fips_bad_decryption_algo(private_key, passphrase): + pubkey = LegacyPublicKey(private_key.replace(".pem", ".pub")) + data = pubkey.encrypt("meh") + key = salt.crypt.PrivateKey(private_key, passphrase) + with pytest.raises(cryptography.exceptions.UnsupportedAlgorithm): + key.decrypt(data) diff --git a/tests/pytests/unit/transport/test_zeromq.py b/tests/pytests/unit/transport/test_zeromq.py index 5ce76bdd19f..b9de5f514aa 100644 --- a/tests/pytests/unit/transport/test_zeromq.py +++ b/tests/pytests/unit/transport/test_zeromq.py @@ -28,19 +28,9 @@ import salt.utils.platform import salt.utils.process import salt.utils.stringutils from salt.master import SMaster +from tests.conftest import FIPS_TESTRUN from tests.support.mock import AsyncMock, MagicMock, patch -try: - from M2Crypto import RSA - - HAS_M2 = True -except ImportError: - HAS_M2 = False - try: - from Cryptodome.Cipher import PKCS1_OAEP - except ImportError: - from Crypto.Cipher import PKCS1_OAEP # nosec - log = logging.getLogger(__name__) @@ -224,6 +214,20 @@ oQIDAQAB AES_KEY = "8wxWlOaMMQ4d3yT74LL4+hGrGTf65w8VgrcNjLJeLRQ2Q6zMa8ItY2EQUgMKKDb7JY+RnPUxbB0=" +@pytest.fixture +def signing_algorithm(): + if FIPS_TESTRUN: + return salt.crypt.PKCS1v15_SHA224 + return salt.crypt.PKCS1v15_SHA1 + + +@pytest.fixture +def encryption_algorithm(): + if FIPS_TESTRUN: + return salt.crypt.OAEP_SHA224 + return salt.crypt.OAEP_SHA1 + + @pytest.fixture def pki_dir(tmp_path): _pki_dir = tmp_path / "pki" @@ -605,37 +609,44 @@ def test_zeromq_async_pub_channel_filtering_decode_message( assert res.result()["enc"] == "aes" -def test_req_server_chan_encrypt_v2(pki_dir): +def test_req_server_chan_encrypt_v2( + pki_dir, encryption_algorithm, signing_algorithm, master_opts +): loop = salt.ext.tornado.ioloop.IOLoop.current() - opts = { - "worker_threads": 1, - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "zmq_monitor": False, - "mworker_queue_niceness": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("master")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - } - server = salt.channel.server.ReqServerChannel.factory(opts) + master_opts.update( + { + "worker_threads": 1, + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "zmq_monitor": False, + "mworker_queue_niceness": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("master")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + } + ) + server = salt.channel.server.ReqServerChannel.factory(master_opts) dictkey = "pillar" nonce = "abcdefg" pillar_data = {"pillar1": "meh"} - ret = server._encrypt_private(pillar_data, dictkey, "minion", nonce) + ret = server._encrypt_private( + pillar_data, + dictkey, + "minion", + nonce, + encryption_algorithm=encryption_algorithm, + signing_algorithm=signing_algorithm, + ) assert "key" in ret assert dictkey in ret - key = salt.crypt.get_rsa_key(str(pki_dir.joinpath("minion", "minion.pem")), None) - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) # pylint: disable=used-before-assignment - aes = cipher.decrypt(ret["key"]) - pcrypt = salt.crypt.Crypticle(opts, aes) + key = salt.crypt.PrivateKey(str(pki_dir.joinpath("minion", "minion.pem"))) + aes = key.decrypt(ret["key"], encryption_algorithm) + pcrypt = salt.crypt.Crypticle(master_opts, aes) signed_msg = pcrypt.loads(ret[dictkey]) assert "sig" in signed_msg @@ -649,93 +660,105 @@ def test_req_server_chan_encrypt_v2(pki_dir): assert data["pillar"] == pillar_data -def test_req_server_chan_encrypt_v1(pki_dir): +def test_req_server_chan_encrypt_v1(pki_dir, encryption_algorithm, master_opts): loop = salt.ext.tornado.ioloop.IOLoop.current() - opts = { - "worker_threads": 1, - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "zmq_monitor": False, - "mworker_queue_niceness": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("master")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - } - server = salt.channel.server.ReqServerChannel.factory(opts) + master_opts.update( + { + "worker_threads": 1, + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "zmq_monitor": False, + "mworker_queue_niceness": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("master")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + } + ) + server = salt.channel.server.ReqServerChannel.factory(master_opts) dictkey = "pillar" nonce = "abcdefg" pillar_data = {"pillar1": "meh"} - ret = server._encrypt_private(pillar_data, dictkey, "minion", sign_messages=False) + ret = server._encrypt_private( + pillar_data, + dictkey, + "minion", + sign_messages=False, + encryption_algorithm=encryption_algorithm, + ) assert "key" in ret assert dictkey in ret - key = salt.crypt.get_rsa_key(str(pki_dir.joinpath("minion", "minion.pem")), None) - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - aes = cipher.decrypt(ret["key"]) - pcrypt = salt.crypt.Crypticle(opts, aes) + key = salt.crypt.PrivateKey(str(pki_dir.joinpath("minion", "minion.pem"))) + aes = key.decrypt(ret["key"], encryption_algorithm) + pcrypt = salt.crypt.Crypticle(master_opts, aes) data = pcrypt.loads(ret[dictkey]) assert data == pillar_data -def test_req_chan_decode_data_dict_entry_v1(pki_dir): +def test_req_chan_decode_data_dict_entry_v1( + pki_dir, encryption_algorithm, minion_opts, master_opts +): mockloop = MagicMock() - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) + master_opts = dict(master_opts, pki_dir=str(pki_dir.joinpath("master"))) server = salt.channel.server.ReqServerChannel.factory(master_opts) - client = salt.channel.client.ReqChannel.factory(opts, io_loop=mockloop) + client = salt.channel.client.ReqChannel.factory(minion_opts, io_loop=mockloop) dictkey = "pillar" target = "minion" pillar_data = {"pillar1": "meh"} - ret = server._encrypt_private(pillar_data, dictkey, target, sign_messages=False) + ret = server._encrypt_private( + pillar_data, + dictkey, + target, + sign_messages=False, + encryption_algorithm=encryption_algorithm, + ) key = client.auth.get_keys() - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - aes = cipher.decrypt(ret["key"]) + aes = key.decrypt(ret["key"], encryption_algorithm) pcrypt = salt.crypt.Crypticle(client.opts, aes) ret_pillar_data = pcrypt.loads(ret[dictkey]) assert ret_pillar_data == pillar_data -async def test_req_chan_decode_data_dict_entry_v2(pki_dir): +async def test_req_chan_decode_data_dict_entry_v2(minion_opts, master_opts, pki_dir): mockloop = MagicMock() - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) server = salt.channel.server.ReqServerChannel.factory(master_opts) - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=mockloop) + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=mockloop) dictkey = "pillar" target = "minion" @@ -743,7 +766,7 @@ async def test_req_chan_decode_data_dict_entry_v2(pki_dir): # Mock auth and message client. auth = client.auth - auth._crypticle = salt.crypt.Crypticle(opts, AES_KEY) + auth._crypticle = salt.crypt.Crypticle(minion_opts, AES_KEY) client.auth = MagicMock() client.auth.mpub = auth.mpub client.auth.authenticated = True @@ -752,12 +775,20 @@ async def test_req_chan_decode_data_dict_entry_v2(pki_dir): client.auth.crypticle.loads = auth.crypticle.loads client.transport = MagicMock() + print(minion_opts["encryption_algorithm"]) + @salt.ext.tornado.gen.coroutine def mocksend(msg, timeout=60, tries=3): client.transport.msg = msg load = client.auth.crypticle.loads(msg["load"]) ret = server._encrypt_private( - pillar_data, dictkey, target, nonce=load["nonce"], sign_messages=True + pillar_data, + dictkey, + target, + nonce=load["nonce"], + sign_messages=True, + encryption_algorithm=minion_opts["encryption_algorithm"], + signing_algorithm=minion_opts["signing_algorithm"], ) raise salt.ext.tornado.gen.Return(ret) @@ -784,24 +815,28 @@ async def test_req_chan_decode_data_dict_entry_v2(pki_dir): assert ret == {"pillar1": "meh"} -async def test_req_chan_decode_data_dict_entry_v2_bad_nonce(pki_dir): +async def test_req_chan_decode_data_dict_entry_v2_bad_nonce( + pki_dir, minion_opts, master_opts +): mockloop = MagicMock() - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) server = salt.channel.server.ReqServerChannel.factory(master_opts) - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=mockloop) + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=mockloop) dictkey = "pillar" badnonce = "abcdefg" @@ -810,7 +845,7 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_nonce(pki_dir): # Mock auth and message client. auth = client.auth - auth._crypticle = salt.crypt.Crypticle(opts, AES_KEY) + auth._crypticle = salt.crypt.Crypticle(minion_opts, AES_KEY) client.auth = MagicMock() client.auth.mpub = auth.mpub client.auth.authenticated = True @@ -819,7 +854,13 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_nonce(pki_dir): client.auth.crypticle.loads = auth.crypticle.loads client.transport = MagicMock() ret = server._encrypt_private( - pillar_data, dictkey, target, nonce=badnonce, sign_messages=True + pillar_data, + dictkey, + target, + nonce=badnonce, + sign_messages=True, + encryption_algorithm=minion_opts["encryption_algorithm"], + signing_algorithm=minion_opts["signing_algorithm"], ) @salt.ext.tornado.gen.coroutine @@ -850,24 +891,28 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_nonce(pki_dir): assert "Pillar nonce verification failed." == excinfo.value.message -async def test_req_chan_decode_data_dict_entry_v2_bad_signature(pki_dir): +async def test_req_chan_decode_data_dict_entry_v2_bad_signature( + pki_dir, minion_opts, master_opts +): mockloop = MagicMock() - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) server = salt.channel.server.ReqServerChannel.factory(master_opts) - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=mockloop) + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=mockloop) dictkey = "pillar" badnonce = "abcdefg" @@ -876,7 +921,7 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_signature(pki_dir): # Mock auth and message client. auth = client.auth - auth._crypticle = salt.crypt.Crypticle(opts, AES_KEY) + auth._crypticle = salt.crypt.Crypticle(minion_opts, AES_KEY) client.auth = MagicMock() client.auth.mpub = auth.mpub client.auth.authenticated = True @@ -890,15 +935,17 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_signature(pki_dir): client.transport.msg = msg load = client.auth.crypticle.loads(msg["load"]) ret = server._encrypt_private( - pillar_data, dictkey, target, nonce=load["nonce"], sign_messages=True + pillar_data, + dictkey, + target, + nonce=load["nonce"], + sign_messages=True, + encryption_algorithm=minion_opts["encryption_algorithm"], + signing_algorithm=minion_opts["signing_algorithm"], ) key = client.auth.get_keys() - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - aes = cipher.decrypt(ret["key"]) + aes = key.decrypt(ret["key"], minion_opts["encryption_algorithm"]) pcrypt = salt.crypt.Crypticle(client.opts, aes) signed_msg = pcrypt.loads(ret[dictkey]) # Changing the pillar data will cause the signature verification to @@ -932,24 +979,28 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_signature(pki_dir): assert "Pillar payload signature failed to validate." == excinfo.value.message -async def test_req_chan_decode_data_dict_entry_v2_bad_key(pki_dir): +async def test_req_chan_decode_data_dict_entry_v2_bad_key( + pki_dir, minion_opts, master_opts +): mockloop = MagicMock() - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) server = salt.channel.server.ReqServerChannel.factory(master_opts) - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=mockloop) + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=mockloop) dictkey = "pillar" badnonce = "abcdefg" @@ -958,7 +1009,7 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_key(pki_dir): # Mock auth and message client. auth = client.auth - auth._crypticle = salt.crypt.Crypticle(opts, AES_KEY) + auth._crypticle = salt.crypt.Crypticle(master_opts, AES_KEY) client.auth = MagicMock() client.auth.mpub = auth.mpub client.auth.authenticated = True @@ -972,30 +1023,28 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_key(pki_dir): client.transport.msg = msg load = client.auth.crypticle.loads(msg["load"]) ret = server._encrypt_private( - pillar_data, dictkey, target, nonce=load["nonce"], sign_messages=True + pillar_data, + dictkey, + target, + nonce=load["nonce"], + sign_messages=True, + encryption_algorithm=minion_opts["encryption_algorithm"], + signing_algorithm=minion_opts["signing_algorithm"], ) - key = client.auth.get_keys() - if HAS_M2: - aes = key.private_decrypt(ret["key"], RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(key) - aes = cipher.decrypt(ret["key"]) + mkey = client.auth.get_keys() + aes = mkey.decrypt(ret["key"], minion_opts["encryption_algorithm"]) pcrypt = salt.crypt.Crypticle(client.opts, aes) signed_msg = pcrypt.loads(ret[dictkey]) # Now encrypt with a different key key = salt.crypt.Crypticle.generate_key_string() - pcrypt = salt.crypt.Crypticle(opts, key) + pcrypt = salt.crypt.Crypticle(master_opts, key) pubfn = os.path.join(master_opts["pki_dir"], "minions", "minion") - pub = salt.crypt.get_rsa_pub_key(pubfn) + pub = salt.crypt.PublicKey(pubfn) ret[dictkey] = pcrypt.dumps(signed_msg) key = salt.utils.stringutils.to_bytes(key) - if HAS_M2: - ret["key"] = pub.public_encrypt(key, RSA.pkcs1_oaep_padding) - else: - cipher = PKCS1_OAEP.new(pub) - ret["key"] = cipher.encrypt(key) + ret["key"] = pub.encrypt(key, minion_opts["encryption_algorithm"]) raise salt.ext.tornado.gen.Return(ret) client.transport.send = mocksend @@ -1013,33 +1062,39 @@ async def test_req_chan_decode_data_dict_entry_v2_bad_key(pki_dir): "cmd": "_pillar", } - with pytest.raises(salt.crypt.AuthenticationError) as excinfo: - await client.crypted_transfer_decode_dictentry( - load, - dictkey="pillar", - ) - assert "Key verification failed." == excinfo.value.message + try: + with pytest.raises(salt.crypt.AuthenticationError) as excinfo: + await client.crypted_transfer_decode_dictentry( + load, + dictkey="pillar", + ) + assert "Key verification failed." == excinfo.value.message + finally: + client.close() + server.close() -async def test_req_serv_auth_v1(pki_dir): - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "max_minions": 0, - "auto_accept": False, - "open_mode": False, - "key_pass": None, - "master_sign_pubkey": False, - "publish_port": 4505, - "auth_mode": 1, - } +async def test_req_serv_auth_v1(pki_dir, minion_opts, master_opts): + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "master_sign_pubkey": False, + "publish_port": 4505, + "auth_mode": 1, + } + ) SMaster.secrets["aes"] = { "secret": multiprocessing.Array( ctypes.c_char, @@ -1047,7 +1102,7 @@ async def test_req_serv_auth_v1(pki_dir): ), "reload": salt.crypt.Crypticle.generate_key_string, } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False @@ -1069,30 +1124,34 @@ async def test_req_serv_auth_v1(pki_dir): "id": "minion", "token": token, "pub": pub_key, + "enc_algo": minion_opts["encryption_algorithm"], + "sig_algo": minion_opts["signing_algorithm"], } ret = server._auth(load, sign_messages=False) assert "load" not in ret -async def test_req_serv_auth_v2(pki_dir): - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "max_minions": 0, - "auto_accept": False, - "open_mode": False, - "key_pass": None, - "master_sign_pubkey": False, - "publish_port": 4505, - "auth_mode": 1, - } +async def test_req_serv_auth_v2(pki_dir, minion_opts, master_opts): + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "master_sign_pubkey": False, + "publish_port": 4505, + "auth_mode": 1, + } + ) SMaster.secrets["aes"] = { "secret": multiprocessing.Array( ctypes.c_char, @@ -1100,7 +1159,7 @@ async def test_req_serv_auth_v2(pki_dir): ), "reload": salt.crypt.Crypticle.generate_key_string, } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False @@ -1123,32 +1182,36 @@ async def test_req_serv_auth_v2(pki_dir): "nonce": nonce, "token": token, "pub": pub_key, + "enc_algo": minion_opts["encryption_algorithm"], + "sig_algo": minion_opts["signing_algorithm"], } ret = server._auth(load, sign_messages=True) assert "sig" in ret assert "load" in ret -async def test_req_chan_auth_v2(pki_dir, io_loop): - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "max_minions": 0, - "auto_accept": False, - "open_mode": False, - "key_pass": None, - "publish_port": 4505, - "auth_mode": 1, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } +async def test_req_chan_auth_v2(pki_dir, io_loop, minion_opts, master_opts): + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "publish_port": 4505, + "auth_mode": 1, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) SMaster.secrets["aes"] = { "secret": multiprocessing.Array( ctypes.c_char, @@ -1156,15 +1219,15 @@ async def test_req_chan_auth_v2(pki_dir, io_loop): ), "reload": salt.crypt.Crypticle.generate_key_string, } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) master_opts["master_sign_pubkey"] = False server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False server.master_key = salt.crypt.MasterKeys(server.opts) - opts["verify_master_pubkey_sign"] = False - opts["always_verify_signature"] = False - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=io_loop) + minion_opts["verify_master_pubkey_sign"] = False + minion_opts["always_verify_signature"] = False + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=io_loop) signin_payload = client.auth.minion_sign_in_payload() pload = client._package_load(signin_payload) assert "version" in pload @@ -1178,26 +1241,30 @@ async def test_req_chan_auth_v2(pki_dir, io_loop): assert "publish_port" in ret -async def test_req_chan_auth_v2_with_master_signing(pki_dir, io_loop): - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "max_minions": 0, - "auto_accept": False, - "open_mode": False, - "key_pass": None, - "publish_port": 4505, - "auth_mode": 1, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } +async def test_req_chan_auth_v2_with_master_signing( + pki_dir, io_loop, minion_opts, master_opts +): + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "publish_port": 4505, + "auth_mode": 1, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) SMaster.secrets["aes"] = { "secret": multiprocessing.Array( ctypes.c_char, @@ -1205,26 +1272,26 @@ async def test_req_chan_auth_v2_with_master_signing(pki_dir, io_loop): ), "reload": salt.crypt.Crypticle.generate_key_string, } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + master_opts = dict(master_opts, pki_dir=str(pki_dir.joinpath("master"))) master_opts["master_sign_pubkey"] = True master_opts["master_use_pubkey_signature"] = False - master_opts["signing_key_pass"] = True + master_opts["signing_key_pass"] = "" master_opts["master_sign_key_name"] = "master_sign" server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False server.master_key = salt.crypt.MasterKeys(server.opts) - opts["verify_master_pubkey_sign"] = True - opts["always_verify_signature"] = True - opts["master_sign_key_name"] = "master_sign" - opts["master"] = "master" + minion_opts["verify_master_pubkey_sign"] = True + minion_opts["always_verify_signature"] = True + minion_opts["master_sign_key_name"] = "master_sign" + minion_opts["master"] = "master" assert ( pki_dir.joinpath("minion", "minion_master.pub").read_text() == pki_dir.joinpath("master", "master.pub").read_text() ) - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=io_loop) + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=io_loop) signin_payload = client.auth.minion_sign_in_payload() pload = client._package_load(signin_payload) assert "version" in pload @@ -1269,28 +1336,32 @@ async def test_req_chan_auth_v2_with_master_signing(pki_dir, io_loop): ) -async def test_req_chan_auth_v2_new_minion_with_master_pub(pki_dir, io_loop): +async def test_req_chan_auth_v2_new_minion_with_master_pub( + pki_dir, io_loop, minion_opts, master_opts +): pki_dir.joinpath("master", "minions", "minion").unlink() - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "max_minions": 0, - "auto_accept": False, - "open_mode": False, - "key_pass": None, - "publish_port": 4505, - "auth_mode": 1, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "publish_port": 4505, + "auth_mode": 1, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) SMaster.secrets["aes"] = { "secret": multiprocessing.Array( ctypes.c_char, @@ -1298,15 +1369,15 @@ async def test_req_chan_auth_v2_new_minion_with_master_pub(pki_dir, io_loop): ), "reload": salt.crypt.Crypticle.generate_key_string, } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) master_opts["master_sign_pubkey"] = False server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False server.master_key = salt.crypt.MasterKeys(server.opts) - opts["verify_master_pubkey_sign"] = False - opts["always_verify_signature"] = False - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=io_loop) + minion_opts["verify_master_pubkey_sign"] = False + minion_opts["always_verify_signature"] = False + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=io_loop) signin_payload = client.auth.minion_sign_in_payload() pload = client._package_load(signin_payload) assert "version" in pload @@ -1318,7 +1389,9 @@ async def test_req_chan_auth_v2_new_minion_with_master_pub(pki_dir, io_loop): assert ret == "retry" -async def test_req_chan_auth_v2_new_minion_with_master_pub_bad_sig(pki_dir, io_loop): +async def test_req_chan_auth_v2_new_minion_with_master_pub_bad_sig( + pki_dir, io_loop, minion_opts, master_opts +): pki_dir.joinpath("master", "minions", "minion").unlink() @@ -1330,25 +1403,27 @@ async def test_req_chan_auth_v2_new_minion_with_master_pub_bad_sig(pki_dir, io_l mapub.unlink() mapub.write_text(MASTER2_PUB_KEY.strip()) - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "max_minions": 0, - "auto_accept": False, - "open_mode": False, - "key_pass": None, - "publish_port": 4505, - "auth_mode": 1, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "publish_port": 4505, + "auth_mode": 1, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) SMaster.secrets["aes"] = { "secret": multiprocessing.Array( ctypes.c_char, @@ -1356,15 +1431,16 @@ async def test_req_chan_auth_v2_new_minion_with_master_pub_bad_sig(pki_dir, io_l ), "reload": salt.crypt.Crypticle.generate_key_string, } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) - master_opts["master_sign_pubkey"] = False + master_opts.update( + pki_dir=str(pki_dir.joinpath("master")), master_sign_pubkey=False + ) server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False server.master_key = salt.crypt.MasterKeys(server.opts) - opts["verify_master_pubkey_sign"] = False - opts["always_verify_signature"] = False - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=io_loop) + minion_opts["verify_master_pubkey_sign"] = False + minion_opts["always_verify_signature"] = False + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=io_loop) signin_payload = client.auth.minion_sign_in_payload() pload = client._package_load(signin_payload) assert "version" in pload @@ -1376,29 +1452,36 @@ async def test_req_chan_auth_v2_new_minion_with_master_pub_bad_sig(pki_dir, io_l ret = client.auth.handle_signin_response(signin_payload, ret) -async def test_req_chan_auth_v2_new_minion_without_master_pub(pki_dir, io_loop): +async def test_req_chan_auth_v2_new_minion_without_master_pub( + minion_opts, + master_opts, + pki_dir, + io_loop, +): pki_dir.joinpath("master", "minions", "minion").unlink() pki_dir.joinpath("minion", "minion_master.pub").unlink() - opts = { - "master_uri": "tcp://127.0.0.1:4506", - "interface": "127.0.0.1", - "ret_port": 4506, - "ipv6": False, - "sock_dir": ".", - "pki_dir": str(pki_dir.joinpath("minion")), - "id": "minion", - "__role": "minion", - "keysize": 4096, - "max_minions": 0, - "auto_accept": False, - "open_mode": False, - "key_pass": None, - "publish_port": 4505, - "auth_mode": 1, - "acceptance_wait_time": 3, - "acceptance_wait_time_max": 3, - } + minion_opts.update( + { + "master_uri": "tcp://127.0.0.1:4506", + "interface": "127.0.0.1", + "ret_port": 4506, + "ipv6": False, + "sock_dir": ".", + "pki_dir": str(pki_dir.joinpath("minion")), + "id": "minion", + "__role": "minion", + "keysize": 4096, + "max_minions": 0, + "auto_accept": False, + "open_mode": False, + "key_pass": None, + "publish_port": 4505, + "auth_mode": 1, + "acceptance_wait_time": 3, + "acceptance_wait_time_max": 3, + } + ) SMaster.secrets["aes"] = { "secret": multiprocessing.Array( ctypes.c_char, @@ -1406,24 +1489,28 @@ async def test_req_chan_auth_v2_new_minion_without_master_pub(pki_dir, io_loop): ), "reload": salt.crypt.Crypticle.generate_key_string, } - master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) + master_opts.update(pki_dir=str(pki_dir.joinpath("master"))) master_opts["master_sign_pubkey"] = False server = salt.channel.server.ReqServerChannel.factory(master_opts) server.auto_key = salt.daemons.masterapi.AutoKey(server.opts) server.cache_cli = False server.master_key = salt.crypt.MasterKeys(server.opts) - opts["verify_master_pubkey_sign"] = False - opts["always_verify_signature"] = False - client = salt.channel.client.AsyncReqChannel.factory(opts, io_loop=io_loop) + minion_opts["verify_master_pubkey_sign"] = False + minion_opts["always_verify_signature"] = False + client = salt.channel.client.AsyncReqChannel.factory(minion_opts, io_loop=io_loop) signin_payload = client.auth.minion_sign_in_payload() pload = client._package_load(signin_payload) - assert "version" in pload - assert pload["version"] == 2 + try: + assert "version" in pload + assert pload["version"] == 2 - ret = server._auth(pload["load"], sign_messages=True) - assert "sig" in ret - ret = client.auth.handle_signin_response(signin_payload, ret) - assert ret == "retry" + ret = server._auth(pload["load"], sign_messages=True) + assert "sig" in ret + ret = client.auth.handle_signin_response(signin_payload, ret) + assert ret == "retry" + finally: + client.close() + server.close() async def test_req_server_garbage_request(io_loop): diff --git a/tests/pytests/unit/utils/test_crypt.py b/tests/pytests/unit/utils/test_crypt.py index ccf2cfbf46e..3aa1c409751 100644 --- a/tests/pytests/unit/utils/test_crypt.py +++ b/tests/pytests/unit/utils/test_crypt.py @@ -5,29 +5,6 @@ Unit tests for salt.utils.crypt.py import pytest import salt.utils.crypt -from tests.support.mock import patch - -try: - import M2Crypto # pylint: disable=unused-import - - HAS_M2CRYPTO = True -except ImportError: - HAS_M2CRYPTO = False - -try: - from Cryptodome import Random as CryptodomeRandom - - HAS_CYPTODOME = True -except ImportError: - HAS_CYPTODOME = False - - -try: - from Crypto import Random as CryptoRandom # nosec - - HAS_CRYPTO = True -except ImportError: - HAS_CRYPTO = False @pytest.fixture @@ -45,28 +22,6 @@ def pub_key_data(): ] -def test_random(): - # make sure the right library is used for random - if HAS_M2CRYPTO: - assert None is salt.utils.crypt.Random - elif HAS_CYPTODOME: - assert CryptodomeRandom is salt.utils.crypt.Random - elif HAS_CRYPTO: - assert CryptoRandom is salt.utils.crypt.Random - - -def test_reinit_crypto(): - # make sure reinit crypto does not crash - salt.utils.crypt.reinit_crypto() - - # make sure reinit does not crash when no crypt is found - with patch("salt.utils.crypt.HAS_M2CRYPTO", False): - with patch("salt.utils.crypt.HAS_CRYPTODOME", False): - with patch("salt.utils.crypt.HAS_CRYPTO", False): - with patch("salt.utils.crypt.Random", None): - salt.utils.crypt.reinit_crypto() - - @pytest.mark.parametrize("line_ending", ["\n", "\r\n"]) def test_pem_finger_file_line_endings(tmp_path, pub_key_data, line_ending): key_file = tmp_path / "master_crlf.pub"