summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/crypto/plugins/modules
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/crypto/plugins/modules')
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_account.py22
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_account_info.py22
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_ari_info.py142
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_certificate.py192
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_certificate_deactivate_authz.py119
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_certificate_renewal_info.py245
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py18
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py8
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_inspect.py19
-rw-r--r--ansible_collections/community/crypto/plugins/modules/ecs_certificate.py4
-rw-r--r--ansible_collections/community/crypto/plugins/modules/get_certificate.py8
-rw-r--r--ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py8
-rw-r--r--ansible_collections/community/crypto/plugins/modules/x509_crl.py5
13 files changed, 668 insertions, 144 deletions
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_account.py b/ansible_collections/community/crypto/plugins/modules/acme_account.py
index 1e8d64a57..960bad313 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_account.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_account.py
@@ -37,7 +37,8 @@ seealso:
- module: community.crypto.acme_inspect
description: Allows to debug problems.
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
attributes:
@@ -169,11 +170,9 @@ account_uri:
import base64
-from ansible.module_utils.basic import AnsibleModule
-
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -188,8 +187,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec()
+ argument_spec.update_argspec(
terms_agreed=dict(type='bool', default=False),
state=dict(type='str', required=True, choices=['absent', 'present', 'changed_key']),
allow_creation=dict(type='bool', default=True),
@@ -202,14 +201,9 @@ def main():
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
key=dict(type='str', required=True, no_log=True),
))
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- required_one_of=(
- ['account_key_src', 'account_key_content'],
- ),
+ )
+ argument_spec.update(
mutually_exclusive=(
- ['account_key_src', 'account_key_content'],
['new_account_key_src', 'new_account_key_content'],
),
required_if=(
@@ -217,8 +211,8 @@ def main():
# new_account_key_src and new_account_key_content are specified
['state', 'changed_key', ['new_account_key_src', 'new_account_key_content'], True],
),
- supports_check_mode=True,
)
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
backend = create_backend(module, True)
if module.params['external_account_binding']:
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_account_info.py b/ansible_collections/community/crypto/plugins/modules/acme_account_info.py
index ac4617c90..33313fe75 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_account_info.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_account_info.py
@@ -25,7 +25,8 @@ notes:
- "This module was called C(acme_account_facts) before Ansible 2.8. The usage
did not change."
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
- community.crypto.attributes.info_module
@@ -213,11 +214,9 @@ order_uris:
version_added: 1.5.0
'''
-from ansible.module_utils.basic import AnsibleModule
-
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -270,20 +269,11 @@ def get_order(client, order_url):
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec()
+ argument_spec.update_argspec(
retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']),
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- required_one_of=(
- ['account_key_src', 'account_key_content'],
- ),
- mutually_exclusive=(
- ['account_key_src', 'account_key_content'],
- ),
- supports_check_mode=True,
)
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
backend = create_backend(module, True)
try:
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_ari_info.py b/ansible_collections/community/crypto/plugins/modules/acme_ari_info.py
new file mode 100644
index 000000000..7783236f0
--- /dev/null
+++ b/ansible_collections/community/crypto/plugins/modules/acme_ari_info.py
@@ -0,0 +1,142 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 Felix Fontein <felix@fontein.de>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = '''
+---
+module: acme_ari_info
+author: "Felix Fontein (@felixfontein)"
+version_added: 2.20.0
+short_description: Retrieves ACME Renewal Information (ARI) for a certificate
+description:
+ - "Allows to retrieve renewal information on a certificate obtained with the
+ L(ACME protocol,https://tools.ietf.org/html/rfc8555)."
+ - "This module only works with the ACME v2 protocol, and requires the ACME server
+ to support the ARI extension (U(https://datatracker.ietf.org/doc/draft-ietf-acme-ari/)).
+ This module implements version 3 of the ARI draft."
+extends_documentation_fragment:
+ - community.crypto.acme.basic
+ - community.crypto.acme.no_account
+ - community.crypto.attributes
+ - community.crypto.attributes.info_module
+options:
+ certificate_path:
+ description:
+ - A path to the X.509 certificate to request information for.
+ - Exactly one of O(certificate_path) and O(certificate_content) must be provided.
+ type: path
+ certificate_content:
+ description:
+ - The content of the X.509 certificate to request information for.
+ - Exactly one of O(certificate_path) and O(certificate_content) must be provided.
+ type: str
+seealso:
+ - module: community.crypto.acme_certificate
+ description: Allows to obtain a certificate using the ACME protocol
+ - module: community.crypto.acme_certificate_revoke
+ description: Allows to revoke a certificate using the ACME protocol
+'''
+
+EXAMPLES = '''
+- name: Retrieve renewal information for a certificate
+ community.crypto.acme_ari_info:
+ certificate_path: /etc/httpd/ssl/sample.com.crt
+ register: cert_data
+
+- name: Show the certificate renewal information
+ ansible.builtin.debug:
+ var: cert_data.renewal_info
+'''
+
+RETURN = '''
+renewal_info:
+ description: The ARI renewal info object (U(https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.2)).
+ returned: success
+ type: dict
+ contains:
+ suggestedWindow:
+ description:
+ - Describes the window during which the certificate should be renewed.
+ type: dict
+ returned: always
+ contains:
+ start:
+ description:
+ - The start of the window during which the certificate should be renewed.
+ - The format is specified in L(RFC 3339,https://www.rfc-editor.org/info/rfc3339).
+ returned: always
+ type: str
+ sample: '2021-01-03T00:00:00Z'
+ end:
+ description:
+ - The end of the window during which the certificate should be renewed.
+ - The format is specified in L(RFC 3339,https://www.rfc-editor.org/info/rfc3339).
+ returned: always
+ type: str
+ sample: '2021-01-03T00:00:00Z'
+ explanationURL:
+ description:
+ - A URL pointing to a page which may explain why the suggested renewal window is what it is.
+ - For example, it may be a page explaining the CA's dynamic load-balancing strategy, or a
+ page documenting which certificates are affected by a mass revocation event. Should be shown
+ to the user.
+ returned: depends on the ACME server
+ type: str
+ sample: https://example.com/docs/ari
+ retryAfter:
+ description:
+ - A timestamp before the next retry to ask for this information should not be made.
+ returned: depends on the ACME server
+ type: str
+ sample: '2024-04-29T01:17:10.236921+00:00'
+'''
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
+ create_backend,
+ create_default_argspec,
+ ACMEClient,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
+
+
+def main():
+ argument_spec = create_default_argspec(with_account=False)
+ argument_spec.update_argspec(
+ certificate_path=dict(type='path'),
+ certificate_content=dict(type='str'),
+ )
+ argument_spec.update(
+ required_one_of=(
+ ['certificate_path', 'certificate_content'],
+ ),
+ mutually_exclusive=(
+ ['certificate_path', 'certificate_content'],
+ ),
+ )
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
+ backend = create_backend(module, True)
+
+ try:
+ client = ACMEClient(module, backend)
+ if not client.directory.has_renewal_info_endpoint():
+ module.fail_json(msg='The ACME endpoint does not support ACME Renewal Information retrieval')
+ renewal_info = client.get_renewal_info(
+ cert_filename=module.params['certificate_path'],
+ cert_content=module.params['certificate_content'],
+ include_retry_after=True,
+ )
+ module.exit_json(renewal_info=renewal_info)
+ except ModuleFailException as e:
+ e.do_fail(module)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_certificate.py b/ansible_collections/community/crypto/plugins/modules/acme_certificate.py
index 21a6d6ae9..8729996c0 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_certificate.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_certificate.py
@@ -58,7 +58,7 @@ seealso:
link: https://tools.ietf.org/html/rfc8555
- name: ACME TLS ALPN Challenge Extension
description: The specification of the V(tls-alpn-01) challenge (RFC 8737).
- link: https://www.rfc-editor.org/rfc/rfc8737.html-05
+ link: https://www.rfc-editor.org/rfc/rfc8737.html
- module: community.crypto.acme_challenge_cert_helper
description: Helps preparing V(tls-alpn-01) challenges.
- module: community.crypto.openssl_privatekey
@@ -77,8 +77,12 @@ seealso:
description: Allows to create, modify or delete an ACME account.
- module: community.crypto.acme_inspect
description: Allows to debug problems.
+ - module: community.crypto.acme_certificate_deactivate_authz
+ description: Allows to deactivate (invalidate) ACME v2 orders.
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
+ - community.crypto.acme.certificate
- community.crypto.attributes
- community.crypto.attributes.files
- community.crypto.attributes.actiongroup_acme
@@ -138,32 +142,8 @@ options:
- 'tls-alpn-01'
- 'no challenge'
csr:
- description:
- - "File containing the CSR for the new certificate."
- - "Can be created with M(community.crypto.openssl_csr) or C(openssl req ...)."
- - "The CSR may contain multiple Subject Alternate Names, but each one
- will lead to an individual challenge that must be fulfilled for the
- CSR to be signed."
- - "I(Note): the private key used to create the CSR I(must not) be the
- account key. This is a bad idea from a security point of view, and
- the CA should not accept the CSR. The ACME server should return an
- error in this case."
- - Precisely one of O(csr) or O(csr_content) must be specified.
- type: path
aliases: ['src']
csr_content:
- description:
- - "Content of the CSR for the new certificate."
- - "Can be created with M(community.crypto.openssl_csr_pipe) or C(openssl req ...)."
- - "The CSR may contain multiple Subject Alternate Names, but each one
- will lead to an individual challenge that must be fulfilled for the
- CSR to be signed."
- - "I(Note): the private key used to create the CSR I(must not) be the
- account key. This is a bad idea from a security point of view, and
- the CA should not accept the CSR. The ACME server should return an
- error in this case."
- - Precisely one of O(csr) or O(csr_content) must be specified.
- type: str
version_added: 1.2.0
data:
description:
@@ -292,6 +272,32 @@ options:
- "The identifier must be of the form
V(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)."
type: str
+ include_renewal_cert_id:
+ description:
+ - Determines whether to request renewal of an existing certificate according to
+ L(the ACME ARI draft 3, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-5).
+ - This is only used when the certificate specified in O(dest) or O(fullchain_dest) already exists.
+ - V(never) never sends the certificate ID of the certificate to renew. V(always) will always send it.
+ - V(when_ari_supported) only sends the certificate ID if the ARI endpoint is found in the ACME directory.
+ - Generally you should use V(when_ari_supported) if you know that the ACME service supports a compatible
+ draft (or final version, once it is out) of the ARI extension. V(always) should never be necessary.
+ If you are not sure, or if you receive strange errors on invalid C(replaces) values in order objects,
+ use V(never), which also happens to be the default.
+ - ACME servers might refuse to create new orders with C(replaces) for certificates that already have an
+ existing order. This can happen if this module is used to create an order, and then the playbook/role
+ fails in case the challenges cannot be set up. If the playbook/role does not record the order data to
+ continue with the existing order, but tries to create a new one on the next run, creating the new order
+ might fail. For this reason, this option should only be set to a value different from V(never) if the
+ role/playbook using it keeps track of order data accross restarts, or if it takes care to deactivate
+ orders whose processing is aborted. Orders can be deactivated with the
+ M(community.crypto.acme_certificate_deactivate_authz) module.
+ type: str
+ choices:
+ - never
+ - when_ari_supported
+ - always
+ default: never
+ version_added: 2.20.0
'''
EXAMPLES = r'''
@@ -375,7 +381,7 @@ EXAMPLES = r'''
# state: present
# wait: true
# # Note: route53 requires TXT entries to be enclosed in quotes
-# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | regex_replace('^(.*)$', '\"\\1\"') }}"
+# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | community.dns.quote_txt(always_quote=true) }}"
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge.challenge_data
#
# Alternative way:
@@ -390,7 +396,7 @@ EXAMPLES = r'''
# wait: true
# # Note: item.value is a list of TXT entries, and route53
# # requires every entry to be enclosed in quotes
-# value: "{{ item.value | map('regex_replace', '^(.*)$', '\"\\1\"' ) | list }}"
+# value: "{{ item.value | map('community.dns.quote_txt', always_quote=true) | list }}"
# loop: "{{ sample_com_challenge.challenge_data_dns | dict2items }}"
# when: sample_com_challenge is changed
@@ -446,39 +452,55 @@ challenge_data:
- Per identifier / challenge type challenge data.
- Since Ansible 2.8.5, only challenges which are not yet valid are returned.
returned: changed
- type: list
- elements: dict
+ type: dict
contains:
- resource:
- description: The challenge resource that must be created for validation.
- returned: changed
- type: str
- sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA
- resource_original:
- description:
- - The original challenge resource including type identifier for V(tls-alpn-01)
- challenges.
- returned: changed and O(challenge) is V(tls-alpn-01)
- type: str
- sample: DNS:example.com
- resource_value:
+ identifier:
description:
- - The value the resource has to produce for the validation.
- - For V(http-01) and V(dns-01) challenges, the value can be used as-is.
- - "For V(tls-alpn-01) challenges, note that this return value contains a
- Base64 encoded version of the correct binary blob which has to be put
- into the acmeValidation x509 extension; see
- U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3)
- for details. To do this, you might need the P(ansible.builtin.b64decode#filter) Jinja filter
- to extract the binary blob from this return value."
+ - For every identifier, provides a dictionary of challenge types mapping to challenge data.
+ - The keys in this dictionary are the identifiers. C(identifier) is a placeholder used in the documentation.
+ - Note that the keys are not valid Jinja2 identifiers.
returned: changed
- type: str
- sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA
- record:
- description: The full DNS record's name for the challenge.
- returned: changed and challenge is V(dns-01)
- type: str
- sample: _acme-challenge.example.com
+ type: dict
+ contains:
+ challenge-type:
+ description:
+ - Data for every challenge type.
+ - The keys in this dictionary are the challenge types. C(challenge-type) is a placeholder used in the documentation.
+ Possible keys are V(http-01), V(dns-01), and V(tls-alpn-01).
+ - Note that the keys are not valid Jinja2 identifiers.
+ returned: changed
+ type: dict
+ contains:
+ resource:
+ description: The challenge resource that must be created for validation.
+ returned: changed
+ type: str
+ sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA
+ resource_original:
+ description:
+ - The original challenge resource including type identifier for V(tls-alpn-01)
+ challenges.
+ returned: changed and O(challenge) is V(tls-alpn-01)
+ type: str
+ sample: DNS:example.com
+ resource_value:
+ description:
+ - The value the resource has to produce for the validation.
+ - For V(http-01) and V(dns-01) challenges, the value can be used as-is.
+ - "For V(tls-alpn-01) challenges, note that this return value contains a
+ Base64 encoded version of the correct binary blob which has to be put
+ into the acmeValidation x509 extension; see
+ U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3)
+ for details. To do this, you might need the P(ansible.builtin.b64decode#filter) Jinja filter
+ to extract the binary blob from this return value."
+ returned: changed
+ type: str
+ sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA
+ record:
+ description: The full DNS record's name for the challenge.
+ returned: changed and challenge is V(dns-01)
+ type: str
+ sample: _acme-challenge.example.com
challenge_data_dns:
description:
- List of TXT values per DNS record, in case challenge is V(dns-01).
@@ -547,11 +569,9 @@ all_chains:
import os
-from ansible.module_utils.basic import AnsibleModule
-
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -585,6 +605,7 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.orders impor
)
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
+ compute_cert_id,
pem_to_der,
)
@@ -621,6 +642,7 @@ class ACMECertificateClient(object):
self.order_uri = self.data.get('order_uri') if self.data else None
self.all_chains = None
self.select_chain_matcher = []
+ self.include_renewal_cert_id = module.params['include_renewal_cert_id']
if self.module.params['select_chain']:
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
@@ -678,6 +700,15 @@ class ACMECertificateClient(object):
# stored in self.order_uri by the constructor).
return self.order_uri is None
+ def _get_cert_info_or_none(self):
+ if self.module.params.get('dest'):
+ filename = self.module.params['dest']
+ else:
+ filename = self.module.params['fullchain_dest']
+ if not os.path.exists(filename):
+ return None
+ return self.client.backend.get_cert_information(cert_filename=filename)
+
def start_challenges(self):
'''
Create new authorizations for all identifiers of the CSR,
@@ -692,7 +723,19 @@ class ACMECertificateClient(object):
authz = Authorization.create(self.client, identifier_type, identifier)
self.authorizations[authz.combined_identifier] = authz
else:
- self.order = Order.create(self.client, self.identifiers)
+ replaces_cert_id = None
+ if (
+ self.include_renewal_cert_id == 'always' or
+ (self.include_renewal_cert_id == 'when_ari_supported' and self.client.directory.has_renewal_info_endpoint())
+ ):
+ cert_info = self._get_cert_info_or_none()
+ if cert_info is not None:
+ replaces_cert_id = compute_cert_id(
+ self.client.backend,
+ cert_info=cert_info,
+ none_if_required_information_is_missing=True,
+ )
+ self.order = Order.create(self.client, self.identifiers, replaces_cert_id)
self.order_uri = self.order.url
self.order.load_authorizations(self.client)
self.authorizations.update(self.order.authorizations)
@@ -854,15 +897,14 @@ class ACMECertificateClient(object):
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec(with_certificate=True)
+ argument_spec.argument_spec['csr']['aliases'] = ['src']
+ argument_spec.update_argspec(
modify_account=dict(type='bool', default=True),
account_email=dict(type='str'),
agreement=dict(type='str'),
terms_agreed=dict(type='bool', default=False),
challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01', NO_CHALLENGE]),
- csr=dict(type='path', aliases=['src']),
- csr_content=dict(type='str'),
data=dict(type='dict'),
dest=dict(type='path', aliases=['cert']),
fullchain_dest=dict(type='path', aliases=['fullchain']),
@@ -878,20 +920,14 @@ def main():
subject_key_identifier=dict(type='str'),
authority_key_identifier=dict(type='str'),
)),
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- required_one_of=(
- ['account_key_src', 'account_key_content'],
+ include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
+ )
+ argument_spec.update(
+ required_one_of=[
['dest', 'fullchain_dest'],
- ['csr', 'csr_content'],
- ),
- mutually_exclusive=(
- ['account_key_src', 'account_key_content'],
- ['csr', 'csr_content'],
- ),
- supports_check_mode=True,
+ ],
)
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
backend = create_backend(module, False)
try:
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_certificate_deactivate_authz.py b/ansible_collections/community/crypto/plugins/modules/acme_certificate_deactivate_authz.py
new file mode 100644
index 000000000..133f777d6
--- /dev/null
+++ b/ansible_collections/community/crypto/plugins/modules/acme_certificate_deactivate_authz.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = '''
+---
+module: acme_certificate_deactivate_authz
+author: "Felix Fontein (@felixfontein)"
+version_added: 2.20.0
+short_description: Deactivate all authz for an ACME v2 order
+description:
+ - "Deactivate all authentication objects (authz) for an ACME v2 order,
+ which effectively deactivates (invalidates) the order itself."
+ - "Authentication objects are bound to an account key and remain valid
+ for a certain amount of time, and can be used to issue certificates
+ without having to re-authenticate the domain. This can be a security
+ concern."
+ - "Another reason to use this module is to deactivate an order whose
+ processing failed when using O(community.crypto.acme_certificate#module:include_renewal_cert_id)."
+seealso:
+ - module: community.crypto.acme_certificate
+extends_documentation_fragment:
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
+ - community.crypto.attributes
+ - community.crypto.attributes.actiongroup_acme
+attributes:
+ check_mode:
+ support: full
+ diff_mode:
+ support: none
+options:
+ order_uri:
+ description:
+ - The ACME v2 order to deactivate.
+ - Can be obtained from RV(community.crypto.acme_certificate#module:order_uri).
+ type: str
+ required: true
+'''
+
+EXAMPLES = r'''
+- name: Deactivate all authzs for an order
+ community.crypto.acme_certificate_deactivate_authz:
+ account_key_content: "{{ account_private_key }}"
+ order_uri: "{{ certificate_result.order_uri }}"
+'''
+
+RETURN = '''#'''
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
+ create_backend,
+ create_default_argspec,
+ ACMEClient,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.account import (
+ ACMEAccount,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
+ ModuleFailException,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.orders import (
+ Order,
+)
+
+
+def main():
+ argument_spec = create_default_argspec()
+ argument_spec.update_argspec(
+ order_uri=dict(type='str', required=True),
+ )
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
+ if module.params['acme_version'] == 1:
+ module.fail_json('The module does not support acme_version=1')
+
+ backend = create_backend(module, False)
+
+ try:
+ client = ACMEClient(module, backend)
+ account = ACMEAccount(client)
+
+ dummy, account_data = account.setup_account(allow_creation=False)
+ if account_data is None:
+ raise ModuleFailException(msg='Account does not exist or is deactivated.')
+
+ order = Order.from_url(client, module.params['order_uri'])
+ order.load_authorizations(client)
+
+ changed = False
+ for authz in order.authorizations.values():
+ if not authz.can_deactivate():
+ continue
+ changed = True
+ if module.check_mode:
+ continue
+ try:
+ authz.deactivate(client)
+ except Exception:
+ # ignore errors
+ pass
+ if authz.status != 'deactivated':
+ module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url))
+
+ module.exit_json(changed=changed)
+ except ModuleFailException as e:
+ e.do_fail(module)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_certificate_renewal_info.py b/ansible_collections/community/crypto/plugins/modules/acme_certificate_renewal_info.py
new file mode 100644
index 000000000..1e2b16918
--- /dev/null
+++ b/ansible_collections/community/crypto/plugins/modules/acme_certificate_renewal_info.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 Felix Fontein <felix@fontein.de>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = '''
+---
+module: acme_certificate_renewal_info
+author: "Felix Fontein (@felixfontein)"
+version_added: 2.20.0
+short_description: Determine whether a certificate should be renewed or not
+description:
+ - Uses various information to determine whether a certificate should be renewed or not.
+ - If available, the ARI extension (ACME Renewal Information, U(https://datatracker.ietf.org/doc/draft-ietf-acme-ari/))
+ is used. This module implements version 3 of the ARI draft."
+extends_documentation_fragment:
+ - community.crypto.acme.basic
+ - community.crypto.acme.no_account
+ - community.crypto.attributes
+ - community.crypto.attributes.info_module
+options:
+ certificate_path:
+ description:
+ - A path to the X.509 certificate to determine renewal of.
+ - In case the certificate does not exist, the module will always return RV(should_renew=true).
+ - O(certificate_path) and O(certificate_content) are mutually exclusive.
+ type: path
+ certificate_content:
+ description:
+ - The content of the X.509 certificate to determine renewal of.
+ - O(certificate_path) and O(certificate_content) are mutually exclusive.
+ type: str
+ use_ari:
+ description:
+ - Whether to use ARI information, if available.
+ - Set this to V(false) if the ACME server implements ARI in a way that is incompatible with this module.
+ type: bool
+ default: true
+ ari_algorithm:
+ description:
+ - If ARI information is used, selects which algorithm is used to determine whether to renew now.
+ - V(standard) selects the L(algorithm provided in the the ARI specification,
+ https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#name-renewalinfo-objects).
+ - V(start) returns RV(should_renew=true) once the start of the renewal interval has been reached.
+ type: str
+ choices:
+ - standard
+ - start
+ default: standard
+ remaining_days:
+ description:
+ - The number of days the certificate must have left being valid.
+ - For example, if O(remaining_days=20), this check causes RV(should_renew=true) if the
+ certificate is valid for less than 20 days.
+ type: int
+ remaining_percentage:
+ description:
+ - The percentage of the certificate's validity period that should be left.
+ - For example, if O(remaining_percentage=0.1), and the certificate's validity period is 90 days,
+ this check causes RV(should_renew=true) if the certificate is valid for less than 9 days.
+ - Must be a value between 0 and 1.
+ type: float
+ now:
+ description:
+ - Use this timestamp instead of the current timestamp to determine whether a certificate should be renewed.
+ - Time can be specified either as relative time or as absolute timestamp.
+ - Time will always be interpreted as UTC.
+ - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ + C([w | d | h | m | s]) (for example V(+32w1d2h)).
+ type: str
+seealso:
+ - module: community.crypto.acme_certificate
+ description: Allows to obtain a certificate using the ACME protocol
+ - module: community.crypto.acme_ari_info
+ description: Obtain renewal information for a certificate
+'''
+
+EXAMPLES = '''
+- name: Retrieve renewal information for a certificate
+ community.crypto.acme_certificate_renewal_info:
+ certificate_path: /etc/httpd/ssl/sample.com.crt
+ register: cert_data
+
+- name: Should the certificate be renewed?
+ ansible.builtin.debug:
+ var: cert_data.should_renew
+'''
+
+RETURN = '''
+should_renew:
+ description:
+ - Whether the certificate should be renewed.
+ - If no certificate is provided, or the certificate is expired, will always be V(true).
+ returned: success
+ type: bool
+ sample: true
+
+msg:
+ description:
+ - Information on the reason for renewal.
+ - Should be shown to the user, as in case of ARI triggered renewal it can contain important
+ information, for example on forced revocations for misissued certificates.
+ type: str
+ returned: success
+ sample: The certificate does not exist.
+
+supports_ari:
+ description:
+ - Whether ARI information was used to determine renewal. This can be used to determine whether to
+ specify O(community.crypto.acme_certificate#module:include_renewal_cert_id=when_ari_supported)
+ for the M(community.crypto.acme_certificate) module.
+ - If O(use_ari=false), this will always be V(false).
+ returned: success
+ type: bool
+ sample: true
+
+cert_id:
+ description:
+ - The certificate ID according to the L(ARI specification, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.1).
+ returned: success, the certificate exists, and has an Authority Key Identifier X.509 extension
+ type: str
+ sample: aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE
+'''
+
+import os
+import random
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
+ create_backend,
+ create_default_argspec,
+ ACMEClient,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.utils import compute_cert_id
+
+
+def main():
+ argument_spec = create_default_argspec(with_account=False)
+ argument_spec.update_argspec(
+ certificate_path=dict(type='path'),
+ certificate_content=dict(type='str'),
+ use_ari=dict(type='bool', default=True),
+ ari_algorithm=dict(type='str', choices=['standard', 'start'], default='standard'),
+ remaining_days=dict(type='int'),
+ remaining_percentage=dict(type='float'),
+ now=dict(type='str'),
+ )
+ argument_spec.update(
+ mutually_exclusive=(
+ ['certificate_path', 'certificate_content'],
+ ),
+ )
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
+ backend = create_backend(module, True)
+
+ result = dict(
+ changed=False,
+ msg='The certificate is still valid and no condition was reached',
+ supports_ari=False,
+ )
+
+ def complete(should_renew, **kwargs):
+ result['should_renew'] = should_renew
+ result.update(kwargs)
+ module.exit_json(**result)
+
+ if not module.params['certificate_path'] and not module.params['certificate_content']:
+ complete(True, msg='No certificate was specified')
+
+ if module.params['certificate_path'] is not None and not os.path.exists(module.params['certificate_path']):
+ complete(True, msg='The certificate file does not exist')
+
+ try:
+ cert_info = backend.get_cert_information(
+ cert_filename=module.params['certificate_path'],
+ cert_content=module.params['certificate_content'],
+ )
+ cert_id = compute_cert_id(backend, cert_info=cert_info, none_if_required_information_is_missing=True)
+ if cert_id is not None:
+ result['cert_id'] = cert_id
+
+ if module.params['now']:
+ now = backend.parse_module_parameter(module.params['now'], 'now')
+ else:
+ now = backend.get_now()
+
+ if now >= cert_info.not_valid_after:
+ complete(True, msg='The certificate has already expired')
+
+ client = ACMEClient(module, backend)
+ if cert_id is not None and module.params['use_ari'] and client.directory.has_renewal_info_endpoint():
+ renewal_info = client.get_renewal_info(cert_id=cert_id)
+ window_start = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['start'])
+ window_end = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['end'])
+ msg_append = ''
+ if 'explanationURL' in renewal_info:
+ msg_append = '. Information on renewal interval: {0}'.format(renewal_info['explanationURL'])
+ result['supports_ari'] = True
+ if now > window_end:
+ complete(True, msg='The suggested renewal interval provided by ARI is in the past{0}'.format(msg_append))
+ if module.params['ari_algorithm'] == 'start':
+ if now > window_start:
+ complete(True, msg='The suggested renewal interval provided by ARI has begun{0}'.format(msg_append))
+ else:
+ random_time = backend.interpolate_timestamp(window_start, window_end, random.random())
+ if now > random_time:
+ complete(
+ True,
+ msg='The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}'.format(
+ random_time,
+ msg_append,
+ ),
+ )
+
+ if module.params['remaining_days'] is not None:
+ remaining_days = (cert_info.not_valid_after - now).days
+ if remaining_days < module.params['remaining_days']:
+ complete(True, msg='The certificate expires in {0} days'.format(remaining_days))
+
+ if module.params['remaining_percentage'] is not None:
+ timestamp = backend.interpolate_timestamp(cert_info.not_valid_before, cert_info.not_valid_after, 1 - module.params['remaining_percentage'])
+ if timestamp < now:
+ complete(
+ True,
+ msg="The remaining percentage {0}% of the certificate's lifespan was reached on {1}".format(
+ module.params['remaining_percentage'] * 100,
+ timestamp,
+ ),
+ )
+
+ complete(False)
+ except ModuleFailException as e:
+ e.do_fail(module)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py b/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py
index 022862e60..2661a1525 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py
@@ -37,7 +37,8 @@ seealso:
- module: community.crypto.acme_inspect
description: Allows to debug problems.
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
attributes:
@@ -127,11 +128,9 @@ EXAMPLES = '''
RETURN = '''#'''
-from ansible.module_utils.basic import AnsibleModule
-
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -152,24 +151,23 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec(require_account_key=False)
+ argument_spec.update_argspec(
private_key_src=dict(type='path'),
private_key_content=dict(type='str', no_log=True),
private_key_passphrase=dict(type='str', no_log=True),
certificate=dict(type='path', required=True),
revoke_reason=dict(type='int'),
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
+ )
+ argument_spec.update(
required_one_of=(
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
),
mutually_exclusive=(
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
),
- supports_check_mode=False,
)
+ module = argument_spec.create_ansible_module()
backend = create_backend(module, False)
try:
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py b/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py
index 48b65f998..edd2c3331 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py
@@ -165,16 +165,16 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
read_file,
)
-from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
- get_now_datetime,
-)
-
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
set_not_valid_after,
set_not_valid_before,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_now_datetime,
+)
+
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_inspect.py b/ansible_collections/community/crypto/plugins/modules/acme_inspect.py
index a2c76507e..c7ee49765 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_inspect.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_inspect.py
@@ -42,7 +42,8 @@ seealso:
description: The specification of the C(tls-alpn-01) challenge (RFC 8737).
link: https://www.rfc-editor.org/rfc/rfc8737.html
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
attributes:
@@ -247,12 +248,11 @@ output_json:
- ...
'''
-from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -263,18 +263,14 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec(require_account_key=False)
+ argument_spec.update_argspec(
url=dict(type='str'),
method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'),
content=dict(type='str'),
fail_on_acme_error=dict(type='bool', default=True),
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- mutually_exclusive=(
- ['account_key_src', 'account_key_content'],
- ),
+ )
+ argument_spec.update(
required_if=(
['method', 'get', ['url']],
['method', 'post', ['url', 'content']],
@@ -282,6 +278,7 @@ def main():
['method', 'post', ['account_key_src', 'account_key_content'], True],
),
)
+ module = argument_spec.create_ansible_module()
backend = create_backend(module, False)
result = dict()
diff --git a/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py b/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py
index 2c1238d48..0276556ab 100644
--- a/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py
+++ b/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py
@@ -938,8 +938,8 @@ def main():
module.fail_json(msg='The cert_expiry field is invalid when request_type="reissue".')
elif module.params['cert_lifetime']:
module.fail_json(msg='The cert_lifetime field is invalid when request_type="reissue".')
- # Only a reissued request can omit the CSR
- else:
+ # Reissued or renew request can omit the CSR
+ elif module.params['request_type'] != 'renew':
module_params_csr = module.params['csr']
if module_params_csr is None:
module.fail_json(msg='The csr field is required when request_type={0}'.format(module.params['request_type']))
diff --git a/ansible_collections/community/crypto/plugins/modules/get_certificate.py b/ansible_collections/community/crypto/plugins/modules/get_certificate.py
index 6ae9439d3..d4b38afbd 100644
--- a/ansible_collections/community/crypto/plugins/modules/get_certificate.py
+++ b/ansible_collections/community/crypto/plugins/modules/get_certificate.py
@@ -220,10 +220,6 @@ from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
-from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
- get_now_datetime,
-)
-
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
cryptography_oid_to_name,
@@ -232,6 +228,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
get_not_valid_before,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_now_datetime,
+)
+
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
CREATE_DEFAULT_CONTEXT_IMP_ERR = None
diff --git a/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py b/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py
index 8379937f7..9e8c20e29 100644
--- a/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py
+++ b/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py
@@ -406,10 +406,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.basic impo
OpenSSLObjectError,
)
-from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
- get_relative_time_option,
-)
-
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
)
@@ -418,6 +414,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
select_backend,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_relative_time_option,
+)
+
def main():
module = AnsibleModule(
diff --git a/ansible_collections/community/crypto/plugins/modules/x509_crl.py b/ansible_collections/community/crypto/plugins/modules/x509_crl.py
index 527975b88..f8eb8d85e 100644
--- a/ansible_collections/community/crypto/plugins/modules/x509_crl.py
+++ b/ansible_collections/community/crypto/plugins/modules/x509_crl.py
@@ -470,7 +470,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
load_certificate,
parse_name_field,
parse_ordered_name_field,
- get_relative_time_option,
select_message_digest,
)
@@ -506,6 +505,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
get_crl_info,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_relative_time_option,
+)
+
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2'
CRYPTOGRAPHY_IMP_ERR = None