From 2186b04934dd327f9a300d0bda009da949c8d562 Mon Sep 17 00:00:00 2001 From: MarkusTeufelberger Date: Wed, 13 Sep 2017 23:39:32 +0200 Subject: [PATCH] Add simple integration test for openssl_certificate (#29038) * openssl_certificate: Fix parameter assertion in Python3 Parameter assertion in Python3 is broken. pyOpenSSL get_X() functions returns b'' type string and tries to compare it with '' string, leading to failure. The error mentionned above has been fixed by sanitizing the inputs from a user to the assert only backend. Also, this error was hidden by the fact that the improper check method was called in the generate() functions. * Add simple integration test for openssl_certificate * remove subject == issuer assertion * run integration tests only on supported hosts * change min supported version to 0.15.x * Add test for more CSR fields * also convert dict members to bytes * fix version_compare * openssl_{csr, certificate}: Fail if pyOpenSSL <= 0.15 Previous 0.13 pyOpenSSL was a C-binding, and required the parameter passed to add_extention to be in ASN.1. This has changed with the move to 0.14 and it is now all pythong and string based. Previous the 0.15 release, the `get_extensions()` method didn't exist, since the modules rely heavily on it we ensure pyOpenSSL version is at last 0.15.0. * check pyopenssl version in openssl_csr integration test --- .../modules/crypto/openssl_certificate.py | 39 +++++++--- lib/ansible/modules/crypto/openssl_csr.py | 7 +- .../targets/openssl_certificate/aliases | 2 + .../targets/openssl_certificate/meta/main.yml | 2 + .../openssl_certificate/tasks/main.yml | 74 +++++++++++++++++++ .../openssl_certificate/tests/validate.yml | 25 +++++++ .../targets/openssl_csr/tasks/main.yml | 21 +++--- 7 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 test/integration/targets/openssl_certificate/aliases create mode 100644 test/integration/targets/openssl_certificate/meta/main.yml create mode 100644 test/integration/targets/openssl_certificate/tasks/main.yml create mode 100644 test/integration/targets/openssl_certificate/tests/validate.yml diff --git a/lib/ansible/modules/crypto/openssl_certificate.py b/lib/ansible/modules/crypto/openssl_certificate.py index b2d7ca4b05c..a9a2dc4e6d2 100644 --- a/lib/ansible/modules/crypto/openssl_certificate.py +++ b/lib/ansible/modules/crypto/openssl_certificate.py @@ -32,8 +32,8 @@ description: want to receive a certificate with these properties is a CSR (Certificate Signing Request). It uses the pyOpenSSL python library to interact with OpenSSL." requirements: - - python-pyOpenSSL >= 0.15 (if using C(selfsigned) provider) - - acme-tiny (if using the acme provider) + - python-pyOpenSSL >= 0.15 (if using C(selfsigned) or C(assertonly) provider) + - acme-tiny (if using the C(acme) provider) options: state: default: "present" @@ -301,7 +301,7 @@ import os from ansible.module_utils import crypto as crypto_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native +from ansible.module_utils._text import to_native, to_bytes try: import OpenSSL @@ -408,12 +408,7 @@ class SelfSignedCertificate(Certificate): cert.set_subject(self.csr.get_subject()) cert.set_version(self.csr.get_version() - 1) cert.set_pubkey(self.csr.get_pubkey()) - - try: - # NOTE: This is only available starting from pyOpenSSL >= 0.15 - cert.add_extensions(self.csr.get_extensions()) - except NameError as exc: - raise CertificateError('You need to have PyOpenSSL>= 0.15 to generate public keys') + cert.add_extensions(self.csr.get_extensions()) cert.sign(self.privatekey, self.digest) self.certificate = cert @@ -467,6 +462,24 @@ class AssertOnlyCertificate(Certificate): self.invalid_at = module.params['invalid_at'] self.valid_in = module.params['valid_in'] self.message = [] + self._sanitize_inputs() + + def _sanitize_inputs(self): + """Ensure inputs are properly sanitized before comparison.""" + + for param in ['signature_algorithms', 'keyUsage', 'extendedKeyUsage', + 'subjectAltName', 'subject', 'issuer', 'notBefore', + 'notAfter', 'valid_at', 'invalid_at']: + + attr = getattr(self, param) + if isinstance(attr, list): + setattr(self, param, [to_bytes(item) for item in attr]) + elif isinstance(attr, tuple): + setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items())) + elif isinstance(attr, dict): + setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items())) + elif isinstance(attr, str): + setattr(self, param, to_bytes(attr)) def assertonly(self): @@ -606,7 +619,8 @@ class AssertOnlyCertificate(Certificate): self.assertonly() - if self.privatekey_path and not self.check(self.module, perms_required=False): + if self.privatekey_path and \ + not super(AssertOnlyCertificate, self).check(module, perms_required=False): self.message.append( 'Certificate %s and private key %s does not match' % (self.path, self.privatekey_path) ) @@ -740,6 +754,11 @@ def main(): if not pyopenssl_found: module.fail_json(msg='The python pyOpenSSL library is required') + if module.params['provider'] in ['selfsigned', 'assertonly']: + try: + getattr(crypto.X509Req, 'get_extensions') + except AttributeError: + module.fail_json(msg='You need to have PyOpenSSL>=0.15') base_dir = os.path.dirname(module.params['path']) if not os.path.isdir(base_dir): diff --git a/lib/ansible/modules/crypto/openssl_csr.py b/lib/ansible/modules/crypto/openssl_csr.py index ca7d695f914..0b208627c2d 100644 --- a/lib/ansible/modules/crypto/openssl_csr.py +++ b/lib/ansible/modules/crypto/openssl_csr.py @@ -26,7 +26,7 @@ description: Note: At least one of common_name or subject_alt_name must be specified. This module uses file common arguments to specify generated file permissions." requirements: - - "python-pyOpenSSL" + - "python-pyOpenSSL >= 0.15" options: state: required: false @@ -430,6 +430,11 @@ def main(): if not pyopenssl_found: module.fail_json(msg='the python pyOpenSSL module is required') + try: + getattr(crypto.X509Req, 'get_extensions') + except AttributeError: + module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs') + base_dir = os.path.dirname(module.params['path']) if not os.path.isdir(base_dir): module.fail_json(name=base_dir, msg='The directory %s does not exist or the file is not a directory' % base_dir) diff --git a/test/integration/targets/openssl_certificate/aliases b/test/integration/targets/openssl_certificate/aliases new file mode 100644 index 00000000000..7978f4a6ce7 --- /dev/null +++ b/test/integration/targets/openssl_certificate/aliases @@ -0,0 +1,2 @@ +posix/ci/group1 +destructive diff --git a/test/integration/targets/openssl_certificate/meta/main.yml b/test/integration/targets/openssl_certificate/meta/main.yml new file mode 100644 index 00000000000..800aff64284 --- /dev/null +++ b/test/integration/targets/openssl_certificate/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_openssl diff --git a/test/integration/targets/openssl_certificate/tasks/main.yml b/test/integration/targets/openssl_certificate/tasks/main.yml new file mode 100644 index 00000000000..e47c319018d --- /dev/null +++ b/test/integration/targets/openssl_certificate/tasks/main.yml @@ -0,0 +1,74 @@ +- block: + - name: Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + + - name: Generate CSR + openssl_csr: + path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + commonName: 'www.ansible.com' + + - name: Generate selfsigned certificate + openssl_certificate: + path: '{{ output_dir }}/cert.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + + - name: Check selfsigned certificate + openssl_certificate: + path: '{{ output_dir }}/cert.pem' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: assertonly + has_expired: False + version: 3 + signature_algorithms: + - sha256WithRSAEncryption + - sha256WithECDSAEncryption + + - name: Generate privatekey2 + openssl_privatekey: + path: '{{ output_dir }}/privatekey2.pem' + + - name: Generate CSR2 + openssl_csr: + C: US + ST: California + L: Los Angeles + O: ACME Inc. + OU: Roadrunner pest control + path: '{{ output_dir }}/csr2.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + CN: 'www.example.com' + + - name: Generate selfsigned certificate2 + openssl_certificate: + path: '{{ output_dir }}/cert2.pem' + csr_path: '{{ output_dir }}/csr2.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + provider: selfsigned + selfsigned_digest: sha256 + + - name: Check selfsigned certificate2 + openssl_certificate: + path: '{{ output_dir }}/cert2.pem' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + provider: assertonly + has_expired: False + version: 3 + signature_algorithms: + - sha256WithRSAEncryption + - sha256WithECDSAEncryption + subject: + CN: www.example.com + C: US + ST: California + L: Los Angeles + O: ACME Inc. + OU: Roadrunner pest control + + - import_tasks: ../tests/validate.yml + + when: pyopenssl_version.stdout|version_compare('0.15', '>=') diff --git a/test/integration/targets/openssl_certificate/tests/validate.yml b/test/integration/targets/openssl_certificate/tests/validate.yml new file mode 100644 index 00000000000..7b504096fb0 --- /dev/null +++ b/test/integration/targets/openssl_certificate/tests/validate.yml @@ -0,0 +1,25 @@ +- name: Validate certificate (test - privatekey modulus) + shell: 'openssl rsa -noout -modulus -in {{ output_dir }}/privatekey.pem | openssl md5' + register: privatekey_modulus + +- name: Validate certificate (test - certificate modulus) + shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/cert.pem | openssl md5' + register: cert_modulus + +- name: Validate certificate (assert) + assert: + that: + - cert_modulus.stdout == privatekey_modulus.stdout + +- name: Validate certificate2 (test - privatekey modulus) + shell: 'openssl rsa -noout -modulus -in {{ output_dir }}/privatekey2.pem | openssl md5' + register: privatekey2_modulus + +- name: Validate certificate2 (test - certificate modulus) + shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/cert2.pem | openssl md5' + register: cert2_modulus + +- name: Validate certificate2 (assert) + assert: + that: + - cert2_modulus.stdout == privatekey2_modulus.stdout diff --git a/test/integration/targets/openssl_csr/tasks/main.yml b/test/integration/targets/openssl_csr/tasks/main.yml index 48f3a489efa..e06eadf3934 100644 --- a/test/integration/targets/openssl_csr/tasks/main.yml +++ b/test/integration/targets/openssl_csr/tasks/main.yml @@ -1,11 +1,14 @@ -- name: Generate privatekey - openssl_privatekey: - path: '{{ output_dir }}/privatekey.pem' +- block: + - name: Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' -- name: Generate CSR - openssl_csr: - path: '{{ output_dir }}/csr.csr' - privatekey_path: '{{ output_dir }}/privatekey.pem' - commonName: 'www.ansible.com' + - name: Generate CSR + openssl_csr: + path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + commonName: 'www.ansible.com' -- import_tasks: ../tests/validate.yml + - import_tasks: ../tests/validate.yml + + when: pyopenssl_version.stdout|version_compare('0.15', '>=')