diff --git a/plugins/modules/acm_certificate.py b/plugins/modules/acm_certificate.py
new file mode 100644
index 00000000000..6b48579d5bc
--- /dev/null
+++ b/plugins/modules/acm_certificate.py
@@ -0,0 +1,571 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+# This module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this software. If not, see .
+#
+# Author:
+# - Matthew Davis
+# on behalf of Telstra Corporation Limited
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+---
+module: acm_certificate
+short_description: Upload and delete certificates in the AWS Certificate Manager service
+version_added: 1.0.0
+description:
+ - >
+ Import and delete certificates in Amazon Web Service's Certificate
+ Manager (AWS ACM).
+ - >
+ This module does not currently interact with AWS-provided certificates.
+ It currently only manages certificates provided to AWS by the user.
+ - The ACM API allows users to upload multiple certificates for the same domain
+ name, and even multiple identical certificates. This module attempts to
+ restrict such freedoms, to be idempotent, as per the Ansible philosophy.
+ It does this through applying AWS resource "Name" tags to ACM certificates.
+ - >
+ When I(state=present),
+ if there is one certificate in ACM
+ with a C(Name) tag equal to the I(name_tag) parameter,
+ and an identical body and chain,
+ this task will succeed without effect.
+ - >
+ When I(state=present),
+ if there is one certificate in ACM
+ a I(Name) tag equal to the I(name_tag) parameter,
+ and a different body,
+ this task will overwrite that certificate.
+ - >
+ When I(state=present),
+ if there are multiple certificates in ACM
+ with a I(Name) tag equal to the I(name_tag) parameter,
+ this task will fail.
+ - >
+ When I(state=absent) and I(certificate_arn) is defined,
+ this module will delete the ACM resource with that ARN if it exists in this
+ region, and succeed without effect if it doesn't exist.
+ - >
+ When I(state=absent) and I(domain_name) is defined, this module will delete
+ all ACM resources in this AWS region with a corresponding domain name.
+ If there are none, it will succeed without effect.
+ - >
+ When I(state=absent) and I(certificate_arn) is not defined,
+ and I(domain_name) is not defined, this module will delete all ACM resources
+ in this AWS region with a corresponding I(Name) tag.
+ If there are none, it will succeed without effect.
+ - >
+ Note that this may not work properly with keys of size 4096 bits, due to a
+ limitation of the ACM API.
+ - Prior to release 5.0.0 this module was called C(community.aws.aws_acm).
+ The usage did not change.
+options:
+ certificate:
+ description:
+ - The body of the PEM encoded public certificate.
+ - Required when I(state) is not C(absent) and the certificate does not exist.
+ - >
+ If your certificate is in a file,
+ use C(lookup('file', 'path/to/cert.pem')).
+ type: str
+ certificate_arn:
+ description:
+ - The ARN of a certificate in ACM to modify or delete.
+ - >
+ If I(state=present), the certificate with the specified ARN can be updated.
+ For example, this can be used to add/remove tags to an existing certificate.
+ - >
+ If I(state=absent), you must provide one of
+ I(certificate_arn), I(domain_name) or I(name_tag).
+ - >
+ If I(state=absent) and no resource exists with this ARN in this region,
+ the task will succeed with no effect.
+ - >
+ If I(state=absent) and the corresponding resource exists in a different
+ region, this task may report success without deleting that resource.
+ type: str
+ aliases: [arn]
+ certificate_chain:
+ description:
+ - The body of the PEM encoded chain for your certificate.
+ - >
+ If your certificate chain is in a file,
+ use C(lookup('file', 'path/to/chain.pem')).
+ - Ignored when I(state=absent)
+ type: str
+ domain_name:
+ description:
+ - The domain name of the certificate.
+ - >
+ If I(state=absent) and I(domain_name) is specified,
+ this task will delete all ACM certificates with this domain.
+ - >
+ Exactly one of I(domain_name), I(name_tag) and I(certificate_arn)
+ must be provided.
+ - >
+ If I(state=present) this must not be specified.
+ (Since the domain name is encoded within the public certificate's body.)
+ type: str
+ aliases: [domain]
+ name_tag:
+ description:
+ - >
+ The unique identifier for tagging resources using AWS tags,
+ with key I(Name).
+ - This can be any set of characters accepted by AWS for tag values.
+ - >
+ This is to ensure Ansible can treat certificates idempotently,
+ even though the ACM API allows duplicate certificates.
+ - If I(state=preset), this must be specified.
+ - >
+ If I(state=absent) and I(name_tag) is specified,
+ this task will delete all ACM certificates with this Name tag.
+ - >
+ If I(state=absent), you must provide exactly one of
+ I(certificate_arn), I(domain_name) or I(name_tag).
+ - >
+ If both I(name_tag) and the 'Name' tag in I(tags) are set,
+ the values must be the same.
+ - >
+ If the 'Name' tag in I(tags) is not set and I(name_tag) is set,
+ the I(name_tag) value is copied to I(tags).
+ type: str
+ aliases: [name]
+ private_key:
+ description:
+ - The body of the PEM encoded private key.
+ - Required when I(state=present) and the certificate does not exist.
+ - Ignored when I(state=absent).
+ - >
+ If your private key is in a file,
+ use C(lookup('file', 'path/to/key.pem')).
+ type: str
+ state:
+ description:
+ - >
+ If I(state=present), the specified public certificate and private key
+ will be uploaded, with I(Name) tag equal to I(name_tag).
+ - >
+ If I(state=absent), any certificates in this region
+ with a corresponding I(domain_name), I(name_tag) or I(certificate_arn)
+ will be deleted.
+ choices: [present, absent]
+ default: present
+ type: str
+
+notes:
+ - Support for I(tags) and I(purge_tags) was added in release 3.2.0
+author:
+ - Matthew Davis (@matt-telstra) on behalf of Telstra Corporation Limited
+extends_documentation_fragment:
+ - amazon.aws.aws
+ - amazon.aws.ec2
+ - amazon.aws.tags.deprecated_purge
+'''
+
+EXAMPLES = '''
+
+- name: upload a self-signed certificate
+ community.aws.aws_acm:
+ certificate: "{{ lookup('file', 'cert.pem' ) }}"
+ privateKey: "{{ lookup('file', 'key.pem' ) }}"
+ name_tag: my_cert # to be applied through an AWS tag as "Name":"my_cert"
+ region: ap-southeast-2 # AWS region
+
+- name: create/update a certificate with a chain
+ community.aws.aws_acm:
+ certificate: "{{ lookup('file', 'cert.pem' ) }}"
+ private_key: "{{ lookup('file', 'key.pem' ) }}"
+ name_tag: my_cert
+ certificate_chain: "{{ lookup('file', 'chain.pem' ) }}"
+ state: present
+ region: ap-southeast-2
+ register: cert_create
+
+- name: print ARN of cert we just created
+ ansible.builtin.debug:
+ var: cert_create.certificate.arn
+
+- name: delete the cert we just created
+ community.aws.aws_acm:
+ name_tag: my_cert
+ state: absent
+ region: ap-southeast-2
+
+- name: delete a certificate with a particular ARN
+ community.aws.aws_acm:
+ certificate_arn: "arn:aws:acm:ap-southeast-2:123456789012:certificate/01234567-abcd-abcd-abcd-012345678901"
+ state: absent
+ region: ap-southeast-2
+
+- name: delete all certificates with a particular domain name
+ community.aws.aws_acm:
+ domain_name: acm.ansible.com
+ state: absent
+ region: ap-southeast-2
+
+- name: add tags to an existing certificate with a particular ARN
+ community.aws.aws_acm:
+ certificate_arn: "arn:aws:acm:ap-southeast-2:123456789012:certificate/01234567-abcd-abcd-abcd-012345678901"
+ tags:
+ Name: my_certificate
+ Application: search
+ Environment: development
+ purge_tags: true
+'''
+
+RETURN = '''
+certificate:
+ description: Information about the certificate which was uploaded
+ type: complex
+ returned: when I(state=present)
+ contains:
+ arn:
+ description: The ARN of the certificate in ACM
+ type: str
+ returned: when I(state=present) and not in check mode
+ sample: "arn:aws:acm:ap-southeast-2:123456789012:certificate/01234567-abcd-abcd-abcd-012345678901"
+ domain_name:
+ description: The domain name encoded within the public certificate
+ type: str
+ returned: when I(state=present)
+ sample: acm.ansible.com
+arns:
+ description: A list of the ARNs of the certificates in ACM which were deleted
+ type: list
+ elements: str
+ returned: when I(state=absent)
+ sample:
+ - "arn:aws:acm:ap-southeast-2:123456789012:certificate/01234567-abcd-abcd-abcd-012345678901"
+'''
+
+
+import base64
+from copy import deepcopy
+import re # regex library
+
+try:
+ import botocore
+except ImportError:
+ pass # handled by AnsibleAWSModule
+
+from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
+from ansible_collections.amazon.aws.plugins.module_utils.acm import ACMServiceManager
+from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_aws_tags
+from ansible_collections.amazon.aws.plugins.module_utils.ec2 import (
+ boto3_tag_list_to_ansible_dict,
+ ansible_dict_to_boto3_tag_list,
+)
+from ansible.module_utils._text import to_text
+
+
+def ensure_tags(client, module, resource_arn, existing_tags, tags, purge_tags):
+ if tags is None:
+ return (False, existing_tags)
+
+ tags_to_add, tags_to_remove = compare_aws_tags(existing_tags, tags, purge_tags)
+ changed = bool(tags_to_add or tags_to_remove)
+ if tags_to_add and not module.check_mode:
+ try:
+ client.add_tags_to_certificate(
+ CertificateArn=resource_arn,
+ Tags=ansible_dict_to_boto3_tag_list(tags_to_add),
+ )
+ except (
+ botocore.exceptions.ClientError,
+ botocore.exceptions.BotoCoreError,
+ ) as e:
+ module.fail_json_aws(
+ e, "Couldn't add tags to certificate {0}".format(resource_arn)
+ )
+ if tags_to_remove and not module.check_mode:
+ # remove_tags_from_certificate wants a list of key, value pairs, not a list of keys.
+ tags_list = [{'Key': key, 'Value': existing_tags.get(key)} for key in tags_to_remove]
+ try:
+ client.remove_tags_from_certificate(
+ CertificateArn=resource_arn,
+ Tags=tags_list,
+ )
+ except (
+ botocore.exceptions.ClientError,
+ botocore.exceptions.BotoCoreError,
+ ) as e:
+ module.fail_json_aws(
+ e, "Couldn't remove tags from certificate {0}".format(resource_arn)
+ )
+ new_tags = deepcopy(existing_tags)
+ for key, value in tags_to_add.items():
+ new_tags[key] = value
+ for key in tags_to_remove:
+ new_tags.pop(key, None)
+ return (changed, new_tags)
+
+
+# Takes in two text arguments
+# Each a PEM encoded certificate
+# Or a chain of PEM encoded certificates
+# May include some lines between each chain in the cert, e.g. "Subject: ..."
+# Returns True iff the chains/certs are functionally identical (including chain order)
+def chain_compare(module, a, b):
+
+ chain_a_pem = pem_chain_split(module, a)
+ chain_b_pem = pem_chain_split(module, b)
+
+ if len(chain_a_pem) != len(chain_b_pem):
+ return False
+
+ # Chain length is the same
+ for (ca, cb) in zip(chain_a_pem, chain_b_pem):
+ der_a = PEM_body_to_DER(module, ca)
+ der_b = PEM_body_to_DER(module, cb)
+ if der_a != der_b:
+ return False
+
+ return True
+
+
+# Takes in PEM encoded data with no headers
+# returns equivilent DER as byte array
+def PEM_body_to_DER(module, pem):
+ try:
+ der = base64.b64decode(to_text(pem))
+ except (ValueError, TypeError) as e:
+ module.fail_json_aws(e, msg="Unable to decode certificate chain")
+ return der
+
+
+# Store this globally to avoid repeated recompilation
+pem_chain_split_regex = re.compile(r"------?BEGIN [A-Z0-9. ]*CERTIFICATE------?([a-zA-Z0-9\+\/=\s]+)------?END [A-Z0-9. ]*CERTIFICATE------?")
+
+
+# Use regex to split up a chain or single cert into an array of base64 encoded data
+# Using "-----BEGIN CERTIFICATE-----" and "----END CERTIFICATE----"
+# Noting that some chains have non-pem data in between each cert
+# This function returns only what's between the headers, excluding the headers
+def pem_chain_split(module, pem):
+
+ pem_arr = re.findall(pem_chain_split_regex, to_text(pem))
+
+ if len(pem_arr) == 0:
+ # This happens if the regex doesn't match at all
+ module.fail_json(msg="Unable to split certificate chain. Possibly zero-length chain?")
+
+ return pem_arr
+
+
+def update_imported_certificate(client, module, acm, old_cert, desired_tags):
+ """
+ Update the existing certificate that was previously imported in ACM.
+ """
+ module.debug("Existing certificate found in ACM")
+ if ('tags' not in old_cert) or ('Name' not in old_cert['tags']):
+ # shouldn't happen
+ module.fail_json(msg="Internal error, unsure which certificate to update", certificate=old_cert)
+ if module.params.get('name_tag') is not None and (old_cert['tags']['Name'] != module.params.get('name_tag')):
+ # This could happen if the user identified the certificate using 'certificate_arn' or 'domain_name',
+ # and the 'Name' tag in the AWS API does not match the ansible 'name_tag'.
+ module.fail_json(msg="Internal error, Name tag does not match", certificate=old_cert)
+ if 'certificate' not in old_cert:
+ # shouldn't happen
+ module.fail_json(msg="Internal error, unsure what the existing cert in ACM is", certificate=old_cert)
+
+ cert_arn = None
+ # Are the existing certificate in ACM and the local certificate the same?
+ same = True
+ if module.params.get('certificate') is not None:
+ same &= chain_compare(module, old_cert['certificate'], module.params['certificate'])
+ if module.params['certificate_chain']:
+ # Need to test this
+ # not sure if Amazon appends the cert itself to the chain when self-signed
+ same &= chain_compare(module, old_cert['certificate_chain'], module.params['certificate_chain'])
+ else:
+ # When there is no chain with a cert
+ # it seems Amazon returns the cert itself as the chain
+ same &= chain_compare(module, old_cert['certificate_chain'], module.params['certificate'])
+
+ if same:
+ module.debug("Existing certificate in ACM is the same")
+ cert_arn = old_cert['certificate_arn']
+ changed = False
+ else:
+ absent_args = ['certificate', 'name_tag', 'private_key']
+ if sum([(module.params[a] is not None) for a in absent_args]) < 3:
+ module.fail_json(msg="When importing a certificate, all of 'name_tag', 'certificate' and 'private_key' must be specified")
+ module.debug("Existing certificate in ACM is different, overwriting")
+ changed = True
+ if module.check_mode:
+ cert_arn = old_cert['certificate_arn']
+ # note: returned domain will be the domain of the previous cert
+ else:
+ # update cert in ACM
+ cert_arn = acm.import_certificate(
+ client,
+ module,
+ certificate=module.params['certificate'],
+ private_key=module.params['private_key'],
+ certificate_chain=module.params['certificate_chain'],
+ arn=old_cert['certificate_arn'],
+ tags=desired_tags,
+ )
+ return (changed, cert_arn)
+
+
+def import_certificate(client, module, acm, desired_tags):
+ """
+ Import a certificate to ACM.
+ """
+ # Validate argument requirements
+ absent_args = ['certificate', 'name_tag', 'private_key']
+ cert_arn = None
+ if sum([(module.params[a] is not None) for a in absent_args]) < 3:
+ module.fail_json(msg="When importing a new certificate, all of 'name_tag', 'certificate' and 'private_key' must be specified")
+ module.debug("No certificate in ACM. Creating new one.")
+ changed = True
+ if module.check_mode:
+ domain = 'example.com'
+ module.exit_json(certificate=dict(domain_name=domain), changed=True)
+ else:
+ cert_arn = acm.import_certificate(
+ client,
+ module,
+ certificate=module.params['certificate'],
+ private_key=module.params['private_key'],
+ certificate_chain=module.params['certificate_chain'],
+ tags=desired_tags,
+ )
+ return (changed, cert_arn)
+
+
+def ensure_certificates_present(client, module, acm, certificates, desired_tags, filter_tags):
+ cert_arn = None
+ changed = False
+ if len(certificates) > 1:
+ msg = "More than one certificate with Name=%s exists in ACM in this region" % module.params['name_tag']
+ module.fail_json(msg=msg, certificates=certificates)
+ elif len(certificates) == 1:
+ # Update existing certificate that was previously imported to ACM.
+ (changed, cert_arn) = update_imported_certificate(client, module, acm, certificates[0], desired_tags)
+ else: # len(certificates) == 0
+ # Import new certificate to ACM.
+ (changed, cert_arn) = import_certificate(client, module, acm, desired_tags)
+
+ # Add/remove tags to/from certificate
+ try:
+ existing_tags = boto3_tag_list_to_ansible_dict(client.list_tags_for_certificate(CertificateArn=cert_arn)['Tags'])
+ except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+ module.fail_json_aws(e, "Couldn't get tags for certificate")
+
+ purge_tags = module.params.get('purge_tags')
+ (c, new_tags) = ensure_tags(client, module, cert_arn, existing_tags, desired_tags, purge_tags)
+ changed |= c
+ domain = acm.get_domain_of_cert(client=client, module=module, arn=cert_arn)
+ module.exit_json(certificate=dict(domain_name=domain, arn=cert_arn, tags=new_tags), changed=changed)
+
+
+def ensure_certificates_absent(client, module, acm, certificates):
+ for cert in certificates:
+ if not module.check_mode:
+ acm.delete_certificate(client, module, cert['certificate_arn'])
+ module.exit_json(arns=[cert['certificate_arn'] for cert in certificates], changed=(len(certificates) > 0))
+
+
+def main():
+ argument_spec = dict(
+ certificate=dict(),
+ certificate_arn=dict(aliases=['arn']),
+ certificate_chain=dict(),
+ domain_name=dict(aliases=['domain']),
+ name_tag=dict(aliases=['name']),
+ private_key=dict(no_log=True),
+ tags=dict(type='dict', aliases=['resource_tags']),
+ purge_tags=dict(type='bool'),
+ state=dict(default='present', choices=['present', 'absent']),
+ )
+ module = AnsibleAWSModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+ acm = ACMServiceManager(module)
+
+ if module.params.get('purge_tags') is None:
+ module.deprecate(
+ 'The purge_tags parameter currently defaults to False.'
+ ' For consistency across the collection, this default value'
+ ' will change to True in release 5.0.0.',
+ version='5.0.0', collection_name='community.aws')
+ module.params['purge_tags'] = False
+
+ # Check argument requirements
+ if module.params['state'] == 'present':
+ # at least one of these should be specified.
+ absent_args = ['certificate_arn', 'domain_name', 'name_tag']
+ if sum([(module.params[a] is not None) for a in absent_args]) < 1:
+ for a in absent_args:
+ module.debug("%s is %s" % (a, module.params[a]))
+ module.fail_json(msg="If 'state' is specified as 'present' then at least one of 'name_tag', 'certificate_arn' or 'domain_name' must be specified")
+ else: # absent
+ # exactly one of these should be specified
+ absent_args = ['certificate_arn', 'domain_name', 'name_tag']
+ if sum([(module.params[a] is not None) for a in absent_args]) != 1:
+ for a in absent_args:
+ module.debug("%s is %s" % (a, module.params[a]))
+ module.fail_json(msg="If 'state' is specified as 'absent' then exactly one of 'name_tag', 'certificate_arn' or 'domain_name' must be specified")
+
+ filter_tags = None
+ desired_tags = None
+ if module.params.get('tags') is not None:
+ desired_tags = module.params['tags']
+ if module.params.get('name_tag') is not None:
+ # The module was originally implemented to filter certificates based on the 'Name' tag.
+ # Other tags are not used to filter certificates.
+ # It would make sense to replace the existing name_tag, domain, certificate_arn attributes
+ # with a 'filter' attribute, but that would break backwards-compatibility.
+ filter_tags = dict(Name=module.params['name_tag'])
+ if desired_tags is not None:
+ if 'Name' in desired_tags:
+ if desired_tags['Name'] != module.params['name_tag']:
+ module.fail_json(msg="Value of 'name_tag' conflicts with value of 'tags.Name'")
+ else:
+ desired_tags['Name'] = module.params['name_tag']
+ else:
+ desired_tags = deepcopy(filter_tags)
+
+ client = module.client('acm')
+
+ # fetch the list of certificates currently in ACM
+ certificates = acm.get_certificates(
+ client=client,
+ module=module,
+ domain_name=module.params['domain_name'],
+ arn=module.params['certificate_arn'],
+ only_tags=filter_tags,
+ )
+
+ module.debug("Found %d corresponding certificates in ACM" % len(certificates))
+ if module.params['state'] == 'present':
+ ensure_certificates_present(client, module, acm, certificates, desired_tags, filter_tags)
+
+ else: # state == absent
+ ensure_certificates_absent(client, module, acm, certificates)
+
+
+if __name__ == '__main__':
+ # tests()
+ main()
diff --git a/plugins/modules/acm_certificate_info.py b/plugins/modules/acm_certificate_info.py
new file mode 100644
index 00000000000..8e16162cedb
--- /dev/null
+++ b/plugins/modules/acm_certificate_info.py
@@ -0,0 +1,294 @@
+#!/usr/bin/python
+# Copyright (c) 2017 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+module: acm_certificate_info
+short_description: Retrieve certificate information from AWS Certificate Manager service
+version_added: 1.0.0
+description:
+ - Retrieve information for ACM certificates.
+ - Note that this will not return information about uploaded keys of size 4096 bits, due to a limitation of the ACM API.
+ - Prior to release 5.0.0 this module was called C(community.aws.aws_acm_info).
+ The usage did not change.
+options:
+ certificate_arn:
+ description:
+ - If provided, the results will be filtered to show only the certificate with this ARN.
+ - If no certificate with this ARN exists, this task will fail.
+ - If a certificate with this ARN exists in a different region, this task will fail
+ aliases:
+ - arn
+ type: str
+ domain_name:
+ description:
+ - The domain name of an ACM certificate to limit the search to.
+ aliases:
+ - name
+ type: str
+ statuses:
+ description:
+ - Status to filter the certificate results.
+ choices: ['PENDING_VALIDATION', 'ISSUED', 'INACTIVE', 'EXPIRED', 'VALIDATION_TIMED_OUT', 'REVOKED', 'FAILED']
+ type: list
+ elements: str
+ tags:
+ description:
+ - Filter results to show only certificates with tags that match all the tags specified here.
+ type: dict
+author:
+ - Will Thames (@willthames)
+extends_documentation_fragment:
+- amazon.aws.aws
+- amazon.aws.ec2
+'''
+
+EXAMPLES = r'''
+- name: obtain all ACM certificates
+ community.aws.aws_acm_info:
+
+- name: obtain all information for a single ACM certificate
+ community.aws.aws_acm_info:
+ domain_name: "*.example_com"
+
+- name: obtain all certificates pending validation
+ community.aws.aws_acm_info:
+ statuses:
+ - PENDING_VALIDATION
+
+- name: obtain all certificates with tag Name=foo and myTag=bar
+ community.aws.aws_acm_info:
+ tags:
+ Name: foo
+ myTag: bar
+
+
+# The output is still a list of certificates, just one item long.
+- name: obtain information about a certificate with a particular ARN
+ community.aws.aws_acm_info:
+ certificate_arn: "arn:aws:acm:ap-southeast-2:123456789876:certificate/abcdeabc-abcd-1234-4321-abcdeabcde12"
+
+'''
+
+RETURN = r'''
+certificates:
+ description: A list of certificates
+ returned: always
+ type: complex
+ contains:
+ certificate:
+ description: The ACM Certificate body
+ returned: when certificate creation is complete
+ sample: '-----BEGIN CERTIFICATE-----\\nMII.....-----END CERTIFICATE-----\\n'
+ type: str
+ certificate_arn:
+ description: Certificate ARN
+ returned: always
+ sample: arn:aws:acm:ap-southeast-2:123456789012:certificate/abcd1234-abcd-1234-abcd-123456789abc
+ type: str
+ certificate_chain:
+ description: Full certificate chain for the certificate
+ returned: when certificate creation is complete
+ sample: '-----BEGIN CERTIFICATE-----\\nMII...\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\n...'
+ type: str
+ created_at:
+ description: Date certificate was created
+ returned: always
+ sample: '2017-08-15T10:31:19+10:00'
+ type: str
+ domain_name:
+ description: Domain name for the certificate
+ returned: always
+ sample: '*.example.com'
+ type: str
+ domain_validation_options:
+ description: Options used by ACM to validate the certificate
+ returned: when certificate type is AMAZON_ISSUED
+ type: complex
+ contains:
+ domain_name:
+ description: Fully qualified domain name of the certificate
+ returned: always
+ sample: example.com
+ type: str
+ validation_domain:
+ description: The domain name ACM used to send validation emails
+ returned: always
+ sample: example.com
+ type: str
+ validation_emails:
+ description: A list of email addresses that ACM used to send domain validation emails
+ returned: always
+ sample:
+ - admin@example.com
+ - postmaster@example.com
+ type: list
+ elements: str
+ validation_status:
+ description: Validation status of the domain
+ returned: always
+ sample: SUCCESS
+ type: str
+ failure_reason:
+ description: Reason certificate request failed
+ returned: only when certificate issuing failed
+ type: str
+ sample: NO_AVAILABLE_CONTACTS
+ in_use_by:
+ description: A list of ARNs for the AWS resources that are using the certificate.
+ returned: always
+ sample: []
+ type: list
+ elements: str
+ issued_at:
+ description: Date certificate was issued
+ returned: always
+ sample: '2017-01-01T00:00:00+10:00'
+ type: str
+ issuer:
+ description: Issuer of the certificate
+ returned: always
+ sample: Amazon
+ type: str
+ key_algorithm:
+ description: Algorithm used to generate the certificate
+ returned: always
+ sample: RSA-2048
+ type: str
+ not_after:
+ description: Date after which the certificate is not valid
+ returned: always
+ sample: '2019-01-01T00:00:00+10:00'
+ type: str
+ not_before:
+ description: Date before which the certificate is not valid
+ returned: always
+ sample: '2017-01-01T00:00:00+10:00'
+ type: str
+ renewal_summary:
+ description: Information about managed renewal process
+ returned: when certificate is issued by Amazon and a renewal has been started
+ type: complex
+ contains:
+ domain_validation_options:
+ description: Options used by ACM to validate the certificate
+ returned: when certificate type is AMAZON_ISSUED
+ type: complex
+ contains:
+ domain_name:
+ description: Fully qualified domain name of the certificate
+ returned: always
+ sample: example.com
+ type: str
+ validation_domain:
+ description: The domain name ACM used to send validation emails
+ returned: always
+ sample: example.com
+ type: str
+ validation_emails:
+ description: A list of email addresses that ACM used to send domain validation emails
+ returned: always
+ sample:
+ - admin@example.com
+ - postmaster@example.com
+ type: list
+ elements: str
+ validation_status:
+ description: Validation status of the domain
+ returned: always
+ sample: SUCCESS
+ type: str
+ renewal_status:
+ description: Status of the domain renewal
+ returned: always
+ sample: PENDING_AUTO_RENEWAL
+ type: str
+ revocation_reason:
+ description: Reason for certificate revocation
+ returned: when the certificate has been revoked
+ sample: SUPERCEDED
+ type: str
+ revoked_at:
+ description: Date certificate was revoked
+ returned: when the certificate has been revoked
+ sample: '2017-09-01T10:00:00+10:00'
+ type: str
+ serial:
+ description: The serial number of the certificate
+ returned: always
+ sample: 00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f
+ type: str
+ signature_algorithm:
+ description: Algorithm used to sign the certificate
+ returned: always
+ sample: SHA256WITHRSA
+ type: str
+ status:
+ description: Status of the certificate in ACM
+ returned: always
+ sample: ISSUED
+ type: str
+ subject:
+ description: The name of the entity that is associated with the public key contained in the certificate
+ returned: always
+ sample: CN=*.example.com
+ type: str
+ subject_alternative_names:
+ description: Subject Alternative Names for the certificate
+ returned: always
+ sample:
+ - '*.example.com'
+ type: list
+ elements: str
+ tags:
+ description: Tags associated with the certificate
+ returned: always
+ type: dict
+ sample:
+ Application: helloworld
+ Environment: test
+ type:
+ description: The source of the certificate
+ returned: always
+ sample: AMAZON_ISSUED
+ type: str
+'''
+
+from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
+from ansible_collections.amazon.aws.plugins.module_utils.acm import ACMServiceManager
+
+
+def main():
+ argument_spec = dict(
+ certificate_arn=dict(aliases=['arn']),
+ domain_name=dict(aliases=['name']),
+ statuses=dict(
+ type='list',
+ elements='str',
+ choices=['PENDING_VALIDATION', 'ISSUED', 'INACTIVE', 'EXPIRED', 'VALIDATION_TIMED_OUT', 'REVOKED', 'FAILED']
+ ),
+ tags=dict(type='dict'),
+ )
+ module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True)
+ acm_info = ACMServiceManager(module)
+
+ client = module.client('acm')
+
+ certificates = acm_info.get_certificates(client, module,
+ domain_name=module.params['domain_name'],
+ statuses=module.params['statuses'],
+ arn=module.params['certificate_arn'],
+ only_tags=module.params['tags'])
+
+ if module.params['certificate_arn'] and len(certificates) != 1:
+ module.fail_json(msg="No certificate exists in this region with ARN %s" % module.params['certificate_arn'])
+
+ module.exit_json(certificates=certificates)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/integration/targets/acm_certificate/aliases b/tests/integration/targets/acm_certificate/aliases
new file mode 100644
index 00000000000..26ae3a05950
--- /dev/null
+++ b/tests/integration/targets/acm_certificate/aliases
@@ -0,0 +1,6 @@
+# https://github.com/ansible/ansible/issues/67788
+# unstable
+
+cloud/aws
+
+acm_certificate_info
diff --git a/tests/integration/targets/acm_certificate/defaults/main.yml b/tests/integration/targets/acm_certificate/defaults/main.yml
new file mode 100644
index 00000000000..5d3648f8e60
--- /dev/null
+++ b/tests/integration/targets/acm_certificate/defaults/main.yml
@@ -0,0 +1,40 @@
+---
+# we'll generate 3 certificates locally for the test
+# Upload the first
+# overwrite it with the second
+# and the third is unrelated, to check we only get info about the first when we want
+local_certs:
+ - priv_key: "{{ remote_tmp_dir }}/private-1.pem"
+ cert: "{{ remote_tmp_dir }}/public-1.pem"
+ csr: "{{ remote_tmp_dir }}/csr-1.csr"
+ domain: "acm1.{{ aws_acm_test_uuid }}.ansible.com"
+ name: "{{ resource_prefix }}_{{ aws_acm_test_uuid }}_1"
+
+ - priv_key: "{{ remote_tmp_dir }}/private-2.pem"
+ cert: "{{ remote_tmp_dir }}/public-2.pem"
+ csr: "{{ remote_tmp_dir }}/csr-2.csr"
+ domain: "acm2.{{ aws_acm_test_uuid }}.ansible.com"
+ name: "{{ resource_prefix }}_{{ aws_acm_test_uuid }}_2"
+
+ - priv_key: "{{ remote_tmp_dir }}/private-3.pem"
+ cert: "{{ remote_tmp_dir }}/public-3.pem"
+ csr: "{{ remote_tmp_dir }}/csr-3.csr"
+ domain: "acm3.{{ aws_acm_test_uuid }}.ansible.com"
+ name: "{{ resource_prefix }}_{{ aws_acm_test_uuid }}_3"
+
+# we'll have one private key
+# make 2 chains using it
+# so we can test what happens when you change just the chain
+# not the domain or key
+chained_cert:
+ priv_key: "{{ remote_tmp_dir }}/private-ch-0.pem"
+ domain: "acm-ch.{{ aws_acm_test_uuid }}.ansible.com"
+ name: "{{ resource_prefix }}_{{ aws_acm_test_uuid }}_4"
+ chains:
+ - cert: "{{ remote_tmp_dir }}/public-ch-0.pem"
+ csr: "{{ remote_tmp_dir }}/csr-ch-0.csr"
+ ca: 0 # index into local_certs
+ - cert: "{{ remote_tmp_dir }}/public-ch-1.pem"
+ csr: "{{ remote_tmp_dir }}/csr-ch-1.csr"
+ ca: 1 # index into local_certs
+
\ No newline at end of file
diff --git a/tests/integration/targets/acm_certificate/meta/main.yml b/tests/integration/targets/acm_certificate/meta/main.yml
new file mode 100644
index 00000000000..1810d4bec98
--- /dev/null
+++ b/tests/integration/targets/acm_certificate/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - setup_remote_tmp_dir
diff --git a/tests/integration/targets/acm_certificate/tasks/full_acm_test.yml b/tests/integration/targets/acm_certificate/tasks/full_acm_test.yml
new file mode 100644
index 00000000000..a02b07b3a37
--- /dev/null
+++ b/tests/integration/targets/acm_certificate/tasks/full_acm_test.yml
@@ -0,0 +1,504 @@
+- name: AWS ACM integration test
+ module_defaults:
+ group/aws:
+ aws_region: '{{ aws_region }}'
+ aws_access_key: '{{ aws_access_key }}'
+ aws_secret_key: '{{ aws_secret_key }}'
+ security_token: '{{ security_token | default(omit) }}'
+ block:
+ - name: list certs
+ aws_acm_info: null
+ register: list_all
+ - name: list certs with check mode
+ aws_acm_info: null
+ register: list_all_check
+ check_mode: yes # read-only task, should work the same as with no
+ - name: check certificate listing worked
+ assert:
+ that:
+ - list_all.certificates is defined
+ - list_all_check.certificates is defined
+ - list_all.certificates == list_all_check.certificates
+ - name: ensure absent cert which doesn't exist - first time
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ state: absent
+ with_items: '{{ local_certs }}'
+ - name: ensure absent cert which doesn't exist - second time
+ aws_acm:
+ name_tag: '{{ item[0].name }}'
+ state: absent
+ check_mode: '{{ item[1] }}'
+ with_nested:
+ - '{{ local_certs }}'
+ - [true, false]
+ register: absent_start_two
+ - name: check no change when ensuring absent cert is absent
+ assert:
+ that:
+ - not item.changed
+ with_items: "{{ absent_start_two.results }}"
+ - name: list cert which shouldn't exist
+ aws_acm_info:
+ tags:
+ Name: '{{ item.name }}'
+ register: list_tag
+ with_items: '{{ local_certs }}'
+ - name: check listing of missing cert returns no result
+ with_items: '{{ list_tag.results }}'
+ assert:
+ that:
+ - (item.certificates | length) == 0
+ - not list_tag.changed
+ - name: check directory was made
+ assert:
+ that:
+ - remote_tmp_dir is defined
+ - name: Generate private key for local certs
+ with_items: '{{ local_certs }}'
+ community.crypto.openssl_privatekey:
+ path: '{{ item.priv_key }}'
+ type: RSA
+ size: 2048
+ - name: Generate an OpenSSL Certificate Signing Request for own certs
+ with_items: '{{ local_certs }}'
+ community.crypto.openssl_csr:
+ path: '{{ item.csr }}'
+ privatekey_path: '{{ item.priv_key }}'
+ common_name: '{{ item.domain }}'
+ - name: Generate a Self Signed OpenSSL certificate for own certs
+ with_items: '{{ local_certs }}'
+ community.crypto.x509_certificate:
+ provider: selfsigned
+ path: '{{ item.cert }}'
+ csr_path: '{{ item.csr }}'
+ privatekey_path: '{{ item.priv_key }}'
+ selfsigned_digest: sha256
+ - name: upload certificate with check mode
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ certificate: '{{ lookup(''file'', item.cert ) }}'
+ private_key: '{{ lookup(''file'', item.priv_key ) }}'
+ state: present
+ check_mode: yes
+ register: upload_check
+ with_items: '{{ local_certs }}'
+ - name: check whether cert was uploaded in check mode
+ aws_acm_info:
+ tags:
+ Name: '{{ item.name }}'
+ register: list_after_check_mode_upload
+ with_items: '{{ local_certs }}'
+ - name: check cert was not really uploaded in check mode
+ with_items: "{{ list_after_check_mode_upload.results }}"
+ assert:
+ that:
+ - upload_check.changed
+ - (item.certificates | length) == 0
+ - name: upload certificates first time
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ certificate: '{{ lookup(''file'', item.cert ) }}'
+ private_key: '{{ lookup(''file'', item.priv_key ) }}'
+ state: present
+ register: upload
+ check_mode: no
+ with_items: '{{ local_certs }}'
+ until: upload is succeeded
+ retries: 5
+ delay: 10
+ - assert:
+ that:
+ - prev_task.certificate.arn is defined
+ - ('arn:aws:acm:123' | regex_search( 'arn:aws:acm:' )) is defined
+ - (prev_task.certificate.arn | regex_search( 'arn:aws:acm:' )) is defined
+ - prev_task.certificate.domain_name == original_cert.domain
+ - prev_task.changed
+ with_items: '{{ upload.results }}'
+ vars:
+ original_cert: '{{ item.item }}'
+ prev_task: '{{ item }}'
+ - name: fetch data about cert just uploaded, by ARN
+ aws_acm_info:
+ certificate_arn: '{{ item.certificate.arn }}'
+ register: fetch_after_up
+ with_items: '{{ upload.results }}'
+ - name: check output of prior task (fetch data about cert just uploaded, by ARN)
+ assert:
+ that:
+ - fetch_after_up_result.certificates | length == 1
+ - fetch_after_up_result.certificates[0].certificate_arn == upload_result.certificate.arn
+ - fetch_after_up_result.certificates[0].domain_name == original_cert.domain
+ - (fetch_after_up_result.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup( 'file', original_cert.cert ) | replace( ' ', '' ) | replace( '\n', '' ))
+ - '''Name'' in fetch_after_up_result.certificates[0].tags'
+ - fetch_after_up_result.certificates[0].tags['Name'] == original_cert.name
+ with_items: '{{ fetch_after_up.results }}'
+ vars:
+ fetch_after_up_result: '{{ item }}'
+ upload_result: '{{ item.item }}'
+ original_cert: '{{ item.item.item }}'
+ - name: fetch data about cert just uploaded, by name
+ aws_acm_info:
+ tags:
+ Name: '{{ original_cert.name }}'
+ register: fetch_after_up_name
+ with_items: '{{ upload.results }}'
+ vars:
+ upload_result: '{{ item }}'
+ original_cert: '{{ item.item }}'
+ - name: check fetched data of cert we just uploaded
+ assert:
+ that:
+ - fetch_after_up_name_result.certificates | length == 1
+ - fetch_after_up_name_result.certificates[0].certificate_arn == upload_result.certificate.arn
+ - fetch_after_up_name_result.certificates[0].domain_name == original_cert.domain
+ - (fetch_after_up_name_result.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', original_cert.cert ) | replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in fetch_after_up_name_result.certificates[0].tags'
+ - fetch_after_up_name_result.certificates[0].tags['Name'] == original_cert.name
+ with_items: '{{ fetch_after_up_name.results }}'
+ vars:
+ fetch_after_up_name_result: '{{ item }}'
+ upload_result: '{{ item.item }}'
+ original_cert: '{{ item.item.item }}'
+ - name: fetch data about cert just uploaded, by domain name
+ aws_acm_info:
+ domain_name: '{{ original_cert.domain }}'
+ register: fetch_after_up_domain
+ with_items: '{{ upload.results }}'
+ vars:
+ original_cert: '{{ item.item }}'
+ - name: compare fetched data of cert just uploaded to upload task
+ assert:
+ that:
+ - fetch_after_up_domain_result.certificates | length == 1
+ - fetch_after_up_domain_result.certificates[0].certificate_arn == upload_result.certificate.arn
+ - fetch_after_up_domain_result.certificates[0].domain_name == original_cert.domain
+ - (fetch_after_up_domain_result.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', original_cert.cert ) | replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in fetch_after_up_domain_result.certificates[0].tags'
+ - fetch_after_up_domain_result.certificates[0].tags['Name'] == original_cert.name
+ with_items: '{{ fetch_after_up_domain.results }}'
+ vars:
+ fetch_after_up_domain_result: '{{ item }}'
+ upload_result: '{{ item.item }}'
+ original_cert: '{{ item.item.item }}'
+ - name: upload certificates again, check not changed
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ certificate: '{{ lookup(''file'', item.cert ) }}'
+ private_key: '{{ lookup(''file'', item.priv_key ) }}'
+ state: present
+ register: upload2
+ with_items: '{{ local_certs }}'
+ failed_when: upload2.changed
+ - name: update first cert with body of the second, first time, check mode
+ aws_acm:
+ state: present
+ name_tag: '{{ local_certs[0].name }}'
+ certificate: '{{ lookup(''file'', local_certs[1].cert ) }}'
+ private_key: '{{ lookup(''file'', local_certs[1].priv_key ) }}'
+ check_mode: yes
+ register: overwrite_check
+ - name: check update in check mode detected required update
+ assert:
+ that:
+ - overwrite_check.changed
+ - name: check previous tasks did not change real cert
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[0].name }}'
+ register: fetch_after_overwrite_check
+ - name: check update with check mode did not change real cert
+ assert:
+ that:
+ - fetch_after_overwrite_check.certificates | length == 1
+ - fetch_after_overwrite_check.certificates[0].certificate_arn == fetch_after_up.results[0].certificates[0].certificate_arn
+ - fetch_after_overwrite_check.certificates[0].domain_name == local_certs[0].domain
+ - (fetch_after_overwrite_check.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', local_certs[0].cert )| replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in fetch_after_overwrite_check.certificates[0].tags'
+ - fetch_after_overwrite_check.certificates[0].tags['Name'] == local_certs[0].name
+ - name: update first cert with body of the second, first real time
+ aws_acm:
+ state: present
+ name_tag: '{{ local_certs[0].name }}'
+ certificate: '{{ lookup(''file'', local_certs[1].cert ) }}'
+ private_key: '{{ lookup(''file'', local_certs[1].priv_key ) }}'
+ register: overwrite
+ - name: check output of previous task (update first cert with body of the second, first time)
+ assert:
+ that:
+ - overwrite.certificate.arn is defined
+ - overwrite.certificate.arn | regex_search( 'arn:aws:acm:' ) is defined
+ - overwrite.certificate.arn == upload.results[0].certificate.arn
+ - overwrite.certificate.domain_name == local_certs[1].domain
+ - overwrite.changed
+ - name: check update was sucessfull
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[0].name }}'
+ register: fetch_after_overwrite
+ - name: check output of update fetch
+ assert:
+ that:
+ - fetch_after_overwrite.certificates | length == 1
+ - fetch_after_overwrite.certificates[0].certificate_arn == fetch_after_up.results[0].certificates[0].certificate_arn
+ - fetch_after_overwrite.certificates[0].domain_name == local_certs[1].domain
+ - (fetch_after_overwrite.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', local_certs[1].cert )| replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in fetch_after_overwrite.certificates[0].tags'
+ - fetch_after_overwrite.certificates[0].tags['Name'] == local_certs[0].name
+ - name: fetch other cert
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[1].name }}'
+ register: check_after_overwrite
+ - name: check other cert unaffected
+ assert:
+ that:
+ - check_after_overwrite.certificates | length == 1
+ - check_after_overwrite.certificates[0].certificate_arn == fetch_after_up.results[1].certificates[0].certificate_arn
+ - check_after_overwrite.certificates[0].domain_name == local_certs[1].domain
+ - (check_after_overwrite.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', local_certs[1].cert ) | replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in check_after_overwrite.certificates[0].tags'
+ - check_after_overwrite.certificates[0].tags['Name'] == local_certs[1].name
+ - name: update first cert with body of the second again
+ aws_acm:
+ state: present
+ name_tag: '{{ local_certs[0].name }}'
+ certificate: '{{ lookup(''file'', local_certs[1].cert ) }}'
+ private_key: '{{ lookup(''file'', local_certs[1].priv_key ) }}'
+ register: overwrite2
+ - name: check output of previous task (update first cert with body of the second again)
+ assert:
+ that:
+ - overwrite2.certificate.arn is defined
+ - overwrite2.certificate.arn | regex_search( 'arn:aws:acm:' ) is defined
+ - overwrite2.certificate.arn == upload.results[0].certificate.arn
+ - overwrite2.certificate.domain_name == local_certs[1].domain
+ - not overwrite2.changed
+ - name: delete certs 1 and 2 in check mode
+ aws_acm:
+ state: absent
+ domain_name: '{{ local_certs[1].domain }}'
+ check_mode: yes
+ register: delete_both_check
+ - name: test deletion with check mode detected change
+ assert:
+ that:
+ - delete_both_check.changed
+ - name: fetch info for certs 1 and 2
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[item].name }}'
+ register: check_del_one_check
+ with_items:
+ - 0
+ - 1
+ - name: test deletion with check mode detected change
+ with_items: '{{ check_del_one_check.results }}'
+ assert:
+ that:
+ - (item.certificates | length) == 1
+ - name: delete certs 1 and 2 real
+ aws_acm:
+ state: absent
+ domain_name: '{{ local_certs[1].domain }}'
+ register: delete_both
+ - name: test prev task
+ assert:
+ that:
+ - delete_both.arns is defined
+ - check_after_overwrite.certificates[0].certificate_arn in delete_both.arns
+ - upload.results[0].certificate.arn in delete_both.arns
+ - delete_both.changed
+ - name: fetch info for certs 1 and 2
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[item].name }}'
+ register: check_del_one
+ with_items:
+ - 0
+ - 1
+ retries: 2
+ until:
+ - check_del_one is not failed
+ - check_del_one.certificates | length == 0
+ delay: 10
+ - name: check certs 1 and 2 were already deleted
+ with_items: '{{ check_del_one.results }}'
+ assert:
+ that: (item.certificates | length) == 0
+ - name: check cert 3
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[2].name }}'
+ register: check_del_one_remain
+ - name: check cert 3 not deleted
+ assert:
+ that:
+ - (check_del_one_remain.certificates | length) == 1
+ - name: delete cert 3
+ aws_acm:
+ state: absent
+ domain_name: '{{ local_certs[2].domain }}'
+ register: delete_third
+ - name: check cert 3 deletion went as expected
+ assert:
+ that:
+ - delete_third.arns is defined
+ - delete_third.arns | length == 1
+ - delete_third.arns[0] == upload.results[2].certificate.arn
+ - delete_third.changed
+ - name: check cert 3 was deleted
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[2].name }}'
+ register: check_del_three
+ failed_when: check_del_three.certificates | length != 0
+ - name: delete cert 3 again
+ aws_acm:
+ state: absent
+ domain_name: '{{ local_certs[2].domain }}'
+ register: delete_third
+ - name: check deletion of cert 3 not changed, because already deleted
+ assert:
+ that:
+ - delete_third.arns is defined
+ - delete_third.arns | length == 0
+ - not delete_third.changed
+ - name: delete cert 3 again, check mode
+ aws_acm:
+ state: absent
+ domain_name: '{{ local_certs[2].domain }}'
+ check_mode: yes
+ register: delete_third_check
+ - name: test deletion in check mode detected required change
+ assert:
+ that:
+ - not delete_third_check.changed
+ - name: check directory was made
+ assert:
+ that:
+ - remote_tmp_dir is defined
+ - name: Generate private key for cert to be chained
+ community.crypto.openssl_privatekey:
+ path: '{{ chained_cert.priv_key }}'
+ type: RSA
+ size: 2048
+ - name: Generate two OpenSSL Certificate Signing Requests for cert to be chained
+ with_items: '{{ chained_cert.chains }}'
+ community.crypto.openssl_csr:
+ path: '{{ item.csr }}'
+ privatekey_path: '{{ chained_cert.priv_key }}'
+ common_name: '{{ chained_cert.domain }}'
+ - name: Sign new certs with cert 0 and 1
+ with_items: '{{ chained_cert.chains }}'
+ community.crypto.x509_certificate:
+ provider: ownca
+ path: '{{ item.cert }}'
+ csr_path: '{{ item.csr }}'
+ ownca_path: '{{ local_certs[item.ca].cert }}'
+ ownca_privatekey_path: '{{ local_certs[item.ca].priv_key }}'
+ selfsigned_digest: sha256
+ - name: check files exist (for next task)
+ file:
+ path: '{{ item }}'
+ state: file
+ with_items:
+ - '{{ local_certs[chained_cert.chains[0].ca].cert }}'
+ - '{{ local_certs[chained_cert.chains[1].ca].cert }}'
+ - '{{ chained_cert.chains[0].cert }}'
+ - '{{ chained_cert.chains[1].cert }}'
+ - name: Find chains
+ with_items: '{{ chained_cert.chains }}'
+ register: chains
+ community.crypto.certificate_complete_chain:
+ input_chain: '{{ lookup(''file'', item.cert ) }}'
+ root_certificates:
+ - '{{ local_certs[item.ca].cert }}'
+ - name: upload chained cert, first chain, first time
+ aws_acm:
+ name_tag: '{{ chained_cert.name }}'
+ certificate: '{{ lookup(''file'', chained_cert.chains[0].cert ) }}'
+ certificate_chain: '{{ chains.results[0].complete_chain | join(''
+
+ '') }}'
+ private_key: '{{ lookup(''file'', chained_cert.priv_key ) }}'
+ state: present
+ register: upload_chain
+ failed_when: not upload_chain.changed
+ - name: fetch chain of cert we just uploaded
+ aws_acm_info:
+ tags:
+ Name: '{{ chained_cert.name }}'
+ register: check_chain
+ - name: check chain of cert we just uploaded
+ assert:
+ that:
+ - (check_chain.certificates[0].certificate_chain | replace( ' ', '' ) | replace( '\n', '')) == ( chains.results[0].complete_chain | join( '\n' ) | replace( ' ', '' ) | replace( '\n', '') )
+ - (check_chain.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == ( lookup('file', chained_cert.chains[0].cert ) | replace( ' ', '' ) | replace( '\n', '') )
+ - name: upload chained cert again, check not changed
+ aws_acm:
+ name_tag: '{{ chained_cert.name }}'
+ certificate: '{{ lookup(''file'', chained_cert.chains[0].cert ) }}'
+ certificate_chain: '{{ chains.results[0].complete_chain | join(''
+
+ '') }}'
+ private_key: '{{ lookup(''file'', chained_cert.priv_key ) }}'
+ state: present
+ register: upload_chain_2
+ - name: check previous task not changed
+ assert:
+ that:
+ - upload_chain_2.certificate.arn == upload_chain.certificate.arn
+ - not upload_chain_2.changed
+ - name: upload chained cert, different chain
+ aws_acm:
+ name_tag: '{{ chained_cert.name }}'
+ certificate: '{{ lookup(''file'', chained_cert.chains[1].cert ) }}'
+ certificate_chain: '{{ chains.results[1].complete_chain | join(''
+
+ '') }}'
+ private_key: '{{ lookup(''file'', chained_cert.priv_key ) }}'
+ state: present
+ register: upload_chain_3
+ - name: check uploading with different chain is changed
+ assert:
+ that:
+ - upload_chain_3.changed
+ - upload_chain_3.certificate.arn == upload_chain.certificate.arn
+ - name: fetch info about chain of cert we just updated
+ aws_acm_info:
+ tags:
+ Name: '{{ chained_cert.name }}'
+ register: check_chain_2
+ - name: check chain of cert we just uploaded
+ assert:
+ that:
+ - (check_chain_2.certificates[0].certificate_chain | replace( ' ', '' ) | replace( '\n', '')) == ( chains.results[1].complete_chain | join( '\n' ) | replace( ' ', '' ) | replace( '\n', '') )
+ - (check_chain_2.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == ( lookup('file', chained_cert.chains[1].cert ) | replace( ' ', '' ) | replace( '\n', '') )
+ - name: delete chained cert
+ aws_acm:
+ name_tag: '{{ chained_cert.name }}'
+ state: absent
+ register: delete_chain_3
+ - name: check deletion of chained cert 3 is changed
+ assert:
+ that:
+ - delete_chain_3.changed
+ - upload_chain.certificate.arn in delete_chain_3.arns
+ always:
+ - name: delete first bunch of certificates
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ state: absent
+ with_items: '{{ local_certs }}'
+ ignore_errors: true
+ - name: delete chained cert
+ aws_acm:
+ state: absent
+ name_tag: '{{ chained_cert.name }}'
+ ignore_errors: true
+ - name: deleting local directory with test artefacts
+ file:
+ path: '{{ remote_tmp_dir }}'
+ state: directory
+ ignore_errors: true
diff --git a/tests/integration/targets/acm_certificate/tasks/main.yml b/tests/integration/targets/acm_certificate/tasks/main.yml
new file mode 100644
index 00000000000..118fca74498
--- /dev/null
+++ b/tests/integration/targets/acm_certificate/tasks/main.yml
@@ -0,0 +1,579 @@
+- name: AWS ACM integration test
+ module_defaults:
+ group/aws:
+ aws_region: '{{ aws_region }}'
+ aws_access_key: '{{ aws_access_key }}'
+ aws_secret_key: '{{ aws_secret_key }}'
+ security_token: '{{ security_token | default(omit) }}'
+ block:
+ # The CI runs many of these tests in parallel
+ # Use this random ID to differentiate which resources
+ # are from which test
+ - set_fact:
+ aws_acm_test_uuid: "{{ (10**9) | random }}"
+ - name: attempt to delete cert without specifying required parameter
+ aws_acm:
+ state: absent
+ register: result
+ ignore_errors: true
+ - name: assert failure when name_tag conflicts with tags.Name
+ assert:
+ that:
+ - 'result.failed'
+ - '"If ''state'' is specified as ''absent'' then exactly one of ''name_tag''" in result.msg'
+ - name: list certs
+ aws_acm_info: null
+ register: list_all
+ failed_when: list_all.certificates is not defined
+ - name: ensure absent cert which doesn't exist - first time
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ state: absent
+ with_items: '{{ local_certs }}'
+ - name: ensure absent cert which doesn't exist - second time
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ state: absent
+ with_items: '{{ local_certs }}'
+ register: absent_start_two
+ failed_when: absent_start_two.changed
+ - name: list cert which shouldn't exist
+ aws_acm_info:
+ tags:
+ Name: '{{ item.name }}'
+ register: list_tag
+ with_items: '{{ local_certs }}'
+ failed_when: list_tag.certificates | length > 0
+ - name: check directory was made
+ assert:
+ that:
+ - remote_tmp_dir is defined
+ - name: Generate private key for local certs
+ with_items: '{{ local_certs }}'
+ community.crypto.openssl_privatekey:
+ path: '{{ item.priv_key }}'
+ type: RSA
+ size: 2048
+ - name: Generate an OpenSSL Certificate Signing Request for own certs
+ with_items: '{{ local_certs }}'
+ community.crypto.openssl_csr:
+ path: '{{ item.csr }}'
+ privatekey_path: '{{ item.priv_key }}'
+ common_name: '{{ item.domain }}'
+ - name: Generate a Self Signed OpenSSL certificate for own certs
+ with_items: '{{ local_certs }}'
+ community.crypto.x509_certificate:
+ provider: selfsigned
+ path: '{{ item.cert }}'
+ csr_path: '{{ item.csr }}'
+ privatekey_path: '{{ item.priv_key }}'
+ selfsigned_digest: sha256
+ - name: try to upload certificate, but name_tag conflicts with tags.Name
+ vars:
+ local_cert: '{{ local_certs[0] }}'
+ aws_acm:
+ name_tag: '{{ local_cert.name }}'
+ certificate: '{{ lookup(''file'', local_cert.cert ) }}'
+ private_key: '{{ lookup(''file'', local_cert.priv_key ) }}'
+ state: present
+ tags:
+ Name: '{{ local_cert.name }}-other'
+ Application: search
+ Environment: development
+ register: result
+ ignore_errors: true
+ - name: assert failure when name_tag conflicts with tags.Name
+ assert:
+ that:
+ - 'result.failed'
+ - '"conflicts with value of" in result.msg'
+ - name: upload certificates first time
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ certificate: '{{ lookup(''file'', item.cert ) }}'
+ private_key: '{{ lookup(''file'', item.priv_key ) }}'
+ state: present
+ # Add tags
+ tags:
+ Application: search
+ Environment: development
+ purge_tags: false
+ register: upload
+ with_items: '{{ local_certs }}'
+ until: upload is succeeded
+ retries: 5
+ delay: 10
+ - assert:
+ that:
+ - prev_task.certificate.arn is defined
+ - ('arn:aws:acm:123' | regex_search( 'arn:aws:acm:' )) is defined
+ - (prev_task.certificate.arn | regex_search( 'arn:aws:acm:' )) is defined
+ - prev_task.certificate.domain_name == original_cert.domain
+ - prev_task.changed
+ with_items: '{{ upload.results }}'
+ vars:
+ original_cert: '{{ item.item }}'
+ prev_task: '{{ item }}'
+ - name: fetch data about cert just uploaded, by ARN
+ aws_acm_info:
+ certificate_arn: '{{ item.certificate.arn }}'
+ register: fetch_after_up
+ with_items: '{{ upload.results }}'
+ - name: check output of prior task (fetch data about cert just uploaded, by ARN)
+ assert:
+ that:
+ - fetch_after_up_result.certificates | length == 1
+ - fetch_after_up_result.certificates[0].certificate_arn == upload_result.certificate.arn
+ - fetch_after_up_result.certificates[0].domain_name == original_cert.domain
+ - (fetch_after_up_result.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup( 'file', original_cert.cert ) | replace( ' ', '' ) | replace( '\n', '' ))
+ - '''Name'' in fetch_after_up_result.certificates[0].tags'
+ - '''Application'' in fetch_after_up_result.certificates[0].tags'
+ - '''Environment'' in fetch_after_up_result.certificates[0].tags'
+ - fetch_after_up_result.certificates[0].tags['Name'] == original_cert.name
+ - fetch_after_up_result.certificates[0].tags['Application'] == 'search'
+ - fetch_after_up_result.certificates[0].tags['Environment'] == 'development'
+ with_items: '{{ fetch_after_up.results }}'
+ vars:
+ fetch_after_up_result: '{{ item }}'
+ upload_result: '{{ item.item }}'
+ original_cert: '{{ item.item.item }}'
+ - name: fetch data about cert just uploaded, by name
+ aws_acm_info:
+ tags:
+ Name: '{{ original_cert.name }}'
+ register: fetch_after_up_name
+ with_items: '{{ upload.results }}'
+ vars:
+ upload_result: '{{ item }}'
+ original_cert: '{{ item.item }}'
+ - name: check fetched data of cert we just uploaded
+ assert:
+ that:
+ - fetch_after_up_name_result.certificates | length == 1
+ - fetch_after_up_name_result.certificates[0].certificate_arn == upload_result.certificate.arn
+ - fetch_after_up_name_result.certificates[0].domain_name == original_cert.domain
+ - (fetch_after_up_name_result.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', original_cert.cert ) | replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in fetch_after_up_name_result.certificates[0].tags'
+ - fetch_after_up_name_result.certificates[0].tags['Name'] == original_cert.name
+ with_items: '{{ fetch_after_up_name.results }}'
+ vars:
+ fetch_after_up_name_result: '{{ item }}'
+ upload_result: '{{ item.item }}'
+ original_cert: '{{ item.item.item }}'
+ - name: fetch data about cert just uploaded, by domain name
+ aws_acm_info:
+ domain_name: '{{ original_cert.domain }}'
+ register: fetch_after_up_domain
+ with_items: '{{ upload.results }}'
+ vars:
+ original_cert: '{{ item.item }}'
+ - name: compare fetched data of cert just uploaded to upload task
+ assert:
+ that:
+ - fetch_after_up_domain_result.certificates | length == 1
+ - fetch_after_up_domain_result.certificates[0].certificate_arn == upload_result.certificate.arn
+ - fetch_after_up_domain_result.certificates[0].domain_name == original_cert.domain
+ - (fetch_after_up_domain_result.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', original_cert.cert ) | replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in fetch_after_up_domain_result.certificates[0].tags'
+ - fetch_after_up_domain_result.certificates[0].tags['Name'] == original_cert.name
+ with_items: '{{ fetch_after_up_domain.results }}'
+ vars:
+ fetch_after_up_domain_result: '{{ item }}'
+ upload_result: '{{ item.item }}'
+ original_cert: '{{ item.item.item }}'
+ - name: upload certificates again, check not changed
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ certificate: '{{ lookup(''file'', item.cert ) }}'
+ private_key: '{{ lookup(''file'', item.priv_key ) }}'
+ state: present
+ register: upload2
+ with_items: '{{ local_certs }}'
+ failed_when: upload2.changed
+ - name: change tags of existing certificate, check mode
+ aws_acm:
+ certificate_arn: '{{ certificate_arn }}'
+ tags:
+ Name: '{{ name_tag }}'
+ Application: search
+ Environment: staging
+ Owner: Bob
+ register: certificate_with_tags
+ check_mode: true
+ vars:
+ name_tag: '{{ upload2.results[0].item.name }}'
+ certificate_arn: '{{ upload2.results[0].certificate.arn }}'
+ domain_name: '{{ upload2.results[0].certificate.domain_name }}'
+ - assert:
+ that:
+ - certificate_with_tags.changed
+ - name: change tags of existing certificate, changes expected
+ aws_acm:
+ # When applying tags to an existing certificate, it is sufficient to specify the 'certificate_arn'.
+ # Previously, the 'aws_acm' module was requiring the 'certificate', 'name_tag' and 'domain_name'
+ # attributes.
+ certificate_arn: '{{ certificate_arn }}'
+ tags:
+ Name: '{{ name_tag }}'
+ Application: search
+ Environment: staging
+ Owner: Bob
+ register: certificate_with_tags
+ vars:
+ name_tag: '{{ upload2.results[0].item.name }}'
+ certificate_arn: '{{ upload2.results[0].certificate.arn }}'
+ domain_name: '{{ upload2.results[0].certificate.domain_name }}'
+ - name: assert certificate tags
+ assert:
+ that:
+ - certificate_with_tags.certificate.tags | length == 4
+ - '''Name'' in certificate_with_tags.certificate.tags'
+ - '''Application'' in certificate_with_tags.certificate.tags'
+ - '''Environment'' in certificate_with_tags.certificate.tags'
+ - '''Owner'' in certificate_with_tags.certificate.tags'
+ - certificate_with_tags.certificate.tags['Name'] == name_tag
+ - certificate_with_tags.certificate.tags['Application'] == 'search'
+ - certificate_with_tags.certificate.tags['Environment'] == 'staging'
+ - certificate_with_tags.certificate.tags['Owner'] == 'Bob'
+ - certificate_with_tags.changed
+ vars:
+ name_tag: '{{ upload2.results[0].item.name }}'
+ - name: change tags of existing certificate, check mode again
+ aws_acm:
+ certificate_arn: '{{ certificate_arn }}'
+ tags:
+ Name: '{{ name_tag }}'
+ Application: search
+ Environment: staging
+ Owner: Bob
+ register: certificate_with_tags
+ check_mode: true
+ vars:
+ name_tag: '{{ upload2.results[0].item.name }}'
+ certificate_arn: '{{ upload2.results[0].certificate.arn }}'
+ - assert:
+ that:
+ - not certificate_with_tags.changed
+ - name: change tags of existing certificate, no change expected
+ aws_acm:
+ certificate_arn: '{{ certificate_arn }}'
+ tags:
+ Name: '{{ name_tag }}'
+ Application: search
+ Environment: staging
+ Owner: Bob
+ register: certificate_with_tags
+ vars:
+ name_tag: '{{ upload2.results[0].item.name }}'
+ certificate_arn: '{{ upload2.results[0].certificate.arn }}'
+ - name: assert certificate tags
+ assert:
+ that:
+ - certificate_with_tags.certificate.tags | length == 4
+ - '''Name'' in certificate_with_tags.certificate.tags'
+ - '''Application'' in certificate_with_tags.certificate.tags'
+ - '''Environment'' in certificate_with_tags.certificate.tags'
+ - '''Owner'' in certificate_with_tags.certificate.tags'
+ - certificate_with_tags.certificate.tags['Name'] == name_tag
+ - certificate_with_tags.certificate.tags['Application'] == 'search'
+ - certificate_with_tags.certificate.tags['Environment'] == 'staging'
+ - certificate_with_tags.certificate.tags['Owner'] == 'Bob'
+ - not certificate_with_tags.changed
+ vars:
+ name_tag: '{{ upload2.results[0].item.name }}'
+ - name: check fetched data of cert we just uploaded
+ vars:
+ certificate_arn: '{{ upload2.results[0].certificate.arn }}'
+ domain_name: '{{ upload2.results[0].certificate.domain_name }}'
+ name_tag: '{{ upload2.results[0].item.name }}'
+ assert:
+ that:
+ - certificate_with_tags.certificate.arn == certificate_arn
+ - certificate_with_tags.certificate.tags | length == 4
+ - '''Name'' in certificate_with_tags.certificate.tags'
+ - '''Application'' in certificate_with_tags.certificate.tags'
+ - '''Environment'' in certificate_with_tags.certificate.tags'
+ - '''Owner'' in certificate_with_tags.certificate.tags'
+ - certificate_with_tags.certificate.tags['Name'] == name_tag
+ - certificate_with_tags.certificate.tags['Application'] == 'search'
+ - certificate_with_tags.certificate.tags['Environment'] == 'staging'
+ - certificate_with_tags.certificate.tags['Owner'] == 'Bob'
+ - name: change tags of existing certificate, purge tags
+ aws_acm:
+ certificate_arn: '{{ certificate_arn }}'
+ tags:
+ Name: '{{ name_tag }}'
+ Application: search
+ Environment: staging
+ # 'Owner' tag should be removed because 'purge_tags: true'
+ purge_tags: true
+ register: certificate_with_tags
+ vars:
+ name_tag: '{{ upload2.results[0].item.name }}'
+ certificate_arn: '{{ upload2.results[0].certificate.arn }}'
+ domain_name: '{{ upload2.results[0].certificate.domain_name }}'
+ - name: check fetched data of cert we just uploaded
+ vars:
+ name_tag: '{{ upload2.results[0].item.name }}'
+ certificate_arn: '{{ upload2.results[0].certificate.arn }}'
+ domain_name: '{{ upload2.results[0].certificate.domain_name }}'
+ assert:
+ that:
+ - certificate_with_tags.certificate.arn == certificate_arn
+ - certificate_with_tags.certificate.tags | length == 3
+ - '''Name'' in certificate_with_tags.certificate.tags'
+ - '''Application'' in certificate_with_tags.certificate.tags'
+ - '''Environment'' in certificate_with_tags.certificate.tags'
+ - certificate_with_tags.certificate.tags['Name'] == name_tag
+ - certificate_with_tags.certificate.tags['Application'] == 'search'
+ - certificate_with_tags.certificate.tags['Environment'] == 'staging'
+ - name: update first cert with body of the second, first time
+ aws_acm:
+ state: present
+ name_tag: '{{ local_certs[0].name }}'
+ certificate: '{{ lookup(''file'', local_certs[1].cert ) }}'
+ private_key: '{{ lookup(''file'', local_certs[1].priv_key ) }}'
+ register: overwrite
+ - name: check output of previous task (update first cert with body of the second, first time)
+ assert:
+ that:
+ - overwrite.certificate.arn is defined
+ - overwrite.certificate.arn | regex_search( 'arn:aws:acm:' ) is defined
+ - overwrite.certificate.arn == upload.results[0].certificate.arn
+ - overwrite.certificate.domain_name == local_certs[1].domain
+ - overwrite.changed
+ - name: check update was sucessfull
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[0].name }}'
+ register: fetch_after_overwrite
+ - name: check output of update fetch
+ assert:
+ that:
+ - fetch_after_overwrite.certificates | length == 1
+ - fetch_after_overwrite.certificates[0].certificate_arn == fetch_after_up.results[0].certificates[0].certificate_arn
+ - fetch_after_overwrite.certificates[0].domain_name == local_certs[1].domain
+ - (fetch_after_overwrite.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', local_certs[1].cert )| replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in fetch_after_overwrite.certificates[0].tags'
+ - fetch_after_overwrite.certificates[0].tags['Name'] == local_certs[0].name
+ - name: fetch other cert
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[1].name }}'
+ register: check_after_overwrite
+ - name: check other cert unaffected
+ assert:
+ that:
+ - check_after_overwrite.certificates | length == 1
+ - check_after_overwrite.certificates[0].certificate_arn == fetch_after_up.results[1].certificates[0].certificate_arn
+ - check_after_overwrite.certificates[0].domain_name == local_certs[1].domain
+ - (check_after_overwrite.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == (lookup('file', local_certs[1].cert ) | replace( ' ', '' ) | replace( '\n', ''))
+ - '''Name'' in check_after_overwrite.certificates[0].tags'
+ - check_after_overwrite.certificates[0].tags['Name'] == local_certs[1].name
+ - name: update first cert with body of the second again
+ aws_acm:
+ state: present
+ name_tag: '{{ local_certs[0].name }}'
+ certificate: '{{ lookup(''file'', local_certs[1].cert ) }}'
+ private_key: '{{ lookup(''file'', local_certs[1].priv_key ) }}'
+ register: overwrite2
+ - name: check output of previous task (update first cert with body of the second again)
+ assert:
+ that:
+ - overwrite2.certificate.arn is defined
+ - overwrite2.certificate.arn | regex_search( 'arn:aws:acm:' ) is defined
+ - overwrite2.certificate.arn == upload.results[0].certificate.arn
+ - overwrite2.certificate.domain_name == local_certs[1].domain
+ - not overwrite2.changed
+ - name: delete certs 1 and 2
+ aws_acm:
+ state: absent
+ domain_name: '{{ local_certs[1].domain }}'
+ register: delete_both
+ - name: test prev task
+ assert:
+ that:
+ - delete_both.arns is defined
+ - check_after_overwrite.certificates[0].certificate_arn in delete_both.arns
+ - upload.results[0].certificate.arn in delete_both.arns
+ - delete_both.changed
+ - name: fetch info for certs 1 and 2
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[item].name }}'
+ register: check_del_one
+ with_items:
+ - 0
+ - 1
+ retries: 2
+ until:
+ - check_del_one is not failed
+ - check_del_one.certificates | length == 0
+ delay: 10
+ - name: check certs 1 and 2 were already deleted
+ with_items: '{{ check_del_one.results }}'
+ assert:
+ that: item.certificates | length == 0
+ - name: check cert 3 not deleted
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[2].name }}'
+ register: check_del_one_remain
+ failed_when: check_del_one_remain.certificates | length != 1
+ - name: delete cert 3
+ aws_acm:
+ state: absent
+ domain_name: '{{ local_certs[2].domain }}'
+ register: delete_third
+ - name: check cert 3 deletion went as expected
+ assert:
+ that:
+ - delete_third.arns is defined
+ - delete_third.arns | length == 1
+ - delete_third.arns[0] == upload.results[2].certificate.arn
+ - delete_third.changed
+ - name: check cert 3 was deleted
+ aws_acm_info:
+ tags:
+ Name: '{{ local_certs[2].name }}'
+ register: check_del_three
+ failed_when: check_del_three.certificates | length != 0
+ - name: delete cert 3 again
+ aws_acm:
+ state: absent
+ domain_name: '{{ local_certs[2].domain }}'
+ register: delete_third
+ - name: check deletion of cert 3 not changed, because already deleted
+ assert:
+ that:
+ - delete_third.arns is defined
+ - delete_third.arns | length == 0
+ - not delete_third.changed
+ - name: check directory was made
+ assert:
+ that:
+ - remote_tmp_dir is defined
+ - name: Generate private key for cert to be chained
+ community.crypto.openssl_privatekey:
+ path: '{{ chained_cert.priv_key }}'
+ type: RSA
+ size: 2048
+ - name: Generate two OpenSSL Certificate Signing Requests for cert to be chained
+ with_items: '{{ chained_cert.chains }}'
+ community.crypto.openssl_csr:
+ path: '{{ item.csr }}'
+ privatekey_path: '{{ chained_cert.priv_key }}'
+ common_name: '{{ chained_cert.domain }}'
+ - name: Sign new certs with cert 0 and 1
+ with_items: '{{ chained_cert.chains }}'
+ community.crypto.x509_certificate:
+ provider: ownca
+ path: '{{ item.cert }}'
+ csr_path: '{{ item.csr }}'
+ ownca_path: '{{ local_certs[item.ca].cert }}'
+ ownca_privatekey_path: '{{ local_certs[item.ca].priv_key }}'
+ selfsigned_digest: sha256
+ - name: check files exist (for next task)
+ file:
+ path: '{{ item }}'
+ state: file
+ with_items:
+ - '{{ local_certs[chained_cert.chains[0].ca].cert }}'
+ - '{{ local_certs[chained_cert.chains[1].ca].cert }}'
+ - '{{ chained_cert.chains[0].cert }}'
+ - '{{ chained_cert.chains[1].cert }}'
+ - name: Find chains
+ with_items: '{{ chained_cert.chains }}'
+ register: chains
+ community.crypto.certificate_complete_chain:
+ input_chain: '{{ lookup(''file'', item.cert ) }}'
+ root_certificates:
+ - '{{ local_certs[item.ca].cert }}'
+ - name: upload chained cert, first chain, first time
+ aws_acm:
+ name_tag: '{{ chained_cert.name }}'
+ certificate: '{{ lookup(''file'', chained_cert.chains[0].cert ) }}'
+ certificate_chain: '{{ chains.results[0].complete_chain | join(''
+
+ '') }}'
+ private_key: '{{ lookup(''file'', chained_cert.priv_key ) }}'
+ state: present
+ register: upload_chain
+ failed_when: not upload_chain.changed
+ - name: fetch chain of cert we just uploaded
+ aws_acm_info:
+ tags:
+ Name: '{{ chained_cert.name }}'
+ register: check_chain
+ - name: check chain of cert we just uploaded
+ assert:
+ that:
+ - (check_chain.certificates[0].certificate_chain | replace( ' ', '' ) | replace( '\n', '')) == ( chains.results[0].complete_chain | join( '\n' ) | replace( ' ', '' ) | replace( '\n', '') )
+ - (check_chain.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == ( lookup('file', chained_cert.chains[0].cert ) | replace( ' ', '' ) | replace( '\n', '') )
+ - name: upload chained cert again, check not changed
+ aws_acm:
+ name_tag: '{{ chained_cert.name }}'
+ certificate: '{{ lookup(''file'', chained_cert.chains[0].cert ) }}'
+ certificate_chain: '{{ chains.results[0].complete_chain | join(''
+
+ '') }}'
+ private_key: '{{ lookup(''file'', chained_cert.priv_key ) }}'
+ state: present
+ register: upload_chain_2
+ - name: check previous task not changed
+ assert:
+ that:
+ - upload_chain_2.certificate.arn == upload_chain.certificate.arn
+ - not upload_chain_2.changed
+ - name: upload chained cert, different chain
+ aws_acm:
+ name_tag: '{{ chained_cert.name }}'
+ certificate: '{{ lookup(''file'', chained_cert.chains[1].cert ) }}'
+ certificate_chain: '{{ chains.results[1].complete_chain | join(''
+
+ '') }}'
+ private_key: '{{ lookup(''file'', chained_cert.priv_key ) }}'
+ state: present
+ register: upload_chain_3
+ - name: check uploading with different chain is changed
+ assert:
+ that:
+ - upload_chain_3.changed
+ - upload_chain_3.certificate.arn == upload_chain.certificate.arn
+ - name: fetch info about chain of cert we just updated
+ aws_acm_info:
+ tags:
+ Name: '{{ chained_cert.name }}'
+ register: check_chain_2
+ - name: check chain of cert we just uploaded
+ assert:
+ that:
+ - (check_chain_2.certificates[0].certificate_chain | replace( ' ', '' ) | replace( '\n', '')) == ( chains.results[1].complete_chain | join( '\n' ) | replace( ' ', '' ) | replace( '\n', '') )
+ - (check_chain_2.certificates[0].certificate | replace( ' ', '' ) | replace( '\n', '')) == ( lookup('file', chained_cert.chains[1].cert ) | replace( ' ', '' ) | replace( '\n', '') )
+ - name: delete chained cert
+ aws_acm:
+ name_tag: '{{ chained_cert.name }}'
+ state: absent
+ register: delete_chain_3
+ - name: check deletion of chained cert 3 is changed
+ assert:
+ that:
+ - delete_chain_3.changed
+ - upload_chain.certificate.arn in delete_chain_3.arns
+ always:
+ - name: delete first bunch of certificates
+ aws_acm:
+ name_tag: '{{ item.name }}'
+ state: absent
+ with_items: '{{ local_certs }}'
+ ignore_errors: true
+ - name: delete chained cert
+ aws_acm:
+ state: absent
+ name_tag: '{{ chained_cert.name }}'
+ ignore_errors: true
+ - name: deleting local directory with test artefacts
+ file:
+ path: '{{ remote_tmp_dir }}'
+ state: directory
+ ignore_errors: true