From 40ce0f995b8c968781c096cc49eda3e34662acb3 Mon Sep 17 00:00:00 2001 From: absynth76 <58172580+absynth76@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:31:58 +0200 Subject: [PATCH] Update java_cert module (#2008) * porting https://github.com/ansible/ansible/pull/56778 as requested in https://github.com/ansible-collections/community.general/issues/821 * fix imports, add back trust_cacerts option * try to fix import, ansible-lint fixes * modify import to use ansible.module_utils.six instead * cleanup indentation for tests/integration/targets/java_cert/tasks/main.yml file * remove external crypto dependency - switch to openssl, work on password obfuscation, using files compare to reduce logic * java_cert - remove latest run_command using password in arguments * fix sanity check * rename changelog fragment file - wrong extension * add openssl dependency * fix openssl_bin parameter missing on _get_digest_from_x509_file function call * remove useless close files, fix paragraph, fix changelog, clean import re * fix missing dots at end-of-line in changelogs fragments * fix reminder case * fix changelog * restore .gitignore * fix indentation on integration test files, delete useless json file * fix typo importing tasks in tests/integration/targets/java_cert/tasks/main.yml * Update changelogs/fragments/2008-update-java-cert-replace-cert-when-changed.yml Co-authored-by: Felix Fontein * Update tests/integration/targets/java_cert/tasks/state_change.yml Co-authored-by: Felix Fontein * Update plugins/modules/system/java_cert.py Co-authored-by: Felix Fontein * Update plugins/modules/system/java_cert.py Co-authored-by: Felix Fontein * Update plugins/modules/system/java_cert.py Co-authored-by: Felix Fontein * Update plugins/modules/system/java_cert.py Co-authored-by: Felix Fontein * Update plugins/modules/system/java_cert.py Co-authored-by: Felix Fontein * Update plugins/modules/system/java_cert.py Co-authored-by: Felix Fontein * Update plugins/modules/system/java_cert.py Co-authored-by: Felix Fontein * fix hardcoded executable keytool, use re.sub instead of import, add required cert_url or cert_alias parameter when absent, fix python script and cert_url test * fix pylint issue with setupSSLServeR.py Co-authored-by: Felix Fontein --- ...te-java-cert-replace-cert-when-changed.yml | 7 + plugins/modules/system/java_cert.py | 305 +++++++++++++----- .../targets/java_cert/defaults/main.yml | 12 +- .../targets/java_cert/files/setupSSLServer.py | 20 ++ .../targets/java_cert/meta/main.yml | 1 + .../targets/java_cert/tasks/main.yml | 93 ++++-- .../targets/java_cert/tasks/state_change.yml | 169 ++++++++++ 7 files changed, 496 insertions(+), 111 deletions(-) create mode 100644 changelogs/fragments/2008-update-java-cert-replace-cert-when-changed.yml create mode 100644 tests/integration/targets/java_cert/files/setupSSLServer.py create mode 100644 tests/integration/targets/java_cert/tasks/state_change.yml diff --git a/changelogs/fragments/2008-update-java-cert-replace-cert-when-changed.yml b/changelogs/fragments/2008-update-java-cert-replace-cert-when-changed.yml new file mode 100644 index 0000000000..8cfda91016 --- /dev/null +++ b/changelogs/fragments/2008-update-java-cert-replace-cert-when-changed.yml @@ -0,0 +1,7 @@ +minor_changes: + - "java_cert - change ``state: present`` to check certificates by hash, not just alias name (https://github.com/ansible/ansible/issues/43249)." +bugfixes: + - "java_cert - allow setting ``state: absent`` by providing just the ``cert_alias`` (https://github.com/ansible/ansible/issues/27982)." + - "java_cert - properly handle proxy arguments when the scheme is provided (https://github.com/ansible/ansible/issues/54481)." +security_fixes: + - "java_cert - remove password from ``run_command`` arguments (https://github.com/ansible-collections/community.general/pull/2008)." diff --git a/plugins/modules/system/java_cert.py b/plugins/modules/system/java_cert.py index 6594ed235b..ad56358034 100644 --- a/plugins/modules/system/java_cert.py +++ b/plugins/modules/system/java_cert.py @@ -10,6 +10,7 @@ __metaclass__ = type DOCUMENTATION = r''' --- module: java_cert + short_description: Uses keytool to import/remove key from java keystore (cacerts) description: - This is a wrapper module around keytool, which can be used to import/remove @@ -81,9 +82,12 @@ options: state: description: - Defines action which can be either certificate import or removal. + - When state is present, the certificate will always idempotently be inserted + into the keystore, even if there already exists a cert alias that is different. type: str choices: [ absent, present ] default: present +requirements: [openssl, keytool] author: - Adam Hamsik (@haad) ''' @@ -166,41 +170,143 @@ cmd: ''' import os +import tempfile +import random +import string import re + # import module snippets from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six.moves.urllib.parse import urlparse +from ansible.module_utils.six.moves.urllib.request import getproxies -def get_keystore_type(keystore_type): +def _get_keystore_type_keytool_parameters(keystore_type): ''' Check that custom keystore is presented in parameters ''' if keystore_type: - return " -storetype '%s'" % keystore_type - return '' + return ["-storetype", keystore_type] + return [] -def check_cert_present(module, executable, keystore_path, keystore_pass, alias, keystore_type): +def _check_cert_present(module, executable, keystore_path, keystore_pass, alias, keystore_type): ''' Check if certificate with alias is present in keystore located at keystore_path ''' - test_cmd = ("%s -noprompt -list -keystore '%s' -storepass '%s' " - "-alias '%s' %s") % (executable, keystore_path, keystore_pass, alias, get_keystore_type(keystore_type)) + test_cmd = [ + executable, + "-list", + "-keystore", + keystore_path, + "-alias", + alias, + "-rfc" + ] + test_cmd += _get_keystore_type_keytool_parameters(keystore_type) - check_rc, dummy, dummy = module.run_command(test_cmd) + (check_rc, stdout, dummy) = module.run_command(test_cmd, data=keystore_pass, check_rc=False) if check_rc == 0: - return True - return False + return (True, stdout) + return (False, '') -def import_cert_url(module, executable, url, port, keystore_path, keystore_pass, alias, keystore_type, trust_cacert): - ''' Import certificate from URL into keystore located at keystore_path ''' +def _get_certificate_from_url(module, executable, url, port, pem_certificate_output): + remote_cert_pem_chain = _download_cert_url(module, executable, url, port) + with open(pem_certificate_output, 'w') as f: + f.write(remote_cert_pem_chain) - https_proxy = os.getenv("https_proxy") + +def _get_first_certificate_from_x509_file(module, pem_certificate_file, pem_certificate_output, openssl_bin): + """ Read a X509 certificate chain file and output the first certificate in the list """ + extract_cmd = [ + openssl_bin, + "x509", + "-in", + pem_certificate_file, + "-out", + pem_certificate_output + ] + (extract_rc, dummy, extract_stderr) = module.run_command(extract_cmd, check_rc=False) + + if extract_rc != 0: + # trying der encoded file + extract_cmd += ["-inform", "der"] + (extract_rc, dummy, extract_stderr) = module.run_command(extract_cmd, check_rc=False) + + if extract_rc != 0: + # this time it's a real failure + module.fail_json(msg="Internal module failure, cannot extract certificate, error: %s" % extract_stderr, + rc=extract_rc, cmd=extract_cmd) + + return extract_rc + + +def _get_digest_from_x509_file(module, pem_certificate_file, openssl_bin): + """ Read a X509 certificate file and output sha256 digest using openssl """ + # cleanup file before to compare + (dummy, tmp_certificate) = tempfile.mkstemp() + module.add_cleanup_file(tmp_certificate) + _get_first_certificate_from_x509_file(module, pem_certificate_file, tmp_certificate, openssl_bin) + dgst_cmd = [ + openssl_bin, + "dgst", + "-r", + "-sha256", + tmp_certificate + ] + (dgst_rc, dgst_stdout, dgst_stderr) = module.run_command(dgst_cmd, check_rc=False) + + if dgst_rc != 0: + module.fail_json(msg="Internal module failure, cannot compute digest for certificate, error: %s" % dgst_stderr, + rc=dgst_rc, cmd=dgst_cmd) + + return dgst_stdout.split(' ')[0] + + +def _export_public_cert_from_pkcs12(module, executable, pkcs_file, alias, password, dest): + """ Runs keytools to extract the public cert from a PKCS12 archive and write it to a file. """ + export_cmd = [ + executable, + "-list", + "-keystore", + pkcs_file, + "-alias", + alias, + "-storetype", + "pkcs12", + "-rfc" + ] + (export_rc, export_stdout, export_err) = module.run_command(export_cmd, data=password, check_rc=False) + + if export_rc != 0: + module.fail_json(msg="Internal module failure, cannot extract public certificate from pkcs12, error: %s" % export_err, + rc=export_rc) + + with open(dest, 'w') as f: + f.write(export_stdout) + + +def get_proxy_settings(scheme='https'): + """ Returns a tuple containing (proxy_host, proxy_port). (False, False) if no proxy is found """ + proxy_url = getproxies().get(scheme, '') + if not proxy_url: + return (False, False) + else: + parsed_url = urlparse(proxy_url) + if parsed_url.scheme: + (proxy_host, proxy_port) = parsed_url.netloc.split(':') + else: + (proxy_host, proxy_port) = parsed_url.path.split(':') + return (proxy_host, proxy_port) + + +def build_proxy_options(): + """ Returns list of valid proxy options for keytool """ + (proxy_host, proxy_port) = get_proxy_settings() no_proxy = os.getenv("no_proxy") - proxy_opts = '' - if https_proxy is not None: - (proxy_host, proxy_port) = https_proxy.split(':') - proxy_opts = "-J-Dhttps.proxyHost=%s -J-Dhttps.proxyPort=%s" % (proxy_host, proxy_port) + proxy_opts = [] + if proxy_host: + proxy_opts.extend(["-J-Dhttps.proxyHost=%s" % proxy_host, "-J-Dhttps.proxyPort=%s" % proxy_port]) if no_proxy is not None: # For Java's nonProxyHosts property, items are separated by '|', @@ -210,46 +316,48 @@ def import_cert_url(module, executable, url, port, keystore_path, keystore_pass, # The property name is http.nonProxyHosts, there is no # separate setting for HTTPS. - proxy_opts += " -J-Dhttp.nonProxyHosts='%s'" % non_proxy_hosts + proxy_opts.extend(["-J-Dhttp.nonProxyHosts=%s" % non_proxy_hosts]) + return proxy_opts - fetch_cmd = "%s -printcert -rfc -sslserver %s %s:%d" % (executable, proxy_opts, url, port) - import_cmd = ("%s -importcert -noprompt -keystore '%s' " - "-storepass '%s' -alias '%s' %s") % (executable, keystore_path, - keystore_pass, alias, - get_keystore_type(keystore_type)) - if trust_cacert: - import_cmd = import_cmd + " -trustcacerts" + +def _download_cert_url(module, executable, url, port): + """ Fetches the certificate from the remote URL using `keytool -printcert...` + The PEM formatted string is returned """ + proxy_opts = build_proxy_options() + fetch_cmd = [executable, "-printcert", "-rfc", "-sslserver"] + proxy_opts + ["%s:%d" % (url, port)] # Fetch SSL certificate from remote host. - dummy, fetch_out, dummy = module.run_command(fetch_cmd, check_rc=True) + (fetch_rc, fetch_out, fetch_err) = module.run_command(fetch_cmd, check_rc=False) - # Use remote certificate from remote host and import it to a java keystore - (import_rc, import_out, import_err) = module.run_command(import_cmd, - data=fetch_out, - check_rc=False) - diff = {'before': '\n', 'after': '%s\n' % alias} - if import_rc == 0: - module.exit_json(changed=True, msg=import_out, - rc=import_rc, cmd=import_cmd, stdout=import_out, - diff=diff) - else: - module.fail_json(msg=import_out, rc=import_rc, cmd=import_cmd, - error=import_err) + if fetch_rc != 0: + module.fail_json(msg="Internal module failure, cannot download certificate, error: %s" % fetch_err, + rc=fetch_rc, cmd=fetch_cmd) + + return fetch_out def import_cert_path(module, executable, path, keystore_path, keystore_pass, alias, keystore_type, trust_cacert): ''' Import certificate from path into keystore located on keystore_path as alias ''' - import_cmd = ("%s -importcert -noprompt -keystore '%s' " - "-storepass '%s' -file '%s' -alias '%s' %s") % (executable, keystore_path, - keystore_pass, path, alias, - get_keystore_type(keystore_type)) + import_cmd = [ + executable, + "-importcert", + "-noprompt", + "-keystore", + keystore_path, + "-file", + path, + "-alias", + alias + ] + import_cmd += _get_keystore_type_keytool_parameters(keystore_type) if trust_cacert: - import_cmd = import_cmd + " -trustcacerts" + import_cmd.extend(["-trustcacerts"]) # Use local certificate from local path and import it to a java keystore (import_rc, import_out, import_err) = module.run_command(import_cmd, + data="%s\n%s" % (keystore_pass, keystore_pass), check_rc=False) diff = {'before': '\n', 'after': '%s\n' % alias} @@ -261,41 +369,29 @@ def import_cert_path(module, executable, path, keystore_path, keystore_pass, ali module.fail_json(msg=import_out, rc=import_rc, cmd=import_cmd) -def import_pkcs12_path(module, executable, path, keystore_path, keystore_pass, pkcs12_pass, pkcs12_alias, alias, keystore_type): - ''' Import pkcs12 from path into keystore located on - keystore_path as alias ''' - import_cmd = ("%s -importkeystore -noprompt -destkeystore '%s' -srcstoretype PKCS12 " - "-deststorepass '%s' -destkeypass '%s' -srckeystore '%s' -srcstorepass '%s' " - "-srcalias '%s' -destalias '%s' %s") % (executable, keystore_path, keystore_pass, - keystore_pass, path, pkcs12_pass, pkcs12_alias, - alias, get_keystore_type(keystore_type)) - - # Use local certificate from local path and import it to a java keystore - (import_rc, import_out, import_err) = module.run_command(import_cmd, - check_rc=False) - - diff = {'before': '\n', 'after': '%s\n' % alias} - if import_rc == 0: - module.exit_json(changed=True, msg=import_out, - rc=import_rc, cmd=import_cmd, stdout=import_out, - error=import_err, diff=diff) - else: - module.fail_json(msg=import_out, rc=import_rc, cmd=import_cmd) - - -def delete_cert(module, executable, keystore_path, keystore_pass, alias, keystore_type): +def delete_cert(module, executable, keystore_path, keystore_pass, alias, keystore_type, exit_after=True): ''' Delete certificate identified with alias from keystore on keystore_path ''' - del_cmd = ("%s -delete -keystore '%s' -storepass '%s' " - "-alias '%s' %s") % (executable, keystore_path, keystore_pass, alias, get_keystore_type(keystore_type)) + del_cmd = [ + executable, + "-delete", + "-noprompt", + "-keystore", + keystore_path, + "-alias", + alias + ] + + del_cmd += _get_keystore_type_keytool_parameters(keystore_type) # Delete SSL certificate from keystore - (del_rc, del_out, del_err) = module.run_command(del_cmd, check_rc=True) + (del_rc, del_out, del_err) = module.run_command(del_cmd, data=keystore_pass, check_rc=True) - diff = {'before': '%s\n' % alias, 'after': None} + if exit_after: + diff = {'before': '%s\n' % alias, 'after': None} - module.exit_json(changed=True, msg=del_out, - rc=del_rc, cmd=del_cmd, stdout=del_out, - error=del_err, diff=diff) + module.exit_json(changed=True, msg=del_out, + rc=del_rc, cmd=del_cmd, stdout=del_out, + error=del_err, diff=diff) def test_keytool(module, executable): @@ -333,7 +429,8 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, - required_one_of=[['cert_path', 'cert_url', 'pkcs12_path']], + required_if=[['state', 'present', ('cert_path', 'cert_url', 'pkcs12_path'), True], + ['state', 'absent', ('cert_url', 'cert_alias'), True]], required_together=[['keystore_path', 'keystore_pass']], mutually_exclusive=[ ['cert_url', 'cert_path', 'pkcs12_path'] @@ -359,6 +456,9 @@ def main(): executable = module.params.get('executable') state = module.params.get('state') + # openssl dependency resolution + openssl_bin = module.get_bin_path('openssl', True) + if path and not cert_alias: module.fail_json(changed=False, msg="Using local path import from %s requires alias argument." @@ -369,31 +469,62 @@ def main(): if not keystore_create: test_keystore(module, keystore_path) - cert_present = check_cert_present(module, executable, keystore_path, - keystore_pass, cert_alias, keystore_type) + alias_exists, alias_exists_output = _check_cert_present( + module, executable, keystore_path, keystore_pass, cert_alias, keystore_type) - if state == 'absent' and cert_present: + (dummy, new_certificate) = tempfile.mkstemp() + (dummy, old_certificate) = tempfile.mkstemp() + module.add_cleanup_file(new_certificate) + module.add_cleanup_file(old_certificate) + + if state == 'absent' and alias_exists: if module.check_mode: module.exit_json(changed=True) + # delete and exit delete_cert(module, executable, keystore_path, keystore_pass, cert_alias, keystore_type) - elif state == 'present' and not cert_present: - if module.check_mode: - module.exit_json(changed=True) + # dump certificate to enroll in the keystore on disk and compute digest + if state == 'present': + # The alias exists in the keystore so we must now compare the SHA256 hash of the + # public certificate already in the keystore, and the certificate we are wanting to add + if alias_exists: + with open(old_certificate, "w") as f: + f.write(alias_exists_output) + keystore_cert_digest = _get_digest_from_x509_file(module, old_certificate, openssl_bin) + + else: + keystore_cert_digest = '' if pkcs12_path: - import_pkcs12_path(module, executable, pkcs12_path, keystore_path, - keystore_pass, pkcs12_pass, pkcs12_alias, cert_alias, keystore_type) + # Extracting certificate with openssl + _export_public_cert_from_pkcs12(module, executable, pkcs12_path, cert_alias, pkcs12_pass, new_certificate) - if path: - import_cert_path(module, executable, path, keystore_path, + elif path: + # Extracting the X509 digest is a bit easier. Keytool will print the PEM + # certificate to stdout so we don't need to do any transformations. + new_certificate = path + + elif url: + # Getting the X509 digest from a URL is the same as from a path, we just have + # to download the cert first + _get_certificate_from_url(module, executable, url, port, new_certificate) + + new_cert_digest = _get_digest_from_x509_file(module, new_certificate, openssl_bin) + + if keystore_cert_digest != new_cert_digest: + + if module.check_mode: + module.exit_json(changed=True) + + if alias_exists: + # The certificate in the keystore does not match with the one we want to be present + # The existing certificate must first be deleted before we insert the correct one + delete_cert(module, executable, keystore_path, keystore_pass, cert_alias, keystore_type, exit_after=False) + + import_cert_path(module, executable, new_certificate, keystore_path, keystore_pass, cert_alias, keystore_type, trust_cacert) - if url: - import_cert_url(module, executable, url, port, keystore_path, - keystore_pass, cert_alias, keystore_type, trust_cacert) - module.exit_json(changed=False) diff --git a/tests/integration/targets/java_cert/defaults/main.yml b/tests/integration/targets/java_cert/defaults/main.yml index 22723ff177..6416f306af 100644 --- a/tests/integration/targets/java_cert/defaults/main.yml +++ b/tests/integration/targets/java_cert/defaults/main.yml @@ -1,3 +1,13 @@ --- test_pkcs12_path: testpkcs.p12 -test_keystore_path: keystore.jks \ No newline at end of file +test_keystore_path: keystore.jks +test_keystore2_path: "{{ output_dir }}/keystore2.jks" +test_keystore2_password: changeit +test_cert_path: "{{ output_dir }}/cert.pem" +test_key_path: "{{ output_dir }}/key.pem" +test_cert2_path: "{{ output_dir }}/cert2.pem" +test_key2_path: "{{ output_dir }}/key2.pem" +test_pkcs_path: "{{ output_dir }}/cert.p12" +test_pkcs2_path: "{{ output_dir }}/cert2.p12" +test_ssl: setupSSLServer.py +test_ssl_port: 21500 \ No newline at end of file diff --git a/tests/integration/targets/java_cert/files/setupSSLServer.py b/tests/integration/targets/java_cert/files/setupSSLServer.py new file mode 100644 index 0000000000..9227eefd81 --- /dev/null +++ b/tests/integration/targets/java_cert/files/setupSSLServer.py @@ -0,0 +1,20 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import ssl +import os +import sys + +root_dir = sys.argv[1] +port = int(sys.argv[2]) + +try: + from BaseHTTPServer import HTTPServer + from SimpleHTTPServer import SimpleHTTPRequestHandler +except ModuleNotFoundError: + from http.server import HTTPServer, SimpleHTTPRequestHandler + +httpd = HTTPServer(('localhost', port), SimpleHTTPRequestHandler) +httpd.socket = ssl.wrap_socket(httpd.socket, server_side=True, + certfile=os.path.join(root_dir, 'cert.pem'), + keyfile=os.path.join(root_dir, 'key.pem')) +httpd.handle_request() diff --git a/tests/integration/targets/java_cert/meta/main.yml b/tests/integration/targets/java_cert/meta/main.yml index 1d18287ada..9bc23ac67f 100644 --- a/tests/integration/targets/java_cert/meta/main.yml +++ b/tests/integration/targets/java_cert/meta/main.yml @@ -1,2 +1,3 @@ dependencies: - setup_java_keytool + - setup_openssl diff --git a/tests/integration/targets/java_cert/tasks/main.yml b/tests/integration/targets/java_cert/tasks/main.yml index e701836e5d..8172db5c15 100644 --- a/tests/integration/targets/java_cert/tasks/main.yml +++ b/tests/integration/targets/java_cert/tasks/main.yml @@ -11,15 +11,16 @@ - name: import pkcs12 java_cert: - pkcs12_path: "{{output_dir}}/{{ test_pkcs12_path }}" - pkcs12_password: changeit - pkcs12_alias: default - cert_alias: default - keystore_path: "{{output_dir}}/{{ test_keystore_path }}" - keystore_pass: changeme_keystore - keystore_create: yes - state: present + pkcs12_path: "{{output_dir}}/{{ test_pkcs12_path }}" + pkcs12_password: changeit + pkcs12_alias: default + cert_alias: default + keystore_path: "{{output_dir}}/{{ test_keystore_path }}" + keystore_pass: changeme_keystore + keystore_create: yes + state: present register: result_success + - name: verify success assert: that: @@ -27,14 +28,14 @@ - name: import pkcs12 with wrong password java_cert: - pkcs12_path: "{{output_dir}}/{{ test_pkcs12_path }}" - pkcs12_password: wrong_pass - pkcs12_alias: default - cert_alias: default_new - keystore_path: "{{output_dir}}/{{ test_keystore_path }}" - keystore_pass: changeme_keystore - keystore_create: yes - state: present + pkcs12_path: "{{output_dir}}/{{ test_pkcs12_path }}" + pkcs12_password: wrong_pass + pkcs12_alias: default + cert_alias: default_new + keystore_path: "{{output_dir}}/{{ test_keystore_path }}" + keystore_pass: changeme_keystore + keystore_create: yes + state: present ignore_errors: true register: result_wrong_pass @@ -45,16 +46,62 @@ - name: test fail on mutually exclusive params java_cert: - cert_path: ca.crt - pkcs12_path: "{{output_dir}}/{{ test_pkcs12_path }}" - cert_alias: default - keystore_path: "{{output_dir}}/{{ test_keystore_path }}" - keystore_pass: changeme_keystore - keystore_create: yes - state: present + cert_path: ca.crt + pkcs12_path: "{{output_dir}}/{{ test_pkcs12_path }}" + cert_alias: default + keystore_path: "{{output_dir}}/{{ test_keystore_path }}" + keystore_pass: changeme_keystore + keystore_create: yes + state: present ignore_errors: true register: result_excl_params + - name: verify failed exclusive params assert: that: - result_excl_params is failed + + - name: test fail on missing required params + java_cert: + keystore_path: "{{output_dir}}/{{ test_keystore_path }}" + keystore_pass: changeme_keystore + state: absent + ignore_errors: true + register: result_missing_required_param + + - name: verify failed missing required params + assert: + that: + - result_missing_required_param is failed + + - name: delete object based on cert_alias parameter + java_cert: + keystore_path: "{{output_dir}}/{{ test_keystore_path }}" + keystore_pass: changeme_keystore + cert_alias: default + state: absent + ignore_errors: true + register: result_alias_deleted + + - name: verify object successfully deleted + assert: + that: + - result_alias_deleted is successful + + - name: include extended test suite + import_tasks: state_change.yml + + - name: cleanup environment + file: + path: "{{ item }}" + state: absent + loop: + - "{{ output_dir }}/{{ test_pkcs12_path }}" + - "{{ output_dir }}/{{ test_keystore_path }}" + - "{{ test_keystore2_path }}" + - "{{ test_cert_path }}" + - "{{ test_key_path }}" + - "{{ test_cert2_path }}" + - "{{ test_key2_path }}" + - "{{ test_pkcs_path }}" + - "{{ test_pkcs2_path }}" \ No newline at end of file diff --git a/tests/integration/targets/java_cert/tasks/state_change.yml b/tests/integration/targets/java_cert/tasks/state_change.yml new file mode 100644 index 0000000000..3c37fc6727 --- /dev/null +++ b/tests/integration/targets/java_cert/tasks/state_change.yml @@ -0,0 +1,169 @@ +--- +- name: Generate the self signed cert used as a place holder to create the java keystore + command: openssl req -x509 -newkey rsa:4096 -keyout {{ test_key_path }} -out {{ test_cert_path }} -days 365 -nodes -subj '/CN=localhost' + args: + creates: "{{ test_key_path }}" + +- name: Create the test keystore + java_keystore: + name: placeholder + dest: "{{ test_keystore2_path }}" + password: "{{ test_keystore2_password }}" + private_key: "{{ lookup('file', '{{ test_key_path }}') }}" + certificate: "{{ lookup('file', '{{ test_cert_path }}') }}" + +- name: Generate the self signed cert we will use for testing + command: openssl req -x509 -newkey rsa:4096 -keyout '{{ test_key2_path }}' -out '{{ test_cert2_path }}' -days 365 -nodes -subj '/CN=localhost' + args: + creates: "{{ test_key2_path }}" + +- name: | + Import the newly created certificate. This is our main test. + If the java_cert has been updated properly, then this task will report changed each time + since the module will be comparing the hash of the certificate instead of validating that the alias + simply exists + java_cert: + cert_alias: test_cert + cert_path: "{{ test_cert2_path }}" + keystore_path: "{{ test_keystore2_path }}" + keystore_pass: "{{ test_keystore2_password }}" + state: present + register: result_x509_changed + +- name: Verify the x509 status has changed + assert: + that: + - result_x509_changed is changed + +- name: | + We also want to make sure that the status doesnt change if we import the same cert + java_cert: + cert_alias: test_cert + cert_path: "{{ test_cert2_path }}" + keystore_path: "{{ test_keystore2_path }}" + keystore_pass: "{{ test_keystore2_password }}" + state: present + register: result_x509_succeeded + +- name: Verify the x509 status is ok + assert: + that: + - result_x509_succeeded is succeeded + +- name: Create the pkcs12 archive from the test x509 cert + command: > + openssl pkcs12 + -in {{ test_cert_path }} + -inkey {{ test_key_path }} + -export + -name test_pkcs12_cert + -out {{ test_pkcs_path }} + -passout pass:"{{ test_keystore2_password }}" + +- name: Create the pkcs12 archive from the certificate we will be trying to add to the keystore + command: > + openssl pkcs12 + -in {{ test_cert2_path }} + -inkey {{ test_key2_path }} + -export + -name test_pkcs12_cert + -out {{ test_pkcs2_path }} + -passout pass:"{{ test_keystore2_password }}" + +- name: > + Ensure the original pkcs12 cert is in the keystore + java_cert: + cert_alias: test_pkcs12_cert + pkcs12_alias: test_pkcs12_cert + pkcs12_path: "{{ test_pkcs_path }}" + pkcs12_password: "{{ test_keystore2_password }}" + keystore_path: "{{ test_keystore2_path }}" + keystore_pass: "{{ test_keystore2_password }}" + state: present + +- name: | + Perform the same test, but we will now be testing the pkcs12 functionality + If we add a different pkcs12 cert with the same alias, we should have a chnaged result, NOT the same + java_cert: + cert_alias: test_pkcs12_cert + pkcs12_alias: test_pkcs12_cert + pkcs12_path: "{{ test_pkcs2_path }}" + pkcs12_password: "{{ test_keystore2_password }}" + keystore_path: "{{ test_keystore2_path }}" + keystore_pass: "{{ test_keystore2_password }}" + state: present + register: result_pkcs12_changed + +- name: Verify the pkcs12 status has changed + assert: + that: + - result_pkcs12_changed is changed + +- name: | + We are requesting the same cert now, so the status should show OK + java_cert: + cert_alias: test_pkcs12_cert + pkcs12_alias: test_pkcs12_cert + pkcs12_path: "{{ test_pkcs2_path }}" + pkcs12_password: "{{ test_keystore2_password }}" + keystore_path: "{{ test_keystore2_path }}" + keystore_pass: "{{ test_keystore2_password }}" + register: result_pkcs12_succeeded + +- name: Verify the pkcs12 status is ok + assert: + that: + - result_pkcs12_succeeded is succeeded + +- name: Copy the ssl server script + copy: + src: "setupSSLServer.py" + dest: "{{ output_dir }}" + +- name: Create an SSL server that we will use for testing URL imports + command: python {{ output_dir }}/setupSSLServer.py {{ output_dir }} {{ test_ssl_port }} + async: 10 + poll: 0 + +- name: | + Download the original cert.pem from our temporary server. The current cert should contain + cert2.pem. Importing this cert should return a status of changed + java_cert: + cert_alias: test_cert_localhost + cert_url: localhost + cert_port: "{{ test_ssl_port }}" + keystore_path: "{{ test_keystore2_path }}" + keystore_pass: "{{ test_keystore2_password }}" + state: present + register: result_url_changed + +- name: Verify that the url status is changed + assert: + that: + - result_url_changed is changed + +- name: Ensure we can remove the x509 cert + java_cert: + cert_alias: test_cert + keystore_path: "{{ test_keystore2_path }}" + keystore_pass: "{{ test_keystore2_password }}" + state: absent + register: result_x509_absent + +- name: Verify the x509 cert is absent + assert: + that: + - result_x509_absent is changed + +- name: Ensure we can remove the pkcs12 archive + java_cert: + cert_alias: test_pkcs12_cert + keystore_path: "{{ test_keystore2_path }}" + keystore_pass: "{{ test_keystore2_password }}" + state: absent + register: result_pkcs12_absent + +- name: Verify the pkcs12 archive is absent + assert: + that: + - result_pkcs12_absent is changed