acme_certificate: allow to request renewal of a certificate according to ARI (#739)
* Allow to request renewal of a certificate according to ARI in acme_certificate. * Improve docs. * Fix typo and use right object. * Add warning.pull/742/head^2
parent
6d4fc589ae
commit
33d278ad8f
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- "acme_certificate - add ``include_renewal_cert_id`` option to allow requesting renewal of a specific certificate according to the current ACME Renewal Information specification draft (https://github.com/ansible-collections/community.crypto/pull/739)."
|
|
@ -32,6 +32,7 @@ class Order(object):
|
||||||
self.identifiers = []
|
self.identifiers = []
|
||||||
for identifier in data['identifiers']:
|
for identifier in data['identifiers']:
|
||||||
self.identifiers.append((identifier['type'], identifier['value']))
|
self.identifiers.append((identifier['type'], identifier['value']))
|
||||||
|
self.replaces_cert_id = data.get('replaces')
|
||||||
self.finalize_uri = data.get('finalize')
|
self.finalize_uri = data.get('finalize')
|
||||||
self.certificate_uri = data.get('certificate')
|
self.certificate_uri = data.get('certificate')
|
||||||
self.authorization_uris = data['authorizations']
|
self.authorization_uris = data['authorizations']
|
||||||
|
@ -44,6 +45,7 @@ class Order(object):
|
||||||
|
|
||||||
self.status = None
|
self.status = None
|
||||||
self.identifiers = []
|
self.identifiers = []
|
||||||
|
self.replaces_cert_id = None
|
||||||
self.finalize_uri = None
|
self.finalize_uri = None
|
||||||
self.certificate_uri = None
|
self.certificate_uri = None
|
||||||
self.authorization_uris = []
|
self.authorization_uris = []
|
||||||
|
@ -62,7 +64,7 @@ class Order(object):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, client, identifiers):
|
def create(cls, client, identifiers, replaces_cert_id=None):
|
||||||
'''
|
'''
|
||||||
Start a new certificate order (ACME v2 protocol).
|
Start a new certificate order (ACME v2 protocol).
|
||||||
https://tools.ietf.org/html/rfc8555#section-7.4
|
https://tools.ietf.org/html/rfc8555#section-7.4
|
||||||
|
@ -76,6 +78,8 @@ class Order(object):
|
||||||
new_order = {
|
new_order = {
|
||||||
"identifiers": acme_identifiers
|
"identifiers": acme_identifiers
|
||||||
}
|
}
|
||||||
|
if replaces_cert_id is not None:
|
||||||
|
new_order["replaces"] = replaces_cert_id
|
||||||
result, info = client.send_signed_request(
|
result, info = client.send_signed_request(
|
||||||
client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201])
|
client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201])
|
||||||
return cls.from_json(client, result, info['location'])
|
return cls.from_json(client, result, info['location'])
|
||||||
|
|
|
@ -293,6 +293,30 @@ options:
|
||||||
- "The identifier must be of the form
|
- "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)."
|
V(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)."
|
||||||
type: str
|
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.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- never
|
||||||
|
- when_ari_supported
|
||||||
|
- always
|
||||||
|
default: never
|
||||||
|
version_added: 2.20.0
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
|
@ -586,6 +610,7 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.orders impor
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||||
|
compute_cert_id,
|
||||||
pem_to_der,
|
pem_to_der,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -622,6 +647,7 @@ class ACMECertificateClient(object):
|
||||||
self.order_uri = self.data.get('order_uri') if self.data else None
|
self.order_uri = self.data.get('order_uri') if self.data else None
|
||||||
self.all_chains = None
|
self.all_chains = None
|
||||||
self.select_chain_matcher = []
|
self.select_chain_matcher = []
|
||||||
|
self.include_renewal_cert_id = module.params['include_renewal_cert_id']
|
||||||
|
|
||||||
if self.module.params['select_chain']:
|
if self.module.params['select_chain']:
|
||||||
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
|
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
|
||||||
|
@ -679,6 +705,15 @@ class ACMECertificateClient(object):
|
||||||
# stored in self.order_uri by the constructor).
|
# stored in self.order_uri by the constructor).
|
||||||
return self.order_uri is None
|
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):
|
def start_challenges(self):
|
||||||
'''
|
'''
|
||||||
Create new authorizations for all identifiers of the CSR,
|
Create new authorizations for all identifiers of the CSR,
|
||||||
|
@ -693,7 +728,15 @@ class ACMECertificateClient(object):
|
||||||
authz = Authorization.create(self.client, identifier_type, identifier)
|
authz = Authorization.create(self.client, identifier_type, identifier)
|
||||||
self.authorizations[authz.combined_identifier] = authz
|
self.authorizations[authz.combined_identifier] = authz
|
||||||
else:
|
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)
|
||||||
|
self.order = Order.create(self.client, self.identifiers, replaces_cert_id)
|
||||||
self.order_uri = self.order.url
|
self.order_uri = self.order.url
|
||||||
self.order.load_authorizations(self.client)
|
self.order.load_authorizations(self.client)
|
||||||
self.authorizations.update(self.order.authorizations)
|
self.authorizations.update(self.order.authorizations)
|
||||||
|
@ -879,6 +922,7 @@ def main():
|
||||||
subject_key_identifier=dict(type='str'),
|
subject_key_identifier=dict(type='str'),
|
||||||
authority_key_identifier=dict(type='str'),
|
authority_key_identifier=dict(type='str'),
|
||||||
)),
|
)),
|
||||||
|
include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
|
||||||
))
|
))
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec,
|
argument_spec=argument_spec,
|
||||||
|
|
Loading…
Reference in New Issue