diff --git a/changelog/62978.added b/changelog/62978.added new file mode 100644 index 00000000000..3262ce53a0b --- /dev/null +++ b/changelog/62978.added @@ -0,0 +1 @@ +Added output and bare functionality to export_key gpg module function diff --git a/salt/modules/gpg.py b/salt/modules/gpg.py index 46799372eb3..b0e9cc290b3 100644 --- a/salt/modules/gpg.py +++ b/salt/modules/gpg.py @@ -783,16 +783,15 @@ def import_key(text=None, filename=None, user=None, gnupghome=None): def export_key( - keyids=None, secret=False, user=None, gnupghome=None, use_passphrase=False + keyids=None, secret=False, user=None, gnupghome=None, output=None, use_passphrase=False, bare=False, ): """ Export a key from the GPG keychain keyids The key ID(s) of the key(s) to be exported. Can be specified as a comma - separated string or a list. Anything which GnuPG itself accepts to - identify a key - for example, the key ID or the fingerprint could be - used. + separated string or a list. Anything which GnuPG itself accepts to identify a key + for example, the key ID, fingerprint, user ID or email address could be used. secret Export the secret key identified by the ``keyids`` information passed. @@ -805,12 +804,19 @@ def export_key( gnupghome Specify the location where GPG keyring and related files are stored. + output + The filename where the exported key data will be written to, default is standard out. + use_passphrase - Whether to use a passphrase with the signing key. Passphrase is received - from Pillar. + Whether to use a passphrase to export the secret key. + Passphrase is received from Pillar. .. versionadded:: 3003 + bare + If ``True``, return the (armored) exported key block as a string without the + standard comment/res dict. + CLI Example: .. code-block:: bash @@ -822,18 +828,38 @@ def export_key( salt '*' gpg.export_key keyids="['3FAD9F1E','3FBD8F1E']" user=username """ + ret = {"res": True} gpg = _create_gpg(user, gnupghome) if isinstance(keyids, str): keyids = keyids.split(",") - if use_passphrase: + if secret and use_passphrase: gpg_passphrase = __salt__["pillar.get"]("gpg_passphrase") if not gpg_passphrase: raise SaltInvocationError("gpg_passphrase not available in pillar.") - ret = gpg.export_keys(keyids, secret, passphrase=gpg_passphrase) + result = gpg.export_keys(keyids, secret, passphrase=gpg_passphrase) else: - ret = gpg.export_keys(keyids, secret, expect_passphrase=False) + result = gpg.export_keys(keyids, secret, expect_passphrase=False) + + if output and result: + with salt.utils.files.flopen(output, "w") as fout: + fout.write(salt.utils.stringutils.to_str(result)) + + if result: + if not bare: + if output: + ret["comment"] = "Exported key data has been written to {}".format(output) + else: + ret["comment"] = result + else: + ret = result + else: + if not bare: + ret["res"] = False + else: + ret = False + return ret diff --git a/tests/pytests/unit/modules/test_gpg.py b/tests/pytests/unit/modules/test_gpg.py index d87df2274fa..ef8505e033f 100644 --- a/tests/pytests/unit/modules/test_gpg.py +++ b/tests/pytests/unit/modules/test_gpg.py @@ -5,6 +5,7 @@ import datetime import logging +import pathlib import shutil import subprocess import time @@ -578,7 +579,7 @@ def test_export_key_without_passphrase(gpghome): "salt.modules.gpg.gnupg.GPG.export_keys", MagicMock(return_value=GPG_TEST_PUB_KEY), ) as gnupg_export_keys: - ret = gpg.export_key("xxxxxxxxxxxxxxxx") + ret = gpg.export_key("xxxxxxxxxxxxxxxx", bare=True) assert ret == GPG_TEST_PUB_KEY gnupg_export_keys.assert_called_with( ["xxxxxxxxxxxxxxxx"], @@ -614,7 +615,8 @@ def test_export_multiple_keys_without_passphrase(gpghome): MagicMock(return_value=GPG_TEST_PUB_KEY), ) as gnupg_export_keys: ret = gpg.export_key( - "xxxxxxxxxxxxxxxx,yyyyyyyyyyyyyyyy,zzzzzzzzzzzzzzzz" + "xxxxxxxxxxxxxxxx,yyyyyyyyyyyyyyyy,zzzzzzzzzzzzzzzz", + bare=True, ) assert ret == GPG_TEST_PUB_KEY gnupg_export_keys.assert_called_with( @@ -654,7 +656,7 @@ def test_export_key_with_passphrase_without_gpg_passphrase_in_pillar(gpghome): MagicMock(return_value=GPG_TEST_PUB_KEY), ) as gnupg_export_keys: with pytest.raises(SaltInvocationError): - assert gpg.export_key("xxxxxxxxxxxxxxxx", use_passphrase=True) + assert gpg.export_key("xxxxxxxxxxxxxxxx", secret=True, use_passphrase=True) gnupg_export_keys.assert_not_called() @@ -687,14 +689,50 @@ def test_export_key_with_passphrase_with_gpg_passphrase_in_pillar(gpghome): "salt.modules.gpg.gnupg.GPG.export_keys", MagicMock(return_value=GPG_TEST_PUB_KEY), ) as gnupg_export_keys: - ret = gpg.export_key("xxxxxxxxxxxxxxxx", use_passphrase=True) + ret = gpg.export_key("xxxxxxxxxxxxxxxx", secret=True, use_passphrase=True, bare=True) assert ret == GPG_TEST_PUB_KEY + # expected gnupg_export_keys.assert_called_with( ["xxxxxxxxxxxxxxxx"], - False, + True, passphrase=GPG_TEST_KEY_PASSPHRASE, ) +def test_export_key_to_file_with_passphrase_with_gpg_passphrase_in_pillar(gpghome): + """ + Test gpg.export_key with passphrase and gpg_passphrase pillar + """ + + _user_mock = { + "shell": "/bin/bash", + "workphone": "", + "uid": 0, + "passwd": "x", + "roomnumber": "", + "gid": 0, + "groups": ["root"], + "home": str(gpghome.path), + "fullname": "root", + "homephone": "", + "name": "root", + } + + exported_keyfile = gpghome.path / "exported_key" + mock_opt = MagicMock(return_value="root") + pillar_mock = MagicMock(return_value=GPG_TEST_KEY_PASSPHRASE) + with patch.dict(gpg.__salt__, {"user.info": MagicMock(return_value=_user_mock)}): + with patch.dict(gpg.__salt__, {"config.option": mock_opt}), patch.dict( + gpg.__salt__, {"pillar.get": pillar_mock} + ): + with patch( + "salt.modules.gpg.gnupg.GPG.export_keys", + MagicMock(return_value=GPG_TEST_PUB_KEY), + ) as gnupg_export_keys: + ret = gpg.export_key(keyids=GPG_TEST_KEY_ID, secret=True, output=exported_keyfile, use_passphrase=True, bare=True) + assert ret == GPG_TEST_PUB_KEY + keyfile_contents = pathlib.Path(exported_keyfile).read_text() + assert keyfile_contents == GPG_TEST_PUB_KEY + def test_create_key_without_passphrase(gpghome): """