Repository: ansible-collections/community.general

Issue:
java_keystore: passwords are passed as command line arguments to Java's keytool
##### SUMMARY
As topic says. Should be changed to use stdin to pass the passwords in (same as for the `openssl` calls, see #276 for that).

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
java_keystore

##### ANSIBLE VERSION
```paste below
all
```



Discussion:
Files identified in the description:
* [`plugins/modules/system/java_keystore.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/system/java_keystore.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @Mogztter
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@felixfontein unfortunately `keytool` doesn't accept passwords from stdin the same way than `openssl`. Alternatives to `-deststorepass clearpassword` are:
- `-deststorepass:file /path/to/file`
- `-deststorepass:env ENVVAR`

that allows one to handle passwords more securely, i.e. without exposing them from command line.

I plan to fix this issue by using an environment variable. Is it acceptable as a definitive design ?
@quidame it's really unfortunate that `keytool` does not accept them from stdin, since that's the best way to pass secrets. Both writing them to files and using environment variables is not optimal, though both are a lot better than using command line arguments. So I agree that using env vars is currently the best choice we have.

Resolving patch:
diff --git a/plugins/modules/system/java_keystore.py b/plugins/modules/system/java_keystore.py
--- a/plugins/modules/system/java_keystore.py
+++ b/plugins/modules/system/java_keystore.py
@@ -146,8 +146,9 @@ 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 = [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)
+    stored_certificate_fingerprint_cmd = [keytool_bin, "-list", "-alias", alias, "-keystore", keystore_path, "-storepass:env", "STOREPASS", "-v"]
+    (rc, stored_certificate_fingerprint_out, stored_certificate_fingerprint_err) = run_commands(
+        module, stored_certificate_fingerprint_cmd, environ_update=dict(STOREPASS=keystore_password))
     if rc != 0:
         if "keytool error: java.lang.Exception: Alias <%s> does not exist" % alias not in stored_certificate_fingerprint_out:
             return module.fail_json(msg=stored_certificate_fingerprint_out,
@@ -168,8 +169,8 @@ def read_stored_certificate_fingerprint(module, keytool_bin, alias, keystore_pat
         return stored_certificate_match.group(1)
 
 
-def run_commands(module, cmd, data=None, check_rc=True):
-    return module.run_command(cmd, check_rc=check_rc, data=data)
+def run_commands(module, cmd, data=None, environ_update=None, check_rc=True):
+    return module.run_command(cmd, check_rc=check_rc, data=data, environ_update=environ_update)
 
 
 def create_path():
@@ -236,10 +237,12 @@ def create_jks(module, name, openssl_bin, keytool_bin, keystore_path, password,
                                    "-srckeystore", keystore_p12_path,
                                    "-srcstoretype", "pkcs12",
                                    "-alias", name,
-                                   "-deststorepass", password,
-                                   "-srcstorepass", password,
+                                   "-deststorepass:env", "STOREPASS",
+                                   "-srcstorepass:env", "STOREPASS",
                                    "-noprompt"]
-            (rc, import_keystore_out, import_keystore_err) = run_commands(module, import_keystore_cmd, data=None)
+
+            (rc, import_keystore_out, import_keystore_err) = run_commands(module, import_keystore_cmd, data=None,
+                                                                          environ_update=dict(STOREPASS=password))
             if rc == 0:
                 update_jks_perm(module, keystore_path)
                 return module.exit_json(changed=True,


Test patch:
diff --git a/tests/unit/plugins/modules/system/test_java_keystore.py b/tests/unit/plugins/modules/system/test_java_keystore.py
--- a/tests/unit/plugins/modules/system/test_java_keystore.py
+++ b/tests/unit/plugins/modules/system/test_java_keystore.py
@@ -71,14 +71,14 @@ def test_create_jks_success(self):
         with patch('os.remove', return_value=True):
             self.create_path.side_effect = ['/tmp/tmpgrzm2ah7']
             self.create_file.side_effect = ['/tmp/etacifitrec', '/tmp/yek_etavirp']
-            self.run_commands.side_effect = lambda module, cmd, data: (0, '', '')
+            self.run_commands.side_effect = [(0, '', ''), (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/tmpgrzm2ah7", "-srcstoretype", "pkcs12", "-alias", "test",
-                     "-deststorepass", "changeit", "-srcstorepass", "changeit", "-noprompt"],
+                     "-deststorepass:env", "STOREPASS", "-srcstorepass:env", "STOREPASS", "-noprompt"],
                 msg='',
                 rc=0,
                 stdout_lines=''
@@ -173,7 +173,7 @@ def test_create_jks_fail_import_key(self):
                 cmd=["keytool", "-importkeystore",
                      "-destkeystore", "/path/to/keystore.jks",
                      "-srckeystore", "/tmp/tmpgrzm2ah7", "-srcstoretype", "pkcs12", "-alias", "test",
-                     "-deststorepass", "changeit", "-srcstorepass", "changeit", "-noprompt"],
+                     "-deststorepass:env", "STOREPASS", "-srcstorepass:env", "STOREPASS", "-noprompt"],
                 msg='',
                 rc=1
             )
@@ -306,7 +306,7 @@ def test_cert_changed_fail_read_keystore(self):
             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:env", "STOREPASS", "-v"],
                 msg='',
                 err='Oops',
                 rc=1