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
This commit is contained in:
parent
9ad90de4bc
commit
2186b04934
7 changed files with 150 additions and 20 deletions
|
@ -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.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):
|
||||
|
|
|
@ -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)
|
||||
|
|
2
test/integration/targets/openssl_certificate/aliases
Normal file
2
test/integration/targets/openssl_certificate/aliases
Normal file
|
@ -0,0 +1,2 @@
|
|||
posix/ci/group1
|
||||
destructive
|
|
@ -0,0 +1,2 @@
|
|||
dependencies:
|
||||
- setup_openssl
|
74
test/integration/targets/openssl_certificate/tasks/main.yml
Normal file
74
test/integration/targets/openssl_certificate/tasks/main.yml
Normal file
|
@ -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', '>=')
|
|
@ -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
|
|
@ -1,3 +1,4 @@
|
|||
- block:
|
||||
- name: Generate privatekey
|
||||
openssl_privatekey:
|
||||
path: '{{ output_dir }}/privatekey.pem'
|
||||
|
@ -9,3 +10,5 @@
|
|||
commonName: 'www.ansible.com'
|
||||
|
||||
- import_tasks: ../tests/validate.yml
|
||||
|
||||
when: pyopenssl_version.stdout|version_compare('0.15', '>=')
|
||||
|
|
Loading…
Reference in a new issue