Allow to run x509_certificate selfsigned provider without providing a CSR (#129)

* Allow to run x509_certificate selfsigned provider without providing a CSR.

* Add missing prefixes (unrelated).
pull/131/head
Felix Fontein 2020-10-19 18:09:40 +02:00 committed by GitHub
parent b32adcce78
commit fd7871ae7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 38 deletions

View File

@ -0,0 +1,2 @@
minor_changes:
- "x509_certificate - for the ``selfsigned`` provider, a CSR is not required anymore. If no CSR is provided, the module behaves as if a minimal CSR which only contains the public key has been provided (https://github.com/ansible-collections/community.crypto/issues/32, https://github.com/ansible-collections/community.crypto/pull/129)."

View File

@ -87,13 +87,13 @@ options:
csr_path:
description:
- Path to the Certificate Signing Request (CSR) used to generate this certificate.
- This is not required in C(assertonly) mode.
- This is not required in C(assertonly) or C(selfsigned) mode.
- This is mutually exclusive with I(csr_content).
type: path
csr_content:
description:
- Content of the Certificate Signing Request (CSR) used to generate this certificate.
- This is not required in C(assertonly) mode.
- This is not required in C(assertonly) or C(selfsigned) mode.
- This is mutually exclusive with I(csr_path).
type: str
version_added: '1.0.0'
@ -1144,7 +1144,7 @@ class SelfSignedCertificateCryptography(Certificate):
self.version = module.params['selfsigned_version']
self.serial_number = x509.random_serial_number()
if self.csr_content is None and not os.path.exists(self.csr_path):
if self.csr_path is not None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file {0} does not exist'.format(self.csr_path)
)
@ -1153,11 +1153,6 @@ class SelfSignedCertificateCryptography(Certificate):
'The private key file {0} does not exist'.format(self.privatekey_path)
)
self.csr = load_certificate_request(
path=self.csr_path,
content=self.csr_content,
backend=self.backend
)
self._module = module
try:
@ -1170,6 +1165,28 @@ class SelfSignedCertificateCryptography(Certificate):
except OpenSSLBadPassphraseError as exc:
module.fail_json(msg=to_native(exc))
if self.csr_path is not None or self.csr_content is not None:
self.csr = load_certificate_request(
path=self.csr_path,
content=self.csr_content,
backend=self.backend
)
else:
# Create empty CSR on the fly
csr = cryptography.x509.CertificateSigningRequestBuilder()
csr = csr.subject_name(cryptography.x509.Name([]))
digest = None
if cryptography_key_needs_digest_for_signing(self.privatekey):
digest = self.digest
if digest is None:
self.module.fail_json(msg='Unsupported digest "{0}"'.format(module.params['selfsigned_digest']))
try:
self.csr = csr.sign(self.privatekey, digest, default_backend())
except TypeError as e:
if str(e) == 'Algorithm must be a registered hash algorithm.' and digest is None:
self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.')
raise
if cryptography_key_needs_digest_for_signing(self.privatekey):
if self.digest is None:
raise CertificateError(
@ -1179,14 +1196,6 @@ class SelfSignedCertificateCryptography(Certificate):
self.digest = None
def generate(self, module):
if self.privatekey_content is None and not os.path.exists(self.privatekey_path):
raise CertificateError(
'The private key %s does not exist' % self.privatekey_path
)
if self.csr_content is None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file %s does not exist' % self.csr_path
)
if not self.check(module, perms_required=False) or self.force:
try:
cert_builder = x509.CertificateBuilder()
@ -1285,7 +1294,7 @@ class SelfSignedCertificate(Certificate):
self.version = module.params['selfsigned_version']
self.serial_number = generate_serial_number()
if self.csr_content is None and not os.path.exists(self.csr_path):
if self.csr_path is not None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file {0} does not exist'.format(self.csr_path)
)
@ -1294,10 +1303,6 @@ class SelfSignedCertificate(Certificate):
'The private key file {0} does not exist'.format(self.privatekey_path)
)
self.csr = load_certificate_request(
path=self.csr_path,
content=self.csr_content,
)
try:
self.privatekey = load_privatekey(
path=self.privatekey_path,
@ -1307,18 +1312,18 @@ class SelfSignedCertificate(Certificate):
except OpenSSLBadPassphraseError as exc:
module.fail_json(msg=str(exc))
if self.csr_path is not None or self.csr_content is not None:
self.csr = load_certificate_request(
path=self.csr_path,
content=self.csr_content,
)
else:
# Create empty CSR on the fly
self.csr = crypto.X509Req()
self.csr.set_pubkey(self.privatekey)
self.csr.sign(self.privatekey, self.digest)
def generate(self, module):
if self.privatekey_content is None and not os.path.exists(self.privatekey_path):
raise CertificateError(
'The private key %s does not exist' % self.privatekey_path
)
if self.csr_content is None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file %s does not exist' % self.csr_path
)
if not self.check(module, perms_required=False) or self.force:
cert = crypto.X509()
cert.set_serial_number(self.serial_number)
@ -2666,8 +2671,8 @@ def main():
certificate = CertificateAbsent(module)
else:
if module.params['provider'] != 'assertonly' and module.params['csr_path'] is None and module.params['csr_content'] is None:
module.fail_json(msg='csr_path or csr_content is required when provider is not assertonly')
if module.params['provider'] not in ('assertonly', 'selfsigned') and module.params['csr_path'] is None and module.params['csr_content'] is None:
module.fail_json(msg='csr_path or csr_content is required when provider is not assertonly or selfsigned')
base_dir = os.path.dirname(module.params['path']) or '.'
if not os.path.isdir(base_dir):

View File

@ -245,11 +245,11 @@
ignore_errors: yes
register: passphrase_error_3
- name: Create broken certificate
- name: (OwnCA, {{select_crypto_backend}}) Create broken certificate
copy:
dest: "{{ output_dir }}/ownca_broken.pem"
content: "broken"
- name: Regenerate broken cert
- name: (OwnCA, {{select_crypto_backend}}) Regenerate broken cert
x509_certificate:
path: '{{ output_dir }}/ownca_broken.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'

View File

@ -10,6 +10,36 @@
cipher: auto
select_crypto_backend: cryptography
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate without CSR
x509_certificate:
path: '{{ output_dir }}/cert_no_csr.pem'
privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: selfsigned
selfsigned_digest: sha256
select_crypto_backend: '{{ select_crypto_backend }}'
return_content: yes
register: selfsigned_certificate_no_csr
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate without CSR - idempotency
x509_certificate:
path: '{{ output_dir }}/cert_no_csr.pem'
privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: selfsigned
selfsigned_digest: sha256
select_crypto_backend: '{{ select_crypto_backend }}'
return_content: yes
register: selfsigned_certificate_no_csr_idempotence
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate without CSR (check mode)
x509_certificate:
path: '{{ output_dir }}/cert_no_csr.pem'
privatekey_path: '{{ output_dir }}/privatekey.pem'
provider: selfsigned
selfsigned_digest: sha256
select_crypto_backend: '{{ select_crypto_backend }}'
check_mode: yes
register: selfsigned_certificate_no_csr_idempotence_check
- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR
openssl_csr:
path: '{{ output_dir }}/csr.csr'
@ -250,11 +280,11 @@
ignore_errors: yes
register: passphrase_error_3
- name: Create broken certificate
- name: (Selfsigned, {{select_crypto_backend}}) Create broken certificate
copy:
dest: "{{ output_dir }}/cert_broken.pem"
content: "broken"
- name: Regenerate broken cert
- name: (Selfsigned, {{select_crypto_backend}}) Regenerate broken cert
x509_certificate:
path: '{{ output_dir }}/cert_broken.pem'
csr_path: '{{ output_dir }}/csr_ecc.csr'

View File

@ -3,6 +3,40 @@
shell: 'openssl rsa -noout -modulus -in {{ output_dir }}/privatekey.pem'
register: privatekey_modulus
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate behavior for no CSR
assert:
that:
- selfsigned_certificate_no_csr is changed
- selfsigned_certificate_no_csr_idempotence is not changed
- selfsigned_certificate_no_csr_idempotence_check is not changed
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate with no CSR (test - certificate modulus)
shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/cert_no_csr.pem'
register: cert_modulus
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate with no CSR (test - certficate version == default == 3)
shell: 'openssl x509 -noout -in {{ output_dir}}/cert_no_csr.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"'
register: cert_version
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate with no CSR (assert)
assert:
that:
- cert_modulus.stdout == privatekey_modulus.stdout
- cert_version.stdout == '3'
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate with no CSR idempotence
assert:
that:
- selfsigned_certificate_no_csr.serial_number == selfsigned_certificate_no_csr_idempotence.serial_number
- selfsigned_certificate_no_csr.notBefore == selfsigned_certificate_no_csr_idempotence.notBefore
- selfsigned_certificate_no_csr.notAfter == selfsigned_certificate_no_csr_idempotence.notAfter
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate data retrieval with no CSR
assert:
that:
- selfsigned_certificate_no_csr.certificate == lookup('file', output_dir ~ '/cert_no_csr.pem', rstrip=False)
- selfsigned_certificate_no_csr.certificate == selfsigned_certificate_no_csr_idempotence.certificate
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - certificate modulus)
shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/cert.pem'
register: cert_modulus