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
parent
b32adcce78
commit
fd7871ae7d
|
@ -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)."
|
|
@ -87,13 +87,13 @@ options:
|
||||||
csr_path:
|
csr_path:
|
||||||
description:
|
description:
|
||||||
- Path to the Certificate Signing Request (CSR) used to generate this certificate.
|
- 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).
|
- This is mutually exclusive with I(csr_content).
|
||||||
type: path
|
type: path
|
||||||
csr_content:
|
csr_content:
|
||||||
description:
|
description:
|
||||||
- Content of the Certificate Signing Request (CSR) used to generate this certificate.
|
- 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).
|
- This is mutually exclusive with I(csr_path).
|
||||||
type: str
|
type: str
|
||||||
version_added: '1.0.0'
|
version_added: '1.0.0'
|
||||||
|
@ -1144,7 +1144,7 @@ class SelfSignedCertificateCryptography(Certificate):
|
||||||
self.version = module.params['selfsigned_version']
|
self.version = module.params['selfsigned_version']
|
||||||
self.serial_number = x509.random_serial_number()
|
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(
|
raise CertificateError(
|
||||||
'The certificate signing request file {0} does not exist'.format(self.csr_path)
|
'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)
|
'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
|
self._module = module
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -1170,6 +1165,28 @@ class SelfSignedCertificateCryptography(Certificate):
|
||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
module.fail_json(msg=to_native(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 cryptography_key_needs_digest_for_signing(self.privatekey):
|
||||||
if self.digest is None:
|
if self.digest is None:
|
||||||
raise CertificateError(
|
raise CertificateError(
|
||||||
|
@ -1179,14 +1196,6 @@ class SelfSignedCertificateCryptography(Certificate):
|
||||||
self.digest = None
|
self.digest = None
|
||||||
|
|
||||||
def generate(self, module):
|
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:
|
if not self.check(module, perms_required=False) or self.force:
|
||||||
try:
|
try:
|
||||||
cert_builder = x509.CertificateBuilder()
|
cert_builder = x509.CertificateBuilder()
|
||||||
|
@ -1285,7 +1294,7 @@ class SelfSignedCertificate(Certificate):
|
||||||
self.version = module.params['selfsigned_version']
|
self.version = module.params['selfsigned_version']
|
||||||
self.serial_number = generate_serial_number()
|
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(
|
raise CertificateError(
|
||||||
'The certificate signing request file {0} does not exist'.format(self.csr_path)
|
'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)
|
'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:
|
try:
|
||||||
self.privatekey = load_privatekey(
|
self.privatekey = load_privatekey(
|
||||||
path=self.privatekey_path,
|
path=self.privatekey_path,
|
||||||
|
@ -1307,18 +1312,18 @@ class SelfSignedCertificate(Certificate):
|
||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
module.fail_json(msg=str(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):
|
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:
|
if not self.check(module, perms_required=False) or self.force:
|
||||||
cert = crypto.X509()
|
cert = crypto.X509()
|
||||||
cert.set_serial_number(self.serial_number)
|
cert.set_serial_number(self.serial_number)
|
||||||
|
@ -2666,8 +2671,8 @@ def main():
|
||||||
certificate = CertificateAbsent(module)
|
certificate = CertificateAbsent(module)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if module.params['provider'] != 'assertonly' and module.params['csr_path'] is None and module.params['csr_content'] is None:
|
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')
|
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 '.'
|
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||||
if not os.path.isdir(base_dir):
|
if not os.path.isdir(base_dir):
|
||||||
|
|
|
@ -245,11 +245,11 @@
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
register: passphrase_error_3
|
register: passphrase_error_3
|
||||||
|
|
||||||
- name: Create broken certificate
|
- name: (OwnCA, {{select_crypto_backend}}) Create broken certificate
|
||||||
copy:
|
copy:
|
||||||
dest: "{{ output_dir }}/ownca_broken.pem"
|
dest: "{{ output_dir }}/ownca_broken.pem"
|
||||||
content: "broken"
|
content: "broken"
|
||||||
- name: Regenerate broken cert
|
- name: (OwnCA, {{select_crypto_backend}}) Regenerate broken cert
|
||||||
x509_certificate:
|
x509_certificate:
|
||||||
path: '{{ output_dir }}/ownca_broken.pem'
|
path: '{{ output_dir }}/ownca_broken.pem'
|
||||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||||
|
|
|
@ -10,6 +10,36 @@
|
||||||
cipher: auto
|
cipher: auto
|
||||||
select_crypto_backend: cryptography
|
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
|
- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR
|
||||||
openssl_csr:
|
openssl_csr:
|
||||||
path: '{{ output_dir }}/csr.csr'
|
path: '{{ output_dir }}/csr.csr'
|
||||||
|
@ -250,11 +280,11 @@
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
register: passphrase_error_3
|
register: passphrase_error_3
|
||||||
|
|
||||||
- name: Create broken certificate
|
- name: (Selfsigned, {{select_crypto_backend}}) Create broken certificate
|
||||||
copy:
|
copy:
|
||||||
dest: "{{ output_dir }}/cert_broken.pem"
|
dest: "{{ output_dir }}/cert_broken.pem"
|
||||||
content: "broken"
|
content: "broken"
|
||||||
- name: Regenerate broken cert
|
- name: (Selfsigned, {{select_crypto_backend}}) Regenerate broken cert
|
||||||
x509_certificate:
|
x509_certificate:
|
||||||
path: '{{ output_dir }}/cert_broken.pem'
|
path: '{{ output_dir }}/cert_broken.pem'
|
||||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||||
|
|
|
@ -3,6 +3,40 @@
|
||||||
shell: 'openssl rsa -noout -modulus -in {{ output_dir }}/privatekey.pem'
|
shell: 'openssl rsa -noout -modulus -in {{ output_dir }}/privatekey.pem'
|
||||||
register: privatekey_modulus
|
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)
|
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - certificate modulus)
|
||||||
shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/cert.pem'
|
shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/cert.pem'
|
||||||
register: cert_modulus
|
register: cert_modulus
|
||||||
|
|
Loading…
Reference in New Issue