diff --git a/changelogs/fragments/317-ignore-timestamps.yml b/changelogs/fragments/317-ignore-timestamps.yml new file mode 100644 index 00000000..4631ffa6 --- /dev/null +++ b/changelogs/fragments/317-ignore-timestamps.yml @@ -0,0 +1,2 @@ +minor_changes: + - "x509_certificate, x509_certificate_pipe - add ``ignore_timestamps`` option which allows to enable idempotency for 'not before' and 'not after' options (https://github.com/ansible-collections/community.crypto/issues/295, https://github.com/ansible-collections/community.crypto/pull/317)." diff --git a/plugins/doc_fragments/module_certificate.py b/plugins/doc_fragments/module_certificate.py index 18e8727f..28df5995 100644 --- a/plugins/doc_fragments/module_certificate.py +++ b/plugins/doc_fragments/module_certificate.py @@ -52,6 +52,14 @@ options: - This is required if the private key is password protected. type: str + ignore_timestamps: + description: + - Whether the "not before" and "not after" timestamps should be ignored for idempotency checks. + - It is better to keep the default value C(true) when using relative timestamps (like C(+0s) for now). + type: bool + default: true + version_added: 2.0.0 + select_crypto_backend: description: - Determines which crypto backend to use. @@ -185,6 +193,7 @@ options: - The minimum certificate lifetime is 90 days, and maximum is three years. - If this value is not specified, the certificate will stop being valid 365 days the date of issue. - This is only used by the C(entrust) provider. + - Please note that this value is B(not) covered by the I(ignore_timestamps) option. type: str default: +365d @@ -258,6 +267,8 @@ options: + C([w | d | h | m | s]) (e.g. C(+32w1d2h). - If this value is not specified, the certificate will start being valid from now. - Note that this value is B(not used to determine whether an existing certificate should be regenerated). + This can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should + avoid relative timestamps when setting I(ignore_timestamps=false). - This is only used by the C(ownca) provider. type: str default: +0s @@ -271,6 +282,8 @@ options: + C([w | d | h | m | s]) (e.g. C(+32w1d2h). - If this value is not specified, the certificate will stop being valid 10 years from now. - Note that this value is B(not used to determine whether an existing certificate should be regenerated). + This can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should + avoid relative timestamps when setting I(ignore_timestamps=false). - This is only used by the C(ownca) provider. - On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer. Please see U(https://support.apple.com/en-us/HT210176) for more details. @@ -349,6 +362,8 @@ options: + C([w | d | h | m | s]) (e.g. C(+32w1d2h). - If this value is not specified, the certificate will start being valid from now. - Note that this value is B(not used to determine whether an existing certificate should be regenerated). + This can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should + avoid relative timestamps when setting I(ignore_timestamps=false). - This is only used by the C(selfsigned) provider. type: str default: +0s @@ -363,6 +378,8 @@ options: + C([w | d | h | m | s]) (e.g. C(+32w1d2h). - If this value is not specified, the certificate will stop being valid 10 years from now. - Note that this value is B(not used to determine whether an existing certificate should be regenerated). + This can be changed by setting the I(ignore_timestamps) option to C(false). Please note that you should + avoid relative timestamps when setting I(ignore_timestamps=false). - This is only used by the C(selfsigned) provider. - On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer. Please see U(https://support.apple.com/en-us/HT210176) for more details. diff --git a/plugins/module_utils/crypto/module_backends/certificate.py b/plugins/module_utils/crypto/module_backends/certificate.py index 8c5bb33c..b2769c1e 100644 --- a/plugins/module_utils/crypto/module_backends/certificate.py +++ b/plugins/module_utils/crypto/module_backends/certificate.py @@ -63,6 +63,7 @@ class CertificateBackend(object): self.backend = backend self.force = module.params['force'] + self.ignore_timestamps = module.params['ignore_timestamps'] self.privatekey_path = module.params['privatekey_path'] self.privatekey_content = module.params['privatekey_content'] if self.privatekey_content is not None: @@ -223,7 +224,7 @@ class CertificateBackend(object): return False return True - def needs_regeneration(self): + def needs_regeneration(self, not_before=None, not_after=None): """Check whether a regeneration is necessary.""" if self.force or self.existing_certificate_bytes is None: return True @@ -247,6 +248,15 @@ class CertificateBackend(object): if self.create_subject_key_identifier != 'never_create' and not self._check_subject_key_identifier(): return True + # Check not before + if not_before is not None and not self.ignore_timestamps: + if self.existing_certificate.not_valid_before != not_before: + return True + + # Check not after + if not_after is not None and not self.ignore_timestamps: + if self.existing_certificate.not_valid_after != not_after: + return True return False def dump(self, include_certificate): @@ -328,6 +338,7 @@ def get_certificate_argument_spec(): force=dict(type='bool', default=False,), csr_path=dict(type='path'), csr_content=dict(type='str'), + ignore_timestamps=dict(type='bool', default=True), select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']), # General properties of a certificate diff --git a/plugins/module_utils/crypto/module_backends/certificate_ownca.py b/plugins/module_utils/crypto/module_backends/certificate_ownca.py index 55cd0374..1f20b54f 100644 --- a/plugins/module_utils/crypto/module_backends/certificate_ownca.py +++ b/plugins/module_utils/crypto/module_backends/certificate_ownca.py @@ -169,7 +169,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend): return self.cert.public_bytes(Encoding.PEM) def needs_regeneration(self): - if super(OwnCACertificateBackendCryptography, self).needs_regeneration(): + if super(OwnCACertificateBackendCryptography, self).needs_regeneration(not_before=self.notBefore, not_after=self.notAfter): return True # Check AuthorityKeyIdentifier diff --git a/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py b/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py index 74aeaa33..d92fbb68 100644 --- a/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py +++ b/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py @@ -134,6 +134,10 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend): """Return bytes for self.cert.""" return self.cert.public_bytes(Encoding.PEM) + def needs_regeneration(self): + return super(SelfSignedCertificateBackendCryptography, self).needs_regeneration( + not_before=self.notBefore, not_after=self.notAfter) + def dump(self, include_certificate): result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate)