From ddb535952963bd9225b1875e75bb809ba8b24211 Mon Sep 17 00:00:00 2001 From: akinross Date: Mon, 23 Oct 2023 14:10:32 +0200 Subject: [PATCH] [minor_change] add support for configuration of system global aes passphrase encryption with aci_system_global_aes_passphrase_encryption module --- ...system_global_aes_passphrase_encryption.py | 253 ++++++++++++++++++ .../aliases | 2 + .../tasks/main.yml | 84 ++++++ 3 files changed, 339 insertions(+) create mode 100644 plugins/modules/aci_system_global_aes_passphrase_encryption.py create mode 100644 tests/integration/targets/aci_system_global_aes_passphrase_encryption/aliases create mode 100644 tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks/main.yml diff --git a/plugins/modules/aci_system_global_aes_passphrase_encryption.py b/plugins/modules/aci_system_global_aes_passphrase_encryption.py new file mode 100644 index 000000000..5657baedb --- /dev/null +++ b/plugins/modules/aci_system_global_aes_passphrase_encryption.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_system_global_aes_passphrase_encryption +short_description: Manage Global AES Passphrase Encryption Settings (pki:ExportEncryptionKey) +description: +- Manage Global AES Passphrase Encryption Settings on Cisco ACI fabrics. +options: + passphrase: + description: + - The AES passphrase to use for configuration export encryption. + - This cannot be modified once in place on the APIC. To modify an existing passphrase, you must delete it by sending a request with state C(absent). + - The value of the passphrase will not be shown in the results of a C(query). + type: str + enable: + description: + - Whether to enable strong encryption. + - The APIC defaults to C(false) when unset during creation. + - Note that this will be set back to False when deleting an existing passphrase. + type: bool + state: + description: + - Use C(present) to create a passphrase or to change the enable setting. + - Use C(absent) to delete the existing passphrase. + - Use C(query) for showing current configuration. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(pki:ExportEncryptionKey). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Enable encryption with a passphrase + cisco.aci.aci_system_global_aes_passphrase_encryption: + host: apic + username: admin + password: SomeSecretPassword + passphrase: ansible_passphrase + enable: yes + state: present + delegate_to: localhost + +- name: Query passphrase settings + cisco.aci.aci_system_global_aes_passphrase_encryption: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Clear encryption key + cisco.aci.aci_system_global_aes_passphrase_encryption: + host: apic + username: admin + password: SomeSecretPassword + state: absent + delegate_to: localhost + +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + passphrase=dict(type="str", no_log=True), + enable=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + aci = ACIModule(module) + + passphrase = module.params.get("passphrase") + enable = aci.boolean(module.params.get("enable")) + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="pkiExportEncryptionKey", + aci_rn="exportcryptkey", + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="pkiExportEncryptionKey", + class_config=dict( + passphrase=passphrase, + strongEncryptionEnabled=enable, + ), + ) + + aci.get_diff(aci_class="pkiExportEncryptionKey") + + aci.post_config() + + elif state == "absent": + aci.payload( + aci_class="pkiExportEncryptionKey", + class_config=dict( + clearEncryptionKey="yes", + ), + ) + + aci.get_diff(aci_class="pkiExportEncryptionKey") + + aci.post_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_system_global_aes_passphrase_encryption/aliases b/tests/integration/targets/aci_system_global_aes_passphrase_encryption/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_system_global_aes_passphrase_encryption/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks/main.yml b/tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks/main.yml new file mode 100644 index 000000000..732a4d4f3 --- /dev/null +++ b/tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks/main.yml @@ -0,0 +1,84 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +# SET VARS +- name: Set vars + set_fact: + aci_info: &aci_info + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will skip execution for cloud sites + block: + + - name: Clear existing passphrase + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: absent + + - name: Set passphrase + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + passphrase: ansible_passphrase + state: present + + - name: Query key configured + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: query + register: query_passphrase + + - name: Enable encryption + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + enable: yes + state: present + + - name: Query encryption enabled + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: query + register: query_encryption + + - name: Verify passphrase and encryption settings + ansible.builtin.assert: + that: + - query_passphrase.current.0.pkiExportEncryptionKey.attributes.keyConfigured == "yes" + - query_passphrase.current.0.pkiExportEncryptionKey.attributes.strongEncryptionEnabled == "no" + - query_encryption.current.0.pkiExportEncryptionKey.attributes.keyConfigured == "yes" + - query_encryption.current.0.pkiExportEncryptionKey.attributes.strongEncryptionEnabled == "yes" + + # CLEAR PASSPHRASE + - name: Clear encryption key + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: absent + + - name: Query cleared encryption key + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: query + register: query_deletion + + - name: Verify encryption key is deleted + ansible.builtin.assert: + that: + - query_deletion.current.0.pkiExportEncryptionKey.attributes.keyConfigured == "no" + - query_deletion.current.0.pkiExportEncryptionKey.attributes.strongEncryptionEnabled == "no"