diff --git a/changelogs/fragments/migrate_iam_server_certificate.yml b/changelogs/fragments/migrate_iam_server_certificate.yml new file mode 100644 index 00000000000..2a9774e7594 --- /dev/null +++ b/changelogs/fragments/migrate_iam_server_certificate.yml @@ -0,0 +1,8 @@ +--- +major_changes: + - iam_server_certificate - The module has been migrated from the ``community.aws`` + collection. Playbooks using the Fully Qualified Collection Name for this module + should be updated to use ``amazon.aws.iam_server_certificate``. + - iam_server_certificate_info - The module has been migrated from the ``community.aws`` + collection. Playbooks using the Fully Qualified Collection Name for this module + should be updated to use ``amazon.aws.iam_server_certificate_info``. diff --git a/meta/runtime.yml b/meta/runtime.yml index a6ddadfa10c..a8b6c172792 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -82,6 +82,8 @@ action_groups: - iam_policy_info - iam_role - iam_role_info + - iam_server_certificate + - iam_server_certificate_info - iam_user - iam_user_info - kms_key diff --git a/plugins/modules/iam_server_certificate.py b/plugins/modules/iam_server_certificate.py new file mode 100644 index 00000000000..0a7db8f9ad8 --- /dev/null +++ b/plugins/modules/iam_server_certificate.py @@ -0,0 +1,380 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Contributors to the Ansible project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = r""" +--- +module: iam_server_certificate +version_added: 1.0.0 +version_added_collection: community.aws +short_description: Manage IAM server certificates for use on ELBs and CloudFront +description: + - Allows for the management of IAM server certificates. +options: + name: + description: + - Name of certificate to add, update or remove. + required: true + type: str + new_name: + description: + - When I(state=present), this will update the name of the cert. + - The I(cert), I(key) and I(cert_chain) parameters will be ignored if this is defined. + type: str + new_path: + description: + - When I(state=present), this will update the path of the cert. + - The I(cert), I(key) and I(cert_chain) parameters will be ignored if this is defined. + type: str + state: + description: + - Whether to create (or update) or delete the certificate. + - If I(new_path) or I(new_name) is defined, specifying present will attempt to make an update these. + required: true + choices: [ "present", "absent" ] + type: str + path: + description: + - When creating or updating, specify the desired path of the certificate. + default: "/" + type: str + cert_chain: + description: + - The content of the CA certificate chain in PEM encoded format. + type: str + cert: + description: + - The content of the certificate body in PEM encoded format. + type: str + key: + description: + - The content of the private key in PEM encoded format. + type: str + dup_ok: + description: + - By default the module will not upload a certificate that is already uploaded into AWS. + - If I(dup_ok=True), it will upload the certificate as long as the name is unique. + - The default value for this value changed in release 5.0.0 to C(true). + default: true + type: bool + +author: + - Jonathan I. Davila (@defionscode) +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +RETURN = r""" # """ + +EXAMPLES = r""" +- name: Basic server certificate upload from local file + amazon.aws.iam_server_certificate: + name: very_ssl + state: present + cert: "{{ lookup('file', 'path/to/cert') }}" + key: "{{ lookup('file', 'path/to/key') }}" + cert_chain: "{{ lookup('file', 'path/to/certchain') }}" + +- name: Server certificate upload using key string + amazon.aws.iam_server_certificate: + name: very_ssl + state: present + path: "/a/cert/path/" + cert: "{{ lookup('file', 'path/to/cert') }}" + key: "{{ lookup('file', 'path/to/key') }}" + cert_chain: "{{ lookup('file', 'path/to/certchain') }}" + +- name: Basic rename of existing certificate + amazon.aws.iam_server_certificate: + name: very_ssl + new_name: new_very_ssl + state: present +""" + +try: + import botocore +except ImportError: + pass # Handled by HAS_BOTO + +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry + + +@AWSRetry.jittered_backoff() +def _list_server_certficates(): + paginator = client.get_paginator("list_server_certificates") + return paginator.paginate().build_full_result()["ServerCertificateMetadataList"] + + +def check_duplicate_cert(new_cert): + orig_cert_names = list(c["ServerCertificateName"] for c in _list_server_certficates()) + for cert_name in orig_cert_names: + cert = get_server_certificate(cert_name) + if not cert: + continue + cert_body = cert.get("certificate_body", None) + if not _compare_cert(new_cert, cert_body): + continue + module.fail_json( + changed=False, + msg=f"This certificate already exists under the name {cert_name} and dup_ok=False", + duplicate_cert=cert, + ) + + +def _compare_cert(cert_a, cert_b): + if not cert_a and not cert_b: + return True + if not cert_a or not cert_b: + return False + # Trim out the whitespace before comparing the certs. While this could mean + # an invalid cert 'matches' a valid cert, that's better than some stray + # whitespace breaking things + cert_a.replace("\r", "") + cert_a.replace("\n", "") + cert_a.replace(" ", "") + cert_b.replace("\r", "") + cert_b.replace("\n", "") + cert_b.replace(" ", "") + + return cert_a == cert_b + + +def update_server_certificate(current_cert): + changed = False + cert = module.params.get("cert") + cert_chain = module.params.get("cert_chain") + + if not _compare_cert(cert, current_cert.get("certificate_body", None)): + module.fail_json(msg="Modifying the certificate body is not supported by AWS") + if not _compare_cert(cert_chain, current_cert.get("certificate_chain", None)): + module.fail_json(msg="Modifying the chaining certificate is not supported by AWS") + # We can't compare keys. + + if module.check_mode: + return changed + + # For now we can't make any changes. Updates to tagging would go here and + # update 'changed' + + return changed + + +def create_server_certificate(): + cert = module.params.get("cert") + key = module.params.get("key") + cert_chain = module.params.get("cert_chain") + + if not module.params.get("dup_ok"): + check_duplicate_cert(cert) + + path = module.params.get("path") + name = module.params.get("name") + + params = dict( + ServerCertificateName=name, + CertificateBody=cert, + PrivateKey=key, + ) + + if cert_chain: + params["CertificateChain"] = cert_chain + if path: + params["Path"] = path + + if module.check_mode: + return True + + try: + client.upload_server_certificate(aws_retry=True, **params) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg=f"Failed to update server certificate {name}") + + return True + + +def rename_server_certificate(current_cert): + name = module.params.get("name") + new_name = module.params.get("new_name") + new_path = module.params.get("new_path") + + changes = dict() + + # Try to be nice, if we've already been renamed exit quietly. + if not current_cert: + current_cert = get_server_certificate(new_name) + else: + if new_name: + changes["NewServerCertificateName"] = new_name + + cert_metadata = current_cert.get("server_certificate_metadata", {}) + + if not current_cert: + module.fail_json(msg=f"Unable to find certificate {name}") + + current_path = cert_metadata.get("path", None) + if new_path and current_path != new_path: + changes["NewPath"] = new_path + + if not changes: + return False + + if module.check_mode: + return True + + try: + client.update_server_certificate(aws_retry=True, ServerCertificateName=name, **changes) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg=f"Failed to update server certificate {name}", changes=changes) + + return True + + +def delete_server_certificate(current_cert): + if not current_cert: + return False + + if module.check_mode: + return True + + name = module.params.get("name") + + try: + result = client.delete_server_certificate( + aws_retry=True, + ServerCertificateName=name, + ) + except is_boto3_error_code("NoSuchEntity"): + return None + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, msg=f"Failed to delete server certificate {name}") + + return True + + +def get_server_certificate(name): + if not name: + return None + try: + result = client.get_server_certificate( + aws_retry=True, + ServerCertificateName=name, + ) + except is_boto3_error_code("NoSuchEntity"): + return None + except ( + botocore.exceptions.ClientError, + botocore.exceptions.BotoCoreError, + ) as e: # pylint: disable=duplicate-except + module.fail_json_aws(e, msg=f"Failed to get server certificate {name}") + cert = dict(camel_dict_to_snake_dict(result.get("ServerCertificate"))) + return cert + + +def compatability_results(current_cert): + compat_results = dict() + + if not current_cert: + return compat_results + + metadata = current_cert.get("server_certificate_metadata", {}) + + if current_cert.get("certificate_body", None): + compat_results["cert_body"] = current_cert.get("certificate_body") + if current_cert.get("certificate_chain", None): + compat_results["chain_cert_body"] = current_cert.get("certificate_chain") + if metadata.get("arn", None): + compat_results["arn"] = metadata.get("arn") + if metadata.get("expiration", None): + compat_results["expiration_date"] = metadata.get("expiration") + if metadata.get("path", None): + compat_results["cert_path"] = metadata.get("path") + if metadata.get("server_certificate_name", None): + compat_results["name"] = metadata.get("server_certificate_name") + if metadata.get("upload_date", None): + compat_results["upload_date"] = metadata.get("upload_date") + + return compat_results + + +def main(): + global module + global client + + argument_spec = dict( + state=dict(required=True, choices=["present", "absent"]), + name=dict(required=True), + cert=dict(), + key=dict(no_log=True), + cert_chain=dict(), + new_name=dict(), + path=dict(default="/"), + new_path=dict(), + dup_ok=dict(type="bool", default=True), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ["new_path", "key"], + ["new_path", "cert"], + ["new_path", "cert_chain"], + ["new_name", "key"], + ["new_name", "cert"], + ["new_name", "cert_chain"], + ], + supports_check_mode=True, + ) + + client = module.client("iam", retry_decorator=AWSRetry.jittered_backoff()) + + state = module.params.get("state") + name = module.params.get("name") + path = module.params.get("path") + new_name = module.params.get("new_name") + new_path = module.params.get("new_path") + dup_ok = module.params.get("dup_ok") + + current_cert = get_server_certificate(name) + + results = dict() + if state == "absent": + changed = delete_server_certificate(current_cert) + if changed: + results["deleted_cert"] = name + else: + msg = f"Certificate with the name {name} already absent" + results["msg"] = msg + else: + if new_name or new_path: + changed = rename_server_certificate(current_cert) + if new_name: + name = new_name + updated_cert = get_server_certificate(name) + elif current_cert: + changed = update_server_certificate(current_cert) + updated_cert = get_server_certificate(name) + else: + changed = create_server_certificate() + updated_cert = get_server_certificate(name) + + results["server_certificate"] = updated_cert + compat_results = compatability_results(updated_cert) + if compat_results: + results.update(compat_results) + + module.exit_json(changed=changed, **results) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/iam_server_certificate_info.py b/plugins/modules/iam_server_certificate_info.py new file mode 100644 index 00000000000..1efc1046850 --- /dev/null +++ b/plugins/modules/iam_server_certificate_info.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = r""" +--- +module: iam_server_certificate_info +version_added: 1.0.0 +version_added_collection: community.aws +short_description: Retrieve the information of a server certificate +description: + - Retrieve the attributes of a server certificate. +author: + - "Allen Sanabria (@linuxdynasty)" +options: + name: + description: + - The name of the server certificate you are retrieving attributes for. + type: str +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +- name: Retrieve server certificate + amazon.aws.iam_server_certificate_info: + name: production-cert + register: server_cert + +- name: Fail if the server certificate name was not found + amazon.aws.iam_server_certificate_info: + name: production-cert + register: server_cert + failed_when: "{{ server_cert.results | length == 0 }}" +""" + +RETURN = r""" +server_certificate_id: + description: The 21 character certificate id + returned: success + type: str + sample: "ADWAJXWTZAXIPIMQHMJPO" +certificate_body: + description: The asn1der encoded PEM string + returned: success + type: str + sample: "-----BEGIN CERTIFICATE-----\nbunch of random data\n-----END CERTIFICATE-----" +server_certificate_name: + description: The name of the server certificate + returned: success + type: str + sample: "server-cert-name" +arn: + description: The Amazon resource name of the server certificate + returned: success + type: str + sample: "arn:aws:iam::123456789012:server-certificate/server-cert-name" +path: + description: The path of the server certificate + returned: success + type: str + sample: "/" +expiration: + description: The date and time this server certificate will expire, in ISO 8601 format. + returned: success + type: str + sample: "2017-06-15T12:00:00+00:00" +upload_date: + description: The date and time this server certificate was uploaded, in ISO 8601 format. + returned: success + type: str + sample: "2015-04-25T00:36:40+00:00" +""" + + +try: + import botocore +except ImportError: + pass # Handled by AnsibleAWSModule + +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule + + +def get_server_certs(iam, name=None): + """Retrieve the attributes of a server certificate if it exists or all certs. + Args: + iam (botocore.client.IAM): The boto3 iam instance. + + Kwargs: + name (str): The name of the server certificate. + + Basic Usage: + >>> import boto3 + >>> iam = boto3.client('iam') + >>> name = "server-cert-name" + >>> results = get_server_certs(iam, name) + { + "upload_date": "2015-04-25T00:36:40+00:00", + "server_certificate_id": "ADWAJXWTZAXIPIMQHMJPO", + "certificate_body": "-----BEGIN CERTIFICATE-----\nbunch of random data\n-----END CERTIFICATE-----", + "server_certificate_name": "server-cert-name", + "expiration": "2017-06-15T12:00:00+00:00", + "path": "/", + "arn": "arn:aws:iam::123456789012:server-certificate/server-cert-name" + } + """ + results = dict() + try: + if name: + server_certs = [iam.get_server_certificate(ServerCertificateName=name)["ServerCertificate"]] + else: + server_certs = iam.list_server_certificates()["ServerCertificateMetadataList"] + + for server_cert in server_certs: + if not name: + server_cert = iam.get_server_certificate(ServerCertificateName=server_cert["ServerCertificateName"])[ + "ServerCertificate" + ] + cert_md = server_cert["ServerCertificateMetadata"] + results[cert_md["ServerCertificateName"]] = { + "certificate_body": server_cert["CertificateBody"], + "server_certificate_id": cert_md["ServerCertificateId"], + "server_certificate_name": cert_md["ServerCertificateName"], + "arn": cert_md["Arn"], + "path": cert_md["Path"], + "expiration": cert_md["Expiration"].isoformat(), + "upload_date": cert_md["UploadDate"].isoformat(), + } + + except botocore.exceptions.ClientError: + pass + + return results + + +def main(): + argument_spec = dict( + name=dict(type="str"), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + try: + iam = module.client("iam") + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Failed to connect to AWS") + + cert_name = module.params.get("name") + results = get_server_certs(iam, cert_name) + module.exit_json(results=results) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/iam_server_certificate/aliases b/tests/integration/targets/iam_server_certificate/aliases new file mode 100644 index 00000000000..98e604ec4b3 --- /dev/null +++ b/tests/integration/targets/iam_server_certificate/aliases @@ -0,0 +1,3 @@ +cloud/aws + +iam_server_certificate_info diff --git a/tests/integration/targets/iam_server_certificate/defaults/main.yml b/tests/integration/targets/iam_server_certificate/defaults/main.yml new file mode 100644 index 00000000000..1fad2411a60 --- /dev/null +++ b/tests/integration/targets/iam_server_certificate/defaults/main.yml @@ -0,0 +1 @@ +cert_name: ansible-test-{{ tiny_prefix }} diff --git a/tests/integration/targets/iam_server_certificate/meta/main.yml b/tests/integration/targets/iam_server_certificate/meta/main.yml new file mode 100644 index 00000000000..9f37e96cd90 --- /dev/null +++ b/tests/integration/targets/iam_server_certificate/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: +- setup_remote_tmp_dir diff --git a/tests/integration/targets/iam_server_certificate/tasks/generate-certs.yml b/tests/integration/targets/iam_server_certificate/tasks/generate-certs.yml new file mode 100644 index 00000000000..f2867064ea3 --- /dev/null +++ b/tests/integration/targets/iam_server_certificate/tasks/generate-certs.yml @@ -0,0 +1,60 @@ +################################################ +# Setup SSL certs to store in IAM +################################################ +- name: Generate SSL Keys + community.crypto.openssl_privatekey: + path: '{{ remote_tmp_dir }}/{{ item }}-key.pem' + size: 2048 + loop: + - ca + - cert1 + - cert2 + +- name: Generate CSRs + community.crypto.openssl_csr: + path: '{{ remote_tmp_dir }}/{{ item }}.csr' + privatekey_path: '{{ remote_tmp_dir }}/{{ item }}-key.pem' + common_name: '{{ item }}.ansible.test' + subject_alt_name: DNS:{{ item }}.ansible.test + basic_constraints: + - CA:TRUE + loop: + - ca + - cert1 + - cert2 + +- name: Self-sign the "root" + community.crypto.x509_certificate: + provider: selfsigned + path: '{{ remote_tmp_dir }}/ca.pem' + privatekey_path: '{{ remote_tmp_dir }}/ca-key.pem' + csr_path: '{{ remote_tmp_dir }}/ca.csr' +- name: Sign the intermediate cert + community.crypto.x509_certificate: + provider: ownca + path: '{{ remote_tmp_dir }}/cert1.pem' + csr_path: '{{ remote_tmp_dir }}/cert1.csr' + ownca_path: '{{ remote_tmp_dir }}/ca.pem' + ownca_privatekey_path: '{{ remote_tmp_dir }}/ca-key.pem' +- name: Sign the end-cert + community.crypto.x509_certificate: + provider: ownca + path: '{{ remote_tmp_dir }}/cert2.pem' + csr_path: '{{ remote_tmp_dir }}/cert2.csr' + ownca_path: '{{ remote_tmp_dir }}/cert1.pem' + ownca_privatekey_path: '{{ remote_tmp_dir }}/cert1-key.pem' +- name: Re-Sign the end-cert + community.crypto.x509_certificate: + provider: ownca + path: '{{ remote_tmp_dir }}/cert2-new.pem' + csr_path: '{{ remote_tmp_dir }}/cert2.csr' + ownca_path: '{{ remote_tmp_dir }}/cert1.pem' + ownca_privatekey_path: '{{ remote_tmp_dir }}/cert1-key.pem' +- set_fact: + path_ca_cert: '{{ remote_tmp_dir }}/ca.pem' + path_ca_key: '{{ remote_tmp_dir }}/ca-key.pem' + path_intermediate_cert: '{{ remote_tmp_dir }}/cert1.pem' + path_intermediate_key: '{{ remote_tmp_dir }}/cert1-key.pem' + path_cert_a: '{{ remote_tmp_dir }}/cert2.pem' + path_cert_b: '{{ remote_tmp_dir }}/cert2-new.pem' + path_cert_key: '{{ remote_tmp_dir }}/cert2-key.pem' diff --git a/tests/integration/targets/iam_server_certificate/tasks/main.yml b/tests/integration/targets/iam_server_certificate/tasks/main.yml new file mode 100644 index 00000000000..95d4603bc4d --- /dev/null +++ b/tests/integration/targets/iam_server_certificate/tasks/main.yml @@ -0,0 +1,571 @@ +# iam_server_certificate integration tests +# +# Note: +# +# AWS APIs only support renaming and/or updating +# the *path*. +# +# It is not possible to update the cert/key/chain +# without deleting the ceritifate +# +- module_defaults: + group/aws: + access_key: '{{ aws_access_key }}' + secret_key: '{{ aws_secret_key }}' + session_token: '{{ security_token | default(omit) }}' + region: '{{ aws_region }}' + block: + ################################################ + + - name: Test with no args + iam_server_certificate: {} + ignore_errors: true + register: no_args + - assert: + that: + - no_args is failed + - no_args.msg.startswith('missing required arguments') + + ################################################ + + - include_tasks: generate-certs.yml + - set_fact: + cert_a_data: '{{ lookup("file", path_cert_a) }}' + cert_b_data: '{{ lookup("file", path_cert_b) }}' + chain_cert_data: '{{ lookup("file", path_intermediate_cert) }}' + - name: Create Certificate - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + register: create_cert + check_mode: true + - name: check result - Create Certificate - check_mode + assert: + that: + - create_cert is successful + - create_cert is changed + + - name: Create Certificate + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + register: create_cert + - name: check result - Create Certificate + assert: + that: + - create_cert is successful + - create_cert is changed + - '"arn" in create_cert' + - '"cert_body" in create_cert' + - '"cert_path" in create_cert' + - '"expiration_date" in create_cert' + - '"name" in create_cert' + - '"upload_date" in create_cert' + - create_cert.arn.startswith('arn:aws') + - create_cert.arn.endswith(cert_name) + - create_cert.name == cert_name + - create_cert.cert_path == '/' + - create_cert.cert_body == cert_a_data + + - name: Create Certificate - idempotency - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + register: create_cert + check_mode: true + - name: check result - Create Certificate - idempotency + assert: + that: + - create_cert is successful + - create_cert is not changed + + - name: Create Certificate - idempotency + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + register: create_cert + - name: check result - Create Certificate - idempotency + assert: + that: + - create_cert is successful + - create_cert is not changed + - '"arn" in create_cert' + - '"cert_body" in create_cert' + - '"cert_path" in create_cert' + - '"expiration_date" in create_cert' + - '"name" in create_cert' + - '"upload_date" in create_cert' + - create_cert.arn.startswith('arn:aws') + - create_cert.arn.endswith(cert_name) + - create_cert.name == cert_name + - create_cert.cert_path == '/' + - create_cert.cert_body == cert_a_data + + ################################################ + + # Module explicitly blocks updating certs + - name: Update Certificate - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_b_data }}' + register: update_cert + ignore_errors: true + check_mode: true + - name: check result - Update Certificate - check_mode + assert: + that: + - update_cert is failed + - '"not supported" in update_cert.msg' + + - name: Update Certificate + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_b_data }}' + register: update_cert + ignore_errors: true + - name: check result - Update Certificate + assert: + that: + - update_cert is failed + - '"not supported" in update_cert.msg' + + - name: Update Chaining Certificate - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert_chain: '{{ chain_cert_data }}' + register: update_cert + ignore_errors: true + check_mode: true + - name: check result - Update Chaining Certificate - check_mode + assert: + that: + - update_cert is failed + - '"not supported" in update_cert.msg' + + - name: Update Chaining Certificate + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert_chain: '{{ chain_cert_data }}' + register: update_cert + ignore_errors: true + - name: check result - Update Chaining Certificate + assert: + that: + - update_cert is failed + - '"not supported" in update_cert.msg' + + # AWS APIs provide no mechanism for accessing + # any information about the key, and as such + # the module can't tell if a key was updated. + + ################################################ + + - name: Delete certificate - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: absent + register: delete_cert + check_mode: true + - name: check result - Delete certificate - check_mode + assert: + that: + - delete_cert is successful + - delete_cert is changed + + - name: Delete certificate + iam_server_certificate: + name: '{{ cert_name }}' + state: absent + register: delete_cert + - name: check result - Delete certificate + assert: + that: + - delete_cert is successful + - delete_cert is changed + + - name: Delete certificate - idempotency - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: absent + register: delete_cert + check_mode: true + - name: check result - Delete certificate - check_mode + assert: + that: + - delete_cert is successful + - delete_cert is not changed + + - name: Delete certificate - idempotency + iam_server_certificate: + name: '{{ cert_name }}' + state: absent + register: delete_cert + - name: check result - Delete certificate + assert: + that: + - delete_cert is successful + - delete_cert is not changed + + ################################################ + + - name: Create Certificate with Chain and path - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + cert_chain: '{{ chain_cert_data }}' + path: /ansible-test-example/ + register: create_cert + check_mode: true + - name: check result - Create Certificate with Chain and path - check_mode + assert: + that: + - create_cert is successful + - create_cert is changed + + - name: Create Certificate with Chain and path + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + cert_chain: '{{ chain_cert_data }}' + path: /ansible-test-example/ + register: create_cert + - name: check result - Create Certificate with Chain and path + assert: + that: + - create_cert is successful + - create_cert is changed + - '"arn" in create_cert' + - '"cert_body" in create_cert' + - '"cert_path" in create_cert' + - '"expiration_date" in create_cert' + - '"name" in create_cert' + - '"upload_date" in create_cert' + - create_cert.arn.startswith('arn:aws') + - create_cert.arn.endswith(cert_name) + - create_cert.name == cert_name + - create_cert.cert_path == '/ansible-test-example/' + - create_cert.cert_body == cert_a_data + + - name: Create Certificate with Chain and path - idempotency - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + cert_chain: '{{ chain_cert_data }}' + path: /ansible-test-example/ + register: create_cert + check_mode: true + - name: check result - Create Certificate with Chain and path - idempotency - check_mode + assert: + that: + - create_cert is successful + - create_cert is not changed + + - name: Create Certificate with Chain and path - idempotency + iam_server_certificate: + name: '{{ cert_name }}' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + cert_chain: '{{ chain_cert_data }}' + path: /ansible-test-example/ + register: create_cert + - name: check result - Create Certificate with Chain and path - idempotency + assert: + that: + - create_cert is successful + - create_cert is not changed + - '"arn" in create_cert' + - '"cert_body" in create_cert' + - '"cert_path" in create_cert' + - '"expiration_date" in create_cert' + - '"name" in create_cert' + - '"upload_date" in create_cert' + - create_cert.arn.startswith('arn:aws') + - create_cert.arn.endswith(cert_name) + - create_cert.name == cert_name + - create_cert.cert_path == '/ansible-test-example/' + - create_cert.cert_body == cert_a_data + + ################################################ + + - name: Create Certificate with identical cert dup_ok=False - check_mode + iam_server_certificate: + name: '{{ cert_name }}-duplicate' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + dup_ok: false + register: create_duplicate + ignore_errors: true + - name: check result - Create Certificate with identical cert - check_mode + assert: + that: + - create_duplicate is failed + + - name: Create Certificate with identical cert dup_ok=False + iam_server_certificate: + name: '{{ cert_name }}-duplicate' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + dup_ok: false + register: create_duplicate + ignore_errors: true + - name: check result - Create Certificate with identical cert + assert: + that: + - create_duplicate is failed + + ################################################ + + - name: Create Certificate with forced identical cert - check_mode + iam_server_certificate: + name: '{{ cert_name }}-duplicate' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + register: create_duplicate + check_mode: true + - name: check result - Create Certificate with forced identical cert - check_mode + assert: + that: + - create_duplicate is successful + - create_duplicate is changed + + - name: Create Certificate with forced identical cert + iam_server_certificate: + name: '{{ cert_name }}-duplicate' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + register: create_duplicate + - name: check result - Create Certificate with forced identical cert + assert: + that: + - create_duplicate is successful + - create_duplicate is changed + - '"arn" in create_duplicate' + - '"cert_body" in create_duplicate' + - '"cert_path" in create_duplicate' + - '"expiration_date" in create_duplicate' + - '"name" in create_duplicate' + - '"upload_date" in create_duplicate' + - create_duplicate.arn.startswith('arn:aws') + - create_duplicate.arn.endswith('-duplicate') + - create_duplicate.name.endswith('duplicate') + - create_duplicate.cert_path == '/' + - create_duplicate.cert_body == cert_a_data + + - name: Create Certificate with forced identical cert - idempotency - check_mode + iam_server_certificate: + name: '{{ cert_name }}-duplicate' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + register: create_duplicate + check_mode: true + - name: check result - Create Certificate with forced identical cert - idempotency + - check_mode + assert: + that: + - create_duplicate is successful + - create_duplicate is not changed + + - name: Create Certificate with forced identical cert - idempotency + iam_server_certificate: + name: '{{ cert_name }}-duplicate' + state: present + cert: '{{ cert_a_data }}' + key: '{{ lookup("file", path_cert_key) }}' + register: create_duplicate + - name: check result - Create Certificate with forced identical cert - idempotency + assert: + that: + - create_duplicate is successful + - create_duplicate is not changed + - '"arn" in create_duplicate' + - '"cert_body" in create_duplicate' + - '"cert_path" in create_duplicate' + - '"expiration_date" in create_duplicate' + - '"name" in create_duplicate' + - '"upload_date" in create_duplicate' + - create_duplicate.arn.startswith('arn:aws') + - create_duplicate.arn.endswith('-duplicate') + - create_duplicate.name.endswith('duplicate') + - create_duplicate.cert_path == '/' + - create_duplicate.cert_body == cert_a_data + + ################################################ + + - name: Update certificate path - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: present + path: /ansible-test-example/ + new_path: /ansible-test-path/ + register: update_path + check_mode: true + - name: check result - Update certificate path - check_mode + assert: + that: + - update_path is successful + - update_path is changed + + - name: Update certificate path + iam_server_certificate: + name: '{{ cert_name }}' + state: present + path: /ansible-test-example/ + new_path: /ansible-test-path/ + register: update_path + - name: check result - Update certificate path + assert: + that: + - update_path is successful + - update_path is changed + - '"arn" in update_path' + - '"cert_body" in update_path' + - '"cert_path" in update_path' + - '"expiration_date" in update_path' + - '"name" in update_path' + - '"upload_date" in update_path' + - update_path.arn.startswith('arn:aws') + - update_path.arn.endswith(cert_name) + - update_path.name == cert_name + - update_path.cert_path == '/ansible-test-path/' + - update_path.cert_body == cert_a_data + + - name: Update certificate path - idempotency - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + state: present + path: /ansible-test-example/ + new_path: /ansible-test-path/ + register: update_path + check_mode: true + - name: check result - Update certificate path - idempotency - check_mode + assert: + that: + - update_path is successful + - update_path is not changed + + - name: Update certificate path - idempotency + iam_server_certificate: + name: '{{ cert_name }}' + state: present + path: /ansible-test-example/ + new_path: /ansible-test-path/ + register: update_path + - name: check result - Update certificate path - idempotency + assert: + that: + - update_path is successful + - update_path is not changed + + ################################################ + + - name: Update certificate name - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + new_name: '{{ cert_name }}-renamed' + state: present + register: update_name + check_mode: true + - name: check result - Update certificate name - check_mode + assert: + that: + - update_name is successful + - update_name is changed + + - name: Update certificate name + iam_server_certificate: + name: '{{ cert_name }}' + new_name: '{{ cert_name }}-renamed' + state: present + register: update_name + - name: check result - Update certificate name + assert: + that: + - update_name is successful + - update_name is changed + - '"arn" in update_name' + - '"cert_body" in update_name' + - '"cert_path" in update_name' + - '"expiration_date" in update_name' + - '"name" in update_name' + - '"upload_date" in update_name' + - update_name.arn.startswith('arn:aws') + - update_name.arn.endswith('-renamed') + - update_name.name.endswith('renamed') + - update_name.cert_path == '/ansible-test-path/' + - update_name.cert_body == cert_a_data + + - name: Update certificate name - idempotency - check_mode + iam_server_certificate: + name: '{{ cert_name }}' + new_name: '{{ cert_name }}-renamed' + state: present + register: update_name + check_mode: true + - name: check result - Update certificate name - idempotency - check_mode + assert: + that: + - update_name is successful + - update_name is not changed + + - name: Update certificate name - idempotency + iam_server_certificate: + name: '{{ cert_name }}' + new_name: '{{ cert_name }}-renamed' + state: present + register: update_name + - name: check result - Update certificate name - idempotency + assert: + that: + - update_name is successful + - update_name is not changed + - '"arn" in update_name' + - '"cert_body" in update_name' + - '"cert_path" in update_name' + - '"expiration_date" in update_name' + - '"name" in update_name' + - '"upload_date" in update_name' + - update_name.arn.startswith('arn:aws') + - update_name.arn.endswith('-renamed') + - update_name.name.endswith('renamed') + - update_name.cert_path == '/ansible-test-path/' + - update_name.cert_body == cert_a_data + + always: + + ################################################ + # TEARDOWN STARTS HERE + ################################################ + + - name: Delete certificate + iam_server_certificate: + name: '{{ item }}' + state: absent + ignore_errors: true + loop: + - '{{ cert_name }}' + - '{{ cert_name }}-renamed' + - '{{ cert_name }}-duplicate'