From 17743523136d0562818935f58eb3893afa27c269 Mon Sep 17 00:00:00 2001 From: Fernando Giorgetti Date: Mon, 25 May 2020 05:09:18 -0300 Subject: [PATCH] java_keystore - Added support for private key pass phrase (#276) (#276) * This fixes (#275) * Migrated PR from https://github.com/ansible/ansible/pull/47768 * Applied requested changes * Fixed issue with load_file_common_arguments * Using args list when calling run_commands * Keytool now reads passwords from stdin * Fixed PEP8 indentation issues --- ...-java_keystore-private_key_passphrase.yaml | 2 + plugins/modules/system/java_keystore.py | 60 +++++++++++------ .../modules/system/test_java_keystore.py | 66 ++++++++++++++----- 3 files changed, 89 insertions(+), 39 deletions(-) create mode 100644 changelogs/fragments/276-java_keystore-private_key_passphrase.yaml diff --git a/changelogs/fragments/276-java_keystore-private_key_passphrase.yaml b/changelogs/fragments/276-java_keystore-private_key_passphrase.yaml new file mode 100644 index 0000000000..b15cf7a070 --- /dev/null +++ b/changelogs/fragments/276-java_keystore-private_key_passphrase.yaml @@ -0,0 +1,2 @@ +minor_changes: + - java_keystore - add the private_key_passphrase parameter (https://github.com/ansible-collections/community.general/pull/276). diff --git a/plugins/modules/system/java_keystore.py b/plugins/modules/system/java_keystore.py index 8677b48943..7a2b669842 100644 --- a/plugins/modules/system/java_keystore.py +++ b/plugins/modules/system/java_keystore.py @@ -27,6 +27,12 @@ options: description: - Private key that should be used to create the key store. required: true + private_key_passphrase: + description: + - Pass phrase for reading the private key, if required. + type: str + required: false + version_added: "2.10" password: description: - Password that should be used to secure the key store. @@ -110,7 +116,7 @@ import re def read_certificate_fingerprint(module, openssl_bin, certificate_path): - current_certificate_fingerprint_cmd = "%s x509 -noout -in %s -fingerprint -sha256" % (openssl_bin, certificate_path) + current_certificate_fingerprint_cmd = [openssl_bin, "x509", "-noout", "-in", certificate_path, "-fingerprint", "-sha256"] (rc, current_certificate_fingerprint_out, current_certificate_fingerprint_err) = run_commands(module, current_certificate_fingerprint_cmd) if rc != 0: return module.fail_json(msg=current_certificate_fingerprint_out, @@ -130,7 +136,7 @@ def read_certificate_fingerprint(module, openssl_bin, certificate_path): def read_stored_certificate_fingerprint(module, keytool_bin, alias, keystore_path, keystore_password): - stored_certificate_fingerprint_cmd = "%s -list -alias '%s' -keystore '%s' -storepass '%s' -v" % (keytool_bin, alias, keystore_path, keystore_password) + stored_certificate_fingerprint_cmd = [keytool_bin, "-list", "-alias", alias, "-keystore", keystore_path, "-storepass", keystore_password, "-v"] (rc, stored_certificate_fingerprint_out, stored_certificate_fingerprint_err) = run_commands(module, stored_certificate_fingerprint_cmd) if rc != 0: if "keytool error: java.lang.Exception: Alias <%s> does not exist" % alias not in stored_certificate_fingerprint_out: @@ -152,8 +158,8 @@ def read_stored_certificate_fingerprint(module, keytool_bin, alias, keystore_pat return stored_certificate_match.group(1) -def run_commands(module, cmd, check_rc=True): - return module.run_command(cmd, check_rc) +def run_commands(module, cmd, data=None, check_rc=True): + return module.run_command(cmd, check_rc=check_rc, data=data) def create_file(path, content): @@ -180,7 +186,7 @@ def cert_changed(module, openssl_bin, keytool_bin, keystore_path, keystore_pass, os.remove(certificate_path) -def create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password): +def create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password, keypass): if module.check_mode: module.exit_json(changed=True) else: @@ -194,23 +200,33 @@ def create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password): if os.path.exists(keystore_p12_path): os.remove(keystore_p12_path) - export_p12_cmd = "%s pkcs12 -export -name '%s' -in '%s' -inkey '%s' -out '%s' -passout 'pass:%s'" % ( - openssl_bin, name, certificate_path, private_key_path, keystore_p12_path, password) - (rc, export_p12_out, export_p12_err) = run_commands(module, export_p12_cmd) + export_p12_cmd = [openssl_bin, "pkcs12", "-export", "-name", name, "-in", certificate_path, + "-inkey", private_key_path, "-out", + keystore_p12_path, "-passout", "stdin"] + + # when keypass is provided, add -passin + cmd_stdin = "" + if keypass: + export_p12_cmd.append("-passin") + export_p12_cmd.append("stdin") + cmd_stdin = "%s\n" % keypass + + cmd_stdin += "%s\n%s" % (password, password) + (rc, export_p12_out, export_p12_err) = run_commands(module, export_p12_cmd, data=cmd_stdin) if rc != 0: return module.fail_json(msg=export_p12_out, rc=rc, cmd=export_p12_cmd) - import_keystore_cmd = "%s -importkeystore " \ - "-destkeystore '%s' " \ - "-srckeystore '%s' " \ - "-srcstoretype pkcs12 " \ - "-alias '%s' " \ - "-deststorepass '%s' " \ - "-srcstorepass '%s' " \ - "-noprompt" % (keytool_bin, keystore_path, keystore_p12_path, name, password, password) - (rc, import_keystore_out, import_keystore_err) = run_commands(module, import_keystore_cmd) + import_keystore_cmd = [keytool_bin, "-importkeystore", + "-destkeystore", keystore_path, + "-srckeystore", keystore_p12_path, + "-srcstoretype", "pkcs12", + "-alias", name, + "-deststorepass", password, + "-srcstorepass", password, + "-noprompt"] + (rc, import_keystore_out, import_keystore_err) = run_commands(module, import_keystore_cmd, data=None) if rc == 0: update_jks_perm(module, keystore_path) return module.exit_json(changed=True, @@ -241,6 +257,7 @@ def update_jks_perm(module, keystore_path): def process_jks(module): name = module.params['name'] password = module.params['password'] + keypass = module.params['private_key_passphrase'] keystore_path = module.params['dest'] force = module.params['force'] openssl_bin = module.get_bin_path('openssl', True) @@ -248,16 +265,16 @@ def process_jks(module): if os.path.exists(keystore_path): if force: - create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password) + create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password, keypass) else: if cert_changed(module, openssl_bin, keytool_bin, keystore_path, password, name): - create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password) + create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password, keypass) else: if not module.check_mode: update_jks_perm(module, keystore_path) return module.exit_json(changed=False) else: - create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password) + create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password, keypass) class ArgumentSpec(object): @@ -270,7 +287,8 @@ class ArgumentSpec(object): private_key=dict(required=True, no_log=True), password=dict(required=True, no_log=True), dest=dict(required=True), - force=dict(required=False, default=False, type='bool') + force=dict(required=False, default=False, type='bool'), + private_key_passphrase=dict(required=False, no_log=True, type='str') ) self.argument_spec = argument_spec diff --git a/tests/unit/plugins/modules/system/test_java_keystore.py b/tests/unit/plugins/modules/system/test_java_keystore.py index 27f6b86efb..8ba0a13de0 100644 --- a/tests/unit/plugins/modules/system/test_java_keystore.py +++ b/tests/unit/plugins/modules/system/test_java_keystore.py @@ -64,19 +64,49 @@ class TestCreateJavaKeystore(ModuleTestCase): module.exit_json = Mock() with patch('os.remove', return_value=True): - self.run_commands.side_effect = lambda args, kwargs: (0, '', '') - create_jks(module, "test", "openssl", "keytool", "/path/to/keystore.jks", "changeit") + self.run_commands.side_effect = lambda module, cmd, data: (0, '', '') + create_jks(module, "test", "openssl", "keytool", "/path/to/keystore.jks", "changeit", "") module.exit_json.assert_called_once_with( changed=True, - cmd="keytool -importkeystore " - "-destkeystore '/path/to/keystore.jks' " - "-srckeystore '/tmp/keystore.p12' -srcstoretype pkcs12 -alias 'test' " - "-deststorepass 'changeit' -srcstorepass 'changeit' -noprompt", + cmd=["keytool", "-importkeystore", + "-destkeystore", "/path/to/keystore.jks", + "-srckeystore", "/tmp/keystore.p12", "-srcstoretype", "pkcs12", "-alias", "test", + "-deststorepass", "changeit", "-srcstorepass", "changeit", "-noprompt"], msg='', rc=0, stdout_lines='' ) + def test_create_jks_keypass_fail_export_pkcs12(self): + set_module_args(dict( + certificate='cert-foo', + private_key='private-foo', + private_key_passphrase='passphrase-foo', + dest='/path/to/keystore.jks', + name='foo', + password='changeit' + )) + + module = AnsibleModule( + argument_spec=self.spec.argument_spec, + supports_check_mode=self.spec.supports_check_mode + ) + + module.fail_json = Mock() + + with patch('os.remove', return_value=True): + self.run_commands.side_effect = [(1, '', ''), (0, '', '')] + create_jks(module, "test", "openssl", "keytool", "/path/to/keystore.jks", "changeit", "passphrase-foo") + module.fail_json.assert_called_once_with( + cmd=["openssl", "pkcs12", "-export", "-name", "test", + "-in", "/tmp/foo.crt", "-inkey", "/tmp/foo.key", + "-out", "/tmp/keystore.p12", + "-passout", "stdin", + "-passin", "stdin"], + msg='', + rc=1 + ) + def test_create_jks_fail_export_pkcs12(self): set_module_args(dict( certificate='cert-foo', @@ -95,12 +125,12 @@ class TestCreateJavaKeystore(ModuleTestCase): with patch('os.remove', return_value=True): self.run_commands.side_effect = [(1, '', ''), (0, '', '')] - create_jks(module, "test", "openssl", "keytool", "/path/to/keystore.jks", "changeit") + create_jks(module, "test", "openssl", "keytool", "/path/to/keystore.jks", "changeit", "") module.fail_json.assert_called_once_with( - cmd="openssl pkcs12 -export -name 'test' " - "-in '/tmp/foo.crt' -inkey '/tmp/foo.key' " - "-out '/tmp/keystore.p12' " - "-passout 'pass:changeit'", + cmd=["openssl", "pkcs12", "-export", "-name", "test", + "-in", "/tmp/foo.crt", "-inkey", "/tmp/foo.key", + "-out", "/tmp/keystore.p12", + "-passout", "stdin"], msg='', rc=1 ) @@ -123,12 +153,12 @@ class TestCreateJavaKeystore(ModuleTestCase): with patch('os.remove', return_value=True): self.run_commands.side_effect = [(0, '', ''), (1, '', '')] - create_jks(module, "test", "openssl", "keytool", "/path/to/keystore.jks", "changeit") + create_jks(module, "test", "openssl", "keytool", "/path/to/keystore.jks", "changeit", "") module.fail_json.assert_called_once_with( - cmd="keytool -importkeystore " - "-destkeystore '/path/to/keystore.jks' " - "-srckeystore '/tmp/keystore.p12' -srcstoretype pkcs12 -alias 'test' " - "-deststorepass 'changeit' -srcstorepass 'changeit' -noprompt", + cmd=["keytool", "-importkeystore", + "-destkeystore", "/path/to/keystore.jks", + "-srckeystore", "/tmp/keystore.p12", "-srcstoretype", "pkcs12", "-alias", "test", + "-deststorepass", "changeit", "-srcstorepass", "changeit", "-noprompt"], msg='', rc=1 ) @@ -231,7 +261,7 @@ class TestCertChanged(ModuleTestCase): self.run_commands.side_effect = [(1, '', 'Oops'), (0, 'SHA256: wxyz:9876:stuv', '')] cert_changed(module, "openssl", "keytool", "/path/to/keystore.jks", "changeit", 'foo') module.fail_json.assert_called_once_with( - cmd="openssl x509 -noout -in /tmp/foo.crt -fingerprint -sha256", + cmd=["openssl", "x509", "-noout", "-in", "/tmp/foo.crt", "-fingerprint", "-sha256"], msg='', err='Oops', rc=1 @@ -257,7 +287,7 @@ class TestCertChanged(ModuleTestCase): self.run_commands.side_effect = [(0, 'foo: wxyz:9876:stuv', ''), (1, '', 'Oops')] cert_changed(module, "openssl", "keytool", "/path/to/keystore.jks", "changeit", 'foo') module.fail_json.assert_called_with( - cmd="keytool -list -alias 'foo' -keystore '/path/to/keystore.jks' -storepass 'changeit' -v", + cmd=["keytool", "-list", "-alias", "foo", "-keystore", "/path/to/keystore.jks", "-storepass", "changeit", "-v"], msg='', err='Oops', rc=1