From 8c88fa0be4d5de8b5ec926be53b57fb756a45472 Mon Sep 17 00:00:00 2001 From: jeanluc Date: Tue, 4 Aug 2026 11:55:40 +0200 Subject: [PATCH 1/3] Add test for issue #69954 --- .../test_certificate_managed_wrapper.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/pytests/integration/ssh/x509_v2/test_certificate_managed_wrapper.py b/tests/pytests/integration/ssh/x509_v2/test_certificate_managed_wrapper.py index c1fcef666635..907ce45a2ff6 100644 --- a/tests/pytests/integration/ssh/x509_v2/test_certificate_managed_wrapper.py +++ b/tests/pytests/integration/ssh/x509_v2/test_certificate_managed_wrapper.py @@ -65,6 +65,7 @@ def cert_args_exts(): @pytest.fixture(scope="module", autouse=True) def cm_wrapper(x509_salt_master): + name = "cert" state_contents = """ {{ salt["x509.certificate_managed_wrapper"]( @@ -80,8 +81,8 @@ def cm_wrapper(x509_salt_master): ) | yaml(false) }} """ - with x509_salt_master.state_tree.base.temp_file("cert.sls", state_contents): - yield + with x509_salt_master.state_tree.base.temp_file(f"{name}.sls", state_contents): + yield name @pytest.fixture @@ -140,6 +141,20 @@ def test_certificate_managed_remote(x509_salt_ssh_cli, cert_args, ca_key, rsa_pr assert _belongs_to(cert, rsa_privkey) +def test_certificate_managed_remote_file_managed_kwargs( + x509_salt_ssh_cli, cert_args, ca_key, cm_wrapper +): + cert_args["certificate_managed"]["mode"] = "0400" + ret = x509_salt_ssh_cli.run("state.apply", cm_wrapper, pillar={"args": cert_args}) + assert ret.returncode == 0 + cert = _get_cert(cert_args["name"]) + assert cert.subject.rfc4514_string() == "CN=from_signing_policy" + assert _signed_by(cert, ca_key) + ret = x509_salt_ssh_cli.run("file.get_mode", cert_args["name"]) + assert ret.returncode == 0 + assert ret.data == "0400" + + def test_certificate_managed_remote_with_privkey_managed( x509_salt_ssh_cli, cert_args, tmp_path, ca_key ): From 0b34d0e63139a42ee7ff4171a0131e9a5bffff73 Mon Sep 17 00:00:00 2001 From: jeanluc Date: Tue, 4 Aug 2026 12:07:36 +0200 Subject: [PATCH 2/3] Correctly pass file.managed kwargs --- changelog/69954.fixed.md | 1 + salt/client/ssh/wrapper/x509_v2.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog/69954.fixed.md diff --git a/changelog/69954.fixed.md b/changelog/69954.fixed.md new file mode 100644 index 000000000000..bfa13ca15adc --- /dev/null +++ b/changelog/69954.fixed.md @@ -0,0 +1 @@ +Fixed `x509_v2.certificate_managed_wrapper` swallowing arguments in `certificate_managed` intended for `file.managed` diff --git a/salt/client/ssh/wrapper/x509_v2.py b/salt/client/ssh/wrapper/x509_v2.py index 530ae4c49aca..56f7557886b0 100644 --- a/salt/client/ssh/wrapper/x509_v2.py +++ b/salt/client/ssh/wrapper/x509_v2.py @@ -937,7 +937,7 @@ def certificate_managed_wrapper( ret[name + "_crt"] = { "x509.certificate_managed_ssh": [{k: v} for k, v in cert_ret.items()] } - ret[name + "_crt"]["x509.certificate_managed_ssh"].append( + ret[name + "_crt"]["x509.certificate_managed_ssh"].extend( {k: v} for k, v in cert_file_args.items() ) except (CommandExecutionError, SaltInvocationError) as err: From 0a238ab3b1d38376a86f7c7ed751f32faabf7fae Mon Sep 17 00:00:00 2001 From: jeanluc Date: Tue, 4 Aug 2026 12:08:27 +0200 Subject: [PATCH 3/3] Adjust ssh_pki tests The previous test for file.managed args could not have succeeded, apparently it wasn't run in CI. bcrypt is only required for handling encrypted private keys, which were not tested in the ssh_pki.certificate_managed_wrapper tests so far. --- .../test_certificate_managed_wrapper_ssh.py | 56 +++++-------------- 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/tests/pytests/integration/ssh/ssh_pki/test_certificate_managed_wrapper_ssh.py b/tests/pytests/integration/ssh/ssh_pki/test_certificate_managed_wrapper_ssh.py index 05f3ea5725e5..287171c5cbfe 100644 --- a/tests/pytests/integration/ssh/ssh_pki/test_certificate_managed_wrapper_ssh.py +++ b/tests/pytests/integration/ssh/ssh_pki/test_certificate_managed_wrapper_ssh.py @@ -200,7 +200,6 @@ def existing_symlink(request): test_file.unlink(missing_ok=True) -@pytest.mark.usefixtures("_check_bcrypt") def test_certificate_managed_remote(ssh_salt_ssh_cli, cert_args, ca_key, rsa_privkey): ret = ssh_salt_ssh_cli.run("state.apply", "cert", pillar={"args": cert_args}) assert ret.returncode == 0 @@ -210,39 +209,25 @@ def test_certificate_managed_remote(ssh_salt_ssh_cli, cert_args, ca_key, rsa_pri assert _belongs_to(cert, rsa_privkey) -@pytest.fixture -def cm_file_args(sshpki_salt_master): - state_contents = """ - {{ - salt["ssh_pki.certificate_managed_wrapper"]( - pillar["args"]["name"], - ca_server=pillar["args"]["ca_server"], - signing_policy=pillar["args"]["signing_policy"], - backend=pillar["args"].get("backend"), - backend_args=pillar["args"].get("backend_args"), - private_key_managed=pillar["args"].get("private_key_managed"), - private_key=pillar["args"].get("private_key"), - private_key_passphrase=pillar["args"].get("private_key_passphrase"), - public_key=pillar["args"].get("public_key"), - certificate_managed=pillar["args"].get("certificate_managed"), - test=opts.get("test"), - mode="0400" - ) | yaml(false) - }} - """ - with sshpki_salt_master.state_tree.base.temp_file( - "cert_file_args.sls", state_contents - ): - yield +@pytest.mark.usefixtures("_check_bcrypt") +def test_certificate_managed_remote_privkey_enc( + ssh_salt_ssh_cli, cert_args, ca_key, rsa_privkey +): + cert_args["private_key"] += "_enc" + cert_args["private_key_passphrase"] = "hunter1" + ret = ssh_salt_ssh_cli.run("state.apply", "cert", pillar={"args": cert_args}) + assert ret.returncode == 0 + cert = _get_cert(cert_args["name"]) + assert cert.key_id == b"from_signing_policy" + assert _signed_by(cert, ca_key) + assert _belongs_to(cert, rsa_privkey) -@pytest.mark.usefixtures("_check_bcrypt", "cm_file_args") def test_certificate_managed_remote_file_managed_kwargs( ssh_salt_ssh_cli, cert_args, ca_key, rsa_privkey ): - ret = ssh_salt_ssh_cli.run( - "state.apply", "cert_file_args", pillar={"args": cert_args} - ) + cert_args["certificate_managed"]["mode"] = "0400" + ret = ssh_salt_ssh_cli.run("state.apply", "cert", pillar={"args": cert_args}) assert ret.returncode == 0 cert = _get_cert(cert_args["name"]) assert cert.key_id == b"from_signing_policy" @@ -253,7 +238,6 @@ def test_certificate_managed_remote_file_managed_kwargs( assert ret.data == "0400" -@pytest.mark.usefixtures("_check_bcrypt") def test_certificate_managed_remote_with_privkey_managed( ssh_salt_ssh_cli, cert_args, tmp_path, ca_key ): @@ -273,7 +257,6 @@ def test_certificate_managed_remote_with_privkey_managed( assert ret.data[state]["changes"] -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("existing_cert") def test_certificate_managed_remote_no_changes(ssh_salt_ssh_cli, cert_args): ret = ssh_salt_ssh_cli.run("state.apply", "cert", pillar={"args": cert_args}) @@ -281,7 +264,6 @@ def test_certificate_managed_remote_no_changes(ssh_salt_ssh_cli, cert_args): assert ret.data[next(iter(ret.data))]["changes"] == {} -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("existing_cert") @pytest.mark.parametrize("existing_cert", ({"private_key_managed": {}},), indirect=True) def test_certificate_managed_remote_no_changes_with_privkey_managed( @@ -300,7 +282,6 @@ def test_certificate_managed_remote_no_changes_with_privkey_managed( assert ret.data[state]["changes"] == {} -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("existing_cert") def test_certificate_managed_remote_policy_change(ssh_salt_ssh_cli, cert_args): cert_args["signing_policy"] = "testchangepolicy" @@ -311,7 +292,6 @@ def test_certificate_managed_remote_policy_change(ssh_salt_ssh_cli, cert_args): assert cert.key_id == b"from_changed_signing_policy" -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("existing_cert") @pytest.mark.parametrize("existing_cert", ({"private_key_managed": {}},), indirect=True) def test_certificate_managed_remote_policy_change_with_privkey_managed( @@ -338,7 +318,6 @@ def test_certificate_managed_remote_policy_change_with_privkey_managed( assert not ret.data[state]["changes"] -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("existing_cert") @pytest.mark.parametrize( "existing_cert", ({"private_key_managed": {"new": True}},), indirect=True @@ -369,7 +348,6 @@ def test_certificate_managed_remote_policy_change_with_privkey_managed_new( assert not ret.data[state]["changes"] -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("existing_cert") def test_certificate_managed_remote_signing_key_change(ssh_salt_ssh_cli, cert_args): cert_args["signing_policy"] = "testchangecapolicy" @@ -381,7 +359,6 @@ def test_certificate_managed_remote_signing_key_change(ssh_salt_ssh_cli, cert_ar assert "signing_private_key" in changes -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("existing_cert") def test_certificate_managed_remote_no_changes_signing_policy_override( ssh_salt_ssh_cli, cert_args @@ -394,7 +371,6 @@ def test_certificate_managed_remote_no_changes_signing_policy_override( assert ret.data[next(iter(ret.data))]["changes"] == {} -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.parametrize("overwrite", (False, True)) def test_certificate_managed_privkey_managed_existing_not_a_privkey( ssh_salt_ssh_cli, cert_args, ca_key, existing_file, overwrite @@ -408,7 +384,6 @@ def test_certificate_managed_privkey_managed_existing_not_a_privkey( ) -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.parametrize("overwrite", (False, True)) def test_certificate_managed_privkey_managed_existing_symlink( ssh_salt_ssh_cli, cert_args, ca_key, existing_symlink, overwrite @@ -455,7 +430,6 @@ def _test_certificate_managed_existing_path( assert bool(ret.data[state]["changes"]) is ("symlink" in existing.name) -@pytest.mark.usefixtures("_check_bcrypt") def test_certificate_managed_existing_not_a_cert( ssh_salt_ssh_cli, cert_args, existing_file, rsa_privkey, ca_key ): @@ -474,7 +448,6 @@ def test_certificate_managed_existing_not_a_cert( assert _belongs_to(cert, rsa_privkey) -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("existing_cert") def test_certificate_managed_remote_renew(ssh_salt_ssh_cli, cert_args): cert_cur = _get_cert(cert_args["name"]) @@ -498,7 +471,6 @@ def test_certificate_managed_different_backend(ssh_salt_ssh_cli, cert_args, cert assert cert.public_bytes().decode().strip() == cert_exts -@pytest.mark.usefixtures("_check_bcrypt") @pytest.mark.usefixtures("other_backend") @pytest.mark.usefixtures("existing_cert") def test_certificate_managed_existing_different_backend(