acme_certificate: add select_chain option (#60710)

* Add select_alternate_chain option.

* Fix docs.

* Allow to match via subject key identifier and authority key identifier.

* Simplify test.

* Add comments.

* Add tests.

* Fix bugs.

* Also consider main chain when searching for alternatives.

* Bump version_added.

* Rename select_alternate_chain -> select_chain.
This commit is contained in:
Felix Fontein 2019-10-29 08:09:15 +01:00 committed by GitHub
parent 35a412fab7
commit 16d4d2dba9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 422 additions and 27 deletions

View file

@ -28,6 +28,7 @@ import sys
import tempfile
import traceback
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.compat import ipaddress as compat_ipaddress
@ -945,15 +946,17 @@ def set_crypto_backend(module):
try:
cryptography.__version__
except Exception as dummy:
module.fail_json(msg='Cannot find cryptography module!')
module.fail_json(msg=missing_required_lib('cryptography'))
HAS_CURRENT_CRYPTOGRAPHY = True
else:
module.fail_json(msg='Unknown crypto backend "{0}"!'.format(backend))
# Inform about choices
if HAS_CURRENT_CRYPTOGRAPHY:
module.debug('Using cryptography backend (library version {0})'.format(CRYPTOGRAPHY_VERSION))
return 'cryptography'
else:
module.debug('Using OpenSSL binary backend')
return 'openssl'
def process_links(info, callback):
@ -985,7 +988,7 @@ def handle_standard_module_arguments(module, needs_acme_v2=False):
'''
Do standard module setup, argument handling and warning emitting.
'''
set_crypto_backend(module)
backend = set_crypto_backend(module)
if not module.params['validate_certs']:
module.warn(
@ -1008,3 +1011,5 @@ def handle_standard_module_arguments(module, needs_acme_v2=False):
# AnsibleModule() changes the locale, so change it back to C because we rely on time.strptime() when parsing certificate dates.
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
locale.setlocale(locale.LC_ALL, 'C')
return backend

View file

@ -203,13 +203,67 @@ options:
version_added: 2.6
retrieve_all_alternates:
description:
- "When set to C(yes), will retrieve all alternate chains offered by the ACME CA.
- "When set to C(yes), will retrieve all alternate trust chains offered by the ACME CA.
These will not be written to disk, but will be returned together with the main
chain as C(all_chains). See the documentation for the C(all_chains) return
value for details."
type: bool
default: no
version_added: "2.9"
select_chain:
description:
- "Allows to specify criteria by which an (alternate) trust chain can be selected."
- "The list of criteria will be processed one by one until a chain is found
matching a criterium. If such a chain is found, it will be used by the
module instead of the default chain."
- "If a criterium matches multiple chains, the first one matching will be
returned. The order is determined by the ordering of the C(Link) headers
returned by the ACME server and might not be deterministic."
- "Every criterium can consist of multiple different conditions, like I(issuer)
and I(subject). For the criterium to match a chain, all conditions must apply
to the same certificate in the chain."
- "This option can only be used with the C(cryptography) backend."
type: list
version_added: "2.10"
suboptions:
test_certificates:
description:
- "Determines which certificates in the chain will be tested."
- "I(all) tests all certificates in the chain (excluding the leaf, which is
identical in all chains)."
- "I(last) only tests the last certificate in the chain, i.e. the one furthest
away from the leaf. Its issuer is the root certificate of this chain."
type: str
default: all
choices: [last, all]
issuer:
description:
- "Allows to specify parts of the issuer of a certificate in the chain must
have to be selected."
- "If I(issuer) is empty, any certificate will match."
- 'An example value would be C({"commonName": "My Preferred CA Root"}).'
type: dict
subject:
description:
- "Allows to specify parts of the subject of a certificate in the chain must
have to be selected."
- "If I(subject) is empty, any certificate will match."
- 'An example value would be C({"CN": "My Preferred CA Intermediate"})'
type: dict
subject_key_identifier:
description:
- "Checks for the SubjectKeyIdentifier extension. This is an identifier based
on the private key of the intermediate certificate."
- "The identifier must be of the form
C(A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1)."
type: str
authority_key_identifier:
description:
- "Checks for the AuthorityKeyIdentifier extension. This is an identifier based
on the private key of the issuer of the intermediate certificate."
- "The identifier must be of the form
C(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)."
type: str
'''
EXAMPLES = r'''
@ -312,6 +366,33 @@ EXAMPLES = r'''
remaining_days: 60
data: "{{ sample_com_challenge }}"
when: sample_com_challenge is changed
# Alternative second step:
- name: Let the challenge be validated and retrieve the cert and intermediate certificate
acme_certificate:
account_key_src: /etc/pki/cert/private/account.key
account_email: myself@sample.com
src: /etc/pki/cert/csr/sample.com.csr
cert: /etc/httpd/ssl/sample.com.crt
fullchain: /etc/httpd/ssl/sample.com-fullchain.crt
chain: /etc/httpd/ssl/sample.com-intermediate.crt
challenge: tls-alpn-01
remaining_days: 60
data: "{{ sample_com_challenge }}"
# We use Let's Encrypt's ACME v2 endpoint
acme_directory: https://acme-v02.api.letsencrypt.org/directory
acme_version: 2
# The following makes sure that if a chain with /CN=DST Root CA X3 in its issuer is provided
# as an alternative, it will be selected. These are the roots cross-signed by IdenTrust.
# As long as Let's Encrypt provides alternate chains with the cross-signed root(s) when
# switching to their own ISRG Root X1 root, this will use the chain ending with a cross-signed
# root. This chain is more compatible with older TLS clients.
select_chain:
- test_certificates: last
issuer:
CN: DST Root CA X3
O: Digital Signature Trust Co.
when: sample_com_challenge is changed
'''
RETURN = '''
@ -432,16 +513,29 @@ from ansible.module_utils.acme import (
)
import base64
import binascii
import hashlib
import os
import re
import textwrap
import time
import traceback
from datetime import datetime
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_bytes
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes, to_native
from ansible.module_utils.compat import ipaddress as compat_ipaddress
from ansible.module_utils import crypto as crypto_utils
try:
import cryptography
import cryptography.hazmat.backends
import cryptography.x509
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
else:
CRYPTOGRAPHY_FOUND = True
def get_cert_days(module, cert_file):
@ -907,6 +1001,60 @@ class ACMEClient(object):
identifier_type, identifier = type_identifier.split(':', 1)
self._validate_challenges(identifier_type, identifier, auth)
def _chain_matches(self, chain, criterium):
'''
Check whether an alternate chain matches the specified criterium.
'''
if criterium['test_certificates'] == 'last':
chain = chain[-1:]
for cert in chain:
try:
x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography.hazmat.backends.default_backend())
matches = True
if criterium['subject']:
for k, v in crypto_utils.parse_name_field(criterium['subject']):
oid = crypto_utils.cryptography_name_to_oid(k)
value = to_native(v)
found = False
for attribute in x509.subject:
if attribute.oid == oid and value == to_native(attribute.value):
found = True
break
if not found:
matches = False
break
if criterium['issuer']:
for k, v in crypto_utils.parse_name_field(criterium['issuer']):
oid = crypto_utils.cryptography_name_to_oid(k)
value = to_native(v)
found = False
for attribute in x509.issuer:
if attribute.oid == oid and value == to_native(attribute.value):
found = True
break
if not found:
matches = False
break
if criterium['subject_key_identifier']:
try:
ext = x509.extensions.get_extension_for_class(cryptography.x509.SubjectKeyIdentifier)
if criterium['subject_key_identifier'] != ext.value.digest:
matches = False
except cryptography.x509.ExtensionNotFound:
matches = False
if criterium['authority_key_identifier']:
try:
ext = x509.extensions.get_extension_for_class(cryptography.x509.AuthorityKeyIdentifier)
if criterium['authority_key_identifier'] != ext.value.key_identifier:
matches = False
except cryptography.x509.ExtensionNotFound:
matches = False
if matches:
return True
except Exception as e:
self.module.warn('Error while loading certificate {0}: {1}'.format(cert, e))
return False
def get_certificate(self):
'''
Request a new certificate and write it to the destination file.
@ -927,7 +1075,8 @@ class ACMEClient(object):
else:
cert_uri = self._finalize_cert()
cert = self._download_cert(cert_uri)
if self.module.params['retrieve_all_alternates']:
if self.module.params['retrieve_all_alternates'] or self.module.params['select_chain']:
# Retrieve alternate chains
alternate_chains = []
for alternate in cert['alternates']:
try:
@ -936,18 +1085,46 @@ class ACMEClient(object):
self.module.warn('Error while downloading alternative certificate {0}: {1}'.format(alternate, e))
continue
alternate_chains.append(alt_cert)
self.all_chains = []
def _append_all_chains(cert_data):
self.all_chains.append(dict(
cert=cert_data['cert'].encode('utf8'),
chain=("\n".join(cert_data.get('chain', []))).encode('utf8'),
full_chain=(cert_data['cert'] + "\n".join(cert_data.get('chain', []))).encode('utf8'),
))
# Prepare return value for all alternate chains
if self.module.params['retrieve_all_alternates']:
self.all_chains = []
_append_all_chains(cert)
for alt_chain in alternate_chains:
_append_all_chains(alt_chain)
def _append_all_chains(cert_data):
self.all_chains.append(dict(
cert=cert_data['cert'].encode('utf8'),
chain=("\n".join(cert_data.get('chain', []))).encode('utf8'),
full_chain=(cert_data['cert'] + "\n".join(cert_data.get('chain', []))).encode('utf8'),
))
_append_all_chains(cert)
for alt_chain in alternate_chains:
_append_all_chains(alt_chain)
# Try to select alternate chain depending on criteria
if self.module.params['select_chain']:
matching_chain = None
all_chains = [cert] + alternate_chains
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
for v in ('subject_key_identifier', 'authority_key_identifier'):
if criterium[v]:
try:
criterium[v] = binascii.unhexlify(criterium[v].replace(':', ''))
except Exception:
self.module.warn('Criterium {0} in select_chain has invalid {1} value. '
'Ignoring criterium.'.format(criterium_idx, v))
continue
for alt_chain in all_chains:
if self._chain_matches(alt_chain.get('chain', []), criterium):
self.module.debug('Found matching chain for criterium {0}'.format(criterium_idx))
matching_chain = alt_chain
break
if matching_chain:
break
if matching_chain:
cert.update(matching_chain)
else:
self.module.debug('Found no matching alternative chain')
if cert['cert'] is not None:
pem_cert = cert['cert']
@ -1009,6 +1186,13 @@ def main():
deactivate_authzs=dict(type='bool', default=False),
force=dict(type='bool', default=False),
retrieve_all_alternates=dict(type='bool', default=False),
select_chain=dict(type='list', elements='dict', options=dict(
test_certificates=dict(type='str', default='all', choices=['last', 'all']),
issuer=dict(type='dict'),
subject=dict(type='dict'),
subject_key_identifier=dict(type='str'),
authority_key_identifier=dict(type='str'),
)),
))
module = AnsibleModule(
argument_spec=argument_spec,
@ -1021,7 +1205,12 @@ def main():
),
supports_check_mode=True,
)
handle_standard_module_arguments(module)
backend = handle_standard_module_arguments(module)
if module.params['select_chain']:
if backend != 'cryptography':
module.fail_json(msg="The 'select_chain' can only be used with the 'cryptography' backend.")
elif not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography'))
try:
if module.params.get('dest'):

View file

@ -58,9 +58,14 @@
terms_agreed: yes
account_email: "example@example.org"
retrieve_all_alternates: yes
acme_expected_root_number: 1
select_chain:
- test_certificates: last
issuer: "{{ acme_roots[1].subject }}"
- name: Store obtain results for cert 1
set_fact:
cert_1_obtain_results: "{{ certificate_obtain_result }}"
cert_1_alternate: "{{ 1 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 2
include_tasks: obtain-cert.yml
vars:
@ -77,9 +82,21 @@
remaining_days: 10
terms_agreed: no
account_email: ""
acme_expected_root_number: 0
retrieve_all_alternates: yes
select_chain:
# All intermediates have the same subject, so always the first
# chain will be found, and we need a second condition to make sure
# that the first condition actually works. (The second condition
# has been tested above.)
- test_certificates: all
subject: "{{ acme_intermediates[0].subject }}"
- test_certificates: all
issuer: "{{ acme_roots[2].subject }}"
- name: Store obtain results for cert 2
set_fact:
cert_2_obtain_results: "{{ certificate_obtain_result }}"
cert_2_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 3
include_tasks: obtain-cert.yml
vars:
@ -96,6 +113,15 @@
remaining_days: 10
terms_agreed: no
account_email: ""
acme_expected_root_number: 0
retrieve_all_alternates: yes
select_chain:
- test_certificates: last
subject: "{{ acme_roots[1].subject }}"
- name: Store obtain results for cert 3
set_fact:
cert_3_obtain_results: "{{ certificate_obtain_result }}"
cert_3_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 4
include_tasks: obtain-cert.yml
vars:
@ -113,6 +139,16 @@
remaining_days: 10
terms_agreed: no
account_email: ""
acme_expected_root_number: 2
select_chain:
- test_certificates: last
issuer: "{{ acme_roots[2].subject }}"
- test_certificates: last
issuer: "{{ acme_roots[1].subject }}"
- name: Store obtain results for cert 4
set_fact:
cert_4_obtain_results: "{{ certificate_obtain_result }}"
cert_4_alternate: "{{ 2 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 5
include_tasks: obtain-cert.yml
vars:
@ -129,6 +165,10 @@
remaining_days: 10
terms_agreed: no
account_email: ""
- name: Store obtain results for cert 5a
set_fact:
cert_5a_obtain_results: "{{ certificate_obtain_result }}"
cert_5_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 5 (should not, since already there and valid for more than 10 days)
include_tasks: obtain-cert.yml
vars:
@ -145,7 +185,8 @@
remaining_days: 10
terms_agreed: no
account_email: ""
- set_fact:
- name: Store obtain results for cert 5b
set_fact:
cert_5_recreate_1: "{{ challenge_data is changed }}"
- name: Obtain cert 5 (should again by less days)
include_tasks: obtain-cert.yml
@ -163,8 +204,10 @@
remaining_days: 1000
terms_agreed: no
account_email: ""
- set_fact:
- name: Store obtain results for cert 5c
set_fact:
cert_5_recreate_2: "{{ challenge_data is changed }}"
cert_5c_obtain_results: "{{ certificate_obtain_result }}"
- name: Obtain cert 5 (should again by force)
include_tasks: obtain-cert.yml
vars:
@ -181,8 +224,10 @@
remaining_days: 10
terms_agreed: no
account_email: ""
- set_fact:
- name: Store obtain results for cert 5d
set_fact:
cert_5_recreate_3: "{{ challenge_data is changed }}"
cert_5d_obtain_results: "{{ certificate_obtain_result }}"
- name: Obtain cert 6
include_tasks: obtain-cert.yml
vars:
@ -200,6 +245,20 @@
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
acme_expected_root_number: 0
select_chain:
# All intermediates have the same subject key identifier, so always
# the first chain will be found, and we need a second condition to
# make sure that the first condition actually works. (The second
# condition has been tested above.)
- test_certificates: last
subject_key_identifier: "{{ acme_intermediates[0].subject_key_identifier }}"
- test_certificates: last
issuer: "{{ acme_roots[1].subject }}"
- name: Store obtain results for cert 6
set_fact:
cert_6_obtain_results: "{{ certificate_obtain_result }}"
cert_6_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 7
include_tasks: obtain-cert.yml
vars:
@ -219,6 +278,14 @@
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
acme_expected_root_number: 2
select_chain:
- test_certificates: last
authority_key_identifier: "{{ acme_roots[2].subject_key_identifier }}"
- name: Store obtain results for cert 7
set_fact:
cert_7_obtain_results: "{{ certificate_obtain_result }}"
cert_7_alternate: "{{ 2 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 8
include_tasks: obtain-cert.yml
vars:
@ -240,6 +307,10 @@
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
- name: Store obtain results for cert 8
set_fact:
cert_8_obtain_results: "{{ certificate_obtain_result }}"
cert_8_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
## DISSECT CERTIFICATES #######################################################################
# Make sure certificates are valid. Root certificate for Pebble equals the chain certificate.
- name: Verifying cert 1
@ -299,6 +370,39 @@
- name: Dumping cert 8
command: openssl x509 -in "{{ output_dir }}/cert-8.pem" -noout -text
register: cert_8_text
# Dump certificate info
- name: Dumping cert 1
openssl_certificate_info:
path: "{{ output_dir }}/cert-1.pem"
register: cert_1_info
- name: Dumping cert 2
openssl_certificate_info:
path: "{{ output_dir }}/cert-2.pem"
register: cert_2_info
- name: Dumping cert 3
openssl_certificate_info:
path: "{{ output_dir }}/cert-3.pem"
register: cert_3_info
- name: Dumping cert 4
openssl_certificate_info:
path: "{{ output_dir }}/cert-4.pem"
register: cert_4_info
- name: Dumping cert 5
openssl_certificate_info:
path: "{{ output_dir }}/cert-5.pem"
register: cert_5_info
- name: Dumping cert 6
openssl_certificate_info:
path: "{{ output_dir }}/cert-6.pem"
register: cert_6_info
- name: Dumping cert 7
openssl_certificate_info:
path: "{{ output_dir }}/cert-7.pem"
register: cert_7_info
- name: Dumping cert 8
openssl_certificate_info:
path: "{{ output_dir }}/cert-8.pem"
register: cert_8_info
## GET ACCOUNT ORDERS #########################################################################
- name: Don't retrieve orders
acme_account_info:

View file

@ -1,4 +1,75 @@
---
- block:
- name: Obtain root and intermediate certificates
get_url:
url: "http://{{ acme_host }}:5000/{{ item.0 }}-certificate-for-ca/{{ item.1 }}"
dest: "{{ output_dir }}/acme-{{ item.0 }}-{{ item.1 }}.pem"
loop: "{{ query('nested', types, root_numbers) }}"
- name: Analyze root certificates
openssl_certificate_info:
path: "{{ output_dir }}/acme-root-{{ item }}.pem"
loop: "{{ root_numbers }}"
register: acme_roots
- name: Analyze intermediate certificates
openssl_certificate_info:
path: "{{ output_dir }}/acme-intermediate-{{ item }}.pem"
loop: "{{ root_numbers }}"
register: acme_intermediates
- set_fact:
x__: "{{ item | dict2items | selectattr('key', 'in', interesting_keys) | list | items2dict }}"
y__: "{{ lookup('file', output_dir ~ '/acme-root-' ~ item.item ~ '.pem', rstrip=False) }}"
loop: "{{ acme_roots.results }}"
register: acme_roots_tmp
- set_fact:
x__: "{{ item | dict2items | selectattr('key', 'in', interesting_keys) | list | items2dict }}"
y__: "{{ lookup('file', output_dir ~ '/acme-intermediate-' ~ item.item ~ '.pem', rstrip=False) }}"
loop: "{{ acme_intermediates.results }}"
register: acme_intermediates_tmp
- set_fact:
acme_roots: "{{ acme_roots_tmp.results | map(attribute='ansible_facts.x__') | list }}"
acme_root_certs: "{{ acme_roots_tmp.results | map(attribute='ansible_facts.y__') | list }}"
acme_intermediates: "{{ acme_intermediates_tmp.results | map(attribute='ansible_facts.x__') | list }}"
acme_intermediate_certs: "{{ acme_intermediates_tmp.results | map(attribute='ansible_facts.y__') | list }}"
vars:
types:
- root
- intermediate
root_numbers:
# The number 3 comes from here: https://github.com/ansible/acme-test-container/blob/master/run.sh#L12
- 0
- 1
- 2
- 3
interesting_keys:
- authority_key_identifier
- subject_key_identifier
- issuer
- subject
#- serial_number
#- public_key_fingerprints
- name: ACME root certificate info
debug:
var: acme_roots
#- name: ACME root certificates as PEM
# debug:
# var: acme_root_certs
- name: ACME intermediate certificate info
debug:
var: acme_intermediates
#- name: ACME intermediate certificates as PEM
# debug:
# var: acme_intermediate_certs
- block:
- name: Running tests with OpenSSL backend
include_tasks: impl.yml

View file

@ -11,10 +11,13 @@
assert:
that:
- "'all_chains' in cert_1_obtain_results"
- "'chain' in cert_1_obtain_results.all_chains[0]"
- "'full_chain' in cert_1_obtain_results.all_chains[0]"
- "lookup('file', output_dir ~ '/cert-1-chain.pem', rstrip=False) == cert_1_obtain_results.all_chains[0].chain"
- "lookup('file', output_dir ~ '/cert-1-fullchain.pem', rstrip=False) == cert_1_obtain_results.all_chains[0].full_chain"
- "cert_1_obtain_results.all_chains | length > 1"
- "'cert' in cert_1_obtain_results.all_chains[cert_1_alternate | int]"
- "'chain' in cert_1_obtain_results.all_chains[cert_1_alternate | int]"
- "'full_chain' in cert_1_obtain_results.all_chains[cert_1_alternate | int]"
- "lookup('file', output_dir ~ '/cert-1.pem', rstrip=False) == cert_1_obtain_results.all_chains[cert_1_alternate | int].cert"
- "lookup('file', output_dir ~ '/cert-1-chain.pem', rstrip=False) == cert_1_obtain_results.all_chains[cert_1_alternate | int].chain"
- "lookup('file', output_dir ~ '/cert-1-fullchain.pem', rstrip=False) == cert_1_obtain_results.all_chains[cert_1_alternate | int].full_chain"
- name: Check that certificate 2 is valid
assert:
@ -25,10 +28,17 @@
that:
- "'DNS:*.example.com' in cert_2_text.stdout"
- "'DNS:example.com' in cert_2_text.stdout"
- name: Check that certificate 2 retrieval did not get all chains
- name: Check that certificate 1 retrieval got all chains
assert:
that:
- "'all_chains' not in cert_2_obtain_results"
- "'all_chains' in cert_2_obtain_results"
- "cert_2_obtain_results.all_chains | length > 1"
- "'cert' in cert_2_obtain_results.all_chains[cert_2_alternate | int]"
- "'chain' in cert_2_obtain_results.all_chains[cert_2_alternate | int]"
- "'full_chain' in cert_2_obtain_results.all_chains[cert_2_alternate | int]"
- "lookup('file', output_dir ~ '/cert-2.pem', rstrip=False) == cert_2_obtain_results.all_chains[cert_2_alternate | int].cert"
- "lookup('file', output_dir ~ '/cert-2-chain.pem', rstrip=False) == cert_2_obtain_results.all_chains[cert_2_alternate | int].chain"
- "lookup('file', output_dir ~ '/cert-2-fullchain.pem', rstrip=False) == cert_2_obtain_results.all_chains[cert_2_alternate | int].full_chain"
- name: Check that certificate 3 is valid
assert:
@ -40,6 +50,17 @@
- "'DNS:*.example.com' in cert_3_text.stdout"
- "'DNS:example.org' in cert_3_text.stdout"
- "'DNS:t1.example.com' in cert_3_text.stdout"
- name: Check that certificate 1 retrieval got all chains
assert:
that:
- "'all_chains' in cert_3_obtain_results"
- "cert_3_obtain_results.all_chains | length > 1"
- "'cert' in cert_3_obtain_results.all_chains[cert_3_alternate | int]"
- "'chain' in cert_3_obtain_results.all_chains[cert_3_alternate | int]"
- "'full_chain' in cert_3_obtain_results.all_chains[cert_3_alternate | int]"
- "lookup('file', output_dir ~ '/cert-3.pem', rstrip=False) == cert_3_obtain_results.all_chains[cert_3_alternate | int].cert"
- "lookup('file', output_dir ~ '/cert-3-chain.pem', rstrip=False) == cert_3_obtain_results.all_chains[cert_3_alternate | int].chain"
- "lookup('file', output_dir ~ '/cert-3-fullchain.pem', rstrip=False) == cert_3_obtain_results.all_chains[cert_3_alternate | int].full_chain"
- name: Check that certificate 4 is valid
assert:
@ -53,6 +74,10 @@
- "'DNS:test.t2.example.com' in cert_4_text.stdout"
- "'DNS:example.org' in cert_4_text.stdout"
- "'DNS:test.example.org' in cert_4_text.stdout"
- name: Check that certificate 4 retrieval did not get all chains
assert:
that:
- "'all_chains' not in cert_4_obtain_results"
- name: Check that certificate 5 is valid
assert:

View file

@ -112,6 +112,7 @@
account_email: "{{ account_email }}"
data: "{{ challenge_data }}"
retrieve_all_alternates: "{{ retrieve_all_alternates | default(omit) }}"
select_chain: "{{ select_chain | default(omit) if select_crypto_backend == 'cryptography' else omit }}"
register: certificate_obtain_result
when: challenge_data is changed
- name: ({{ certgen_title }}) Deleting HTTP challenges
@ -134,6 +135,6 @@
when: "challenge_data is changed and challenge == 'tls-alpn-01'"
- name: ({{ certgen_title }}) Get root certificate
get_url:
url: "http://{{ acme_host }}:5000/root-certificate-for-ca/0"
url: "http://{{ acme_host }}:5000/root-certificate-for-ca/{{ acme_expected_root_number | default(0) if select_crypto_backend == 'cryptography' else 0 }}"
dest: "{{ output_dir }}/{{ certificate_name }}-root.pem"
###############################################################################################