From 2df6735dcf321a939044abfd795466a054dd70a1 Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Wed, 10 Jan 2018 16:53:31 +0100 Subject: [PATCH] aci_aaa_user_certificate: Add a certificate to an AAA user (#34602) * aci_user_certificate: Add a certificate to a user A new ACI module to add a X.509 certificate to a user. * Add integration tests --- .../network/aci/aci_aaa_user_certificate.py | 174 ++++++++++++++++++ .../targets/aci_aaa_user_certificate/aliases | 0 .../aci_aaa_user_certificate/pki/admin.crt | 14 ++ .../aci_aaa_user_certificate/pki/admin.key | 16 ++ .../aci_aaa_user_certificate/tasks/main.yml | 136 ++++++++++++++ 5 files changed, 340 insertions(+) create mode 100644 lib/ansible/modules/network/aci/aci_aaa_user_certificate.py create mode 100644 test/integration/targets/aci_aaa_user_certificate/aliases create mode 100644 test/integration/targets/aci_aaa_user_certificate/pki/admin.crt create mode 100644 test/integration/targets/aci_aaa_user_certificate/pki/admin.key create mode 100644 test/integration/targets/aci_aaa_user_certificate/tasks/main.yml diff --git a/lib/ansible/modules/network/aci/aci_aaa_user_certificate.py b/lib/ansible/modules/network/aci/aci_aaa_user_certificate.py new file mode 100644 index 0000000000..ff8d1ab26f --- /dev/null +++ b/lib/ansible/modules/network/aci/aci_aaa_user_certificate.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (dagwieers) +# 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 + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = r''' +--- +module: aci_aaa_user_certificate +short_description: Manage AAA user certificates (aaa:UserCert) +description: +- Manage AAA user and appuser certificates. +- More information from the internal APIC class I(aaa:UserCert) at + U(https://developer.cisco.com/site/aci/docs/apis/apic-mim-ref/). +author: +- Dag Wieers (@dagwieers) +version_added: '2.5' +notes: +- The C(user) must exist before using this module in your playbook. + The M(aci_user) module can be used for this. +options: + user: + description: + - The name of the user to add a certificate to. + certificate: + description: + - The PEM format public key extracted from the X.509 certificate. + aliases: [ cert_data, certificate_data ] + certificate_name: + description: + - The name of the user certificate entry in ACI. + aliases: [ cert_name ] + user_type: + description: + - Whether this is a normal user or an appuser. + choices: [ user, userapp ] + default: user + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: aci +''' + +EXAMPLES = r''' +- name: Add a certificate to user + aci_aaa_user_certificate: + hostname: apic + username: admin + password: SomeSecretPassword + user: admin + certificate_name: admin + certificate_data: '{{ lookup("file", "pki/admin.crt") }}' + state: present + +- name: Remove a certificate of a user + aci_aaa_user_certificate: + hostname: apic + username: admin + password: SomeSecretPassword + user: admin + certificate_name: admin + state: absent + +- name: Query a certificate of a user + aci_aaa_user_certificate: + hostname: apic + username: admin + password: SomeSecretPassword + user: admin + certificate_name: admin + state: query + +- name: Query all certificates of a user + aci_aaa_user_certificate: + hostname: apic + username: admin + password: SomeSecretPassword + user: admin + state: query +''' + +RETURN = r''' # ''' + +from ansible.module_utils.network.aci.aci import ACIModule, aci_argument_spec +from ansible.module_utils.basic import AnsibleModule + +ACI_MAPPING = dict( + appuser=dict( + aci_class='aaaAppUser', + aci_mo='userext/appuser-', + ), + user=dict( + aci_class='aaaUser', + aci_mo='userext/user-', + ), +) + + +def main(): + argument_spec = aci_argument_spec + argument_spec.update( + certificate=dict(type='str', aliases=['certificate_data', 'data']), + certificate_name=dict(type='str', aliases=['cert_name']), + state=dict(type='str', default='present', choices=['absent', 'present', 'query']), + user=dict(type='str', required=True), + user_type=dict(type='str', default='user', choices=['appuser', 'user']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ['state', 'absent', ['user', 'certificate_name']], + ['state', 'present', ['user', 'certificate', 'certificate_name']], + ], + ) + + certificate = module.params['certificate'] + certificate_name = module.params['certificate_name'] + state = module.params['state'] + user = module.params['user'] + user_type = module.params['user_type'] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=ACI_MAPPING[user_type]['aci_class'], + aci_rn=ACI_MAPPING[user_type]['aci_mo'] + user, + filter_target='eq({0}.name, "{1}")'.format(ACI_MAPPING[user_type]['aci_class'], user), + module_object=user, + ), + subclass_1=dict( + aci_class='aaaUserCert', + aci_rn='usercert-{0}'.format(certificate_name), + filter_target='eq(aaaUserCert.name, "{0}")'.format(certificate_name), + module_object=certificate_name, + ), + ) + aci.get_existing() + + if state == 'present': + # Filter out module params with null values + aci.payload( + aci_class='aaaUserCert', + class_config=dict( + data=certificate, + name=certificate_name, + ), + ) + + # Generate config diff which will be used as POST request body + aci.get_diff(aci_class='aaaUserCert') + + # Submit changes if module not in check_mode and the proposed is different than existing + aci.post_config() + + elif state == 'absent': + aci.delete_config() + + module.exit_json(**aci.result) + + +if __name__ == "__main__": + main() diff --git a/test/integration/targets/aci_aaa_user_certificate/aliases b/test/integration/targets/aci_aaa_user_certificate/aliases new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/integration/targets/aci_aaa_user_certificate/pki/admin.crt b/test/integration/targets/aci_aaa_user_certificate/pki/admin.crt new file mode 100644 index 0000000000..cfac5531e9 --- /dev/null +++ b/test/integration/targets/aci_aaa_user_certificate/pki/admin.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICODCCAaGgAwIBAgIJAIt8XMntue0VMA0GCSqGSIb3DQEBCwUAMDQxDjAMBgNV +BAMMBUFkbWluMRUwEwYDVQQKDAxZb3VyIENvbXBhbnkxCzAJBgNVBAYTAlVTMCAX +DTE4MDEwOTAwNTk0NFoYDzIxMTcxMjE2MDA1OTQ0WjA0MQ4wDAYDVQQDDAVBZG1p +bjEVMBMGA1UECgwMWW91ciBDb21wYW55MQswCQYDVQQGEwJVUzCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEAohG/7axtt7CbSaMP7r+2mhTKbNgh0Ww36C7Ta14i +v+VmLyKkQHnXinKGhp6uy3Nug+15a+eIu7CrgpBVMQeCiWfsnwRocKcQJWIYDrWl +XHxGQn31yYKR6mylE7Dcj3rMFybnyhezr5D8GcP85YRPmwG9H2hO/0Y1FUnWu9Iw +AQkCAwEAAaNQME4wHQYDVR0OBBYEFD0jLXfpkrU/ChzRvfruRs/fy1VXMB8GA1Ud +IwQYMBaAFD0jLXfpkrU/ChzRvfruRs/fy1VXMAwGA1UdEwQFMAMBAf8wDQYJKoZI +hvcNAQELBQADgYEAOmvre+5tgZ0+F3DgsfxNQqLTrGiBgGCIymPkP/cBXXkNuJyl +3ac7tArHQc7WEA4U2R2rZbEq8FC3UJJm4nUVtCPvEh3G9OhN2xwYev79yt6pIn/l +KU0Td2OpVyo0eLqjoX5u2G90IBWzhyjFbo+CcKMrSVKj1YOdG0E3OuiJf00= +-----END CERTIFICATE----- diff --git a/test/integration/targets/aci_aaa_user_certificate/pki/admin.key b/test/integration/targets/aci_aaa_user_certificate/pki/admin.key new file mode 100644 index 0000000000..63bb00cc00 --- /dev/null +++ b/test/integration/targets/aci_aaa_user_certificate/pki/admin.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKIRv+2sbbewm0mj +D+6/tpoUymzYIdFsN+gu02teIr/lZi8ipEB514pyhoaerstzboPteWvniLuwq4KQ +VTEHgoln7J8EaHCnECViGA61pVx8RkJ99cmCkepspROw3I96zBcm58oXs6+Q/BnD +/OWET5sBvR9oTv9GNRVJ1rvSMAEJAgMBAAECgYByu3QO0qF9h7X3JEu0Ld4cKBnB +giQ2uJC/et7KxIJ/LOvw9GopBthyt27KwG1ntBkJpkTuAaQHkyNns7vLkNB0S0IR ++owVFEcKYq9VCHTaiQU8TDp24gN+yPTrpRuH8YhDVq5SfVdVuTMgHVQdj4ya4VlF +Gj+a7+ipxtGiLsVGrQJBAM7p0Fm0xmzi+tBOASUAcVrPLcteFIaTBFwfq16dm/ON +00Khla8Et5kMBttTbqbukl8mxFjBEEBlhQqb6EdQQ0sCQQDIhHx1a9diG7y/4DQA +4KvR3FCYwP8PBORlSamegzCo+P1OzxiEo0amX7yQMA5UyiP/kUsZrme2JBZgna8S +p4R7AkEAr7rMhSOPUnMD6V4WgsJ5g1Jp5kqkzBaYoVUUSms5RASz4+cwJVCwTX91 +Y1jcpVIBZmaaY3a0wrx13ajEAa0dOQJBAIpjnb4wqpsEh7VpmJqOdSdGxb1XXfFQ +sA0T1OQYqQnFppWwqrxIL+d9pZdiA1ITnNqyvUFBNETqDSOrUHwwb2cCQGArE+vu +ffPUWQ0j+fiK+covFG8NL7H+26NSGB5+Xsn9uwOGLj7K/YT6CbBtr9hJiuWjM1Al +0V4ltlTuu2mTMaw= +-----END PRIVATE KEY----- diff --git a/test/integration/targets/aci_aaa_user_certificate/tasks/main.yml b/test/integration/targets/aci_aaa_user_certificate/tasks/main.yml new file mode 100644 index 0000000000..121df628b4 --- /dev/null +++ b/test/integration/targets/aci_aaa_user_certificate/tasks/main.yml @@ -0,0 +1,136 @@ +# Test code for the ACI modules +# Copyright: (c) 2017, Dag Wieers (dagwieers) +# +# GNU General Public License v3.0+ (see COPYING 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 + + +# CLEAN ENVIRONMENT +- name: Remove any pre-existing certificate + aci_aaa_user_certificate: &cert_absent + hostname: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + use_proxy: no + validate_certs: no + user: admin + certificate_name: admin + state: absent + delegate_to: localhost + + +# ADD USER CERTIFICATE +- name: Add user certificate (check_mode) + aci_aaa_user_certificate: &cert_present + hostname: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + use_proxy: no + validate_certs: no + user: admin + certificate_name: admin + certificate: "{{ lookup('file', 'pki/admin.crt') }}" + state: present + check_mode: yes + delegate_to: localhost + register: cm_add_cert + +- name: Add user certificate (normal mode) + aci_aaa_user_certificate: *cert_present + delegate_to: localhost + register: nm_add_cert + +- name: Add user certificate again (check mode) + aci_aaa_user_certificate: *cert_present + check_mode: yes + delegate_to: localhost + register: cm_add_cert_again + +- name: Add user certificate again (normal mode) + aci_aaa_user_certificate: *cert_present + delegate_to: localhost + register: nm_add_cert_again + +- name: Verify add_cert + assert: + that: + - cm_add_cert.changed == nm_add_cert.changed == true + - cm_add_cert_again.changed == nm_add_cert_again.changed == false + + +# QUERY ALL USER CERTIFICATES +- name: Query all user certificates (check_mode) + aci_aaa_user_certificate: &cert_query + hostname: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + use_proxy: no + validate_certs: no + user: admin + state: query + check_mode: yes + delegate_to: localhost + register: cm_query_all_certs + +- name: Query all user certificates (normal mode) + aci_aaa_user_certificate: *cert_query + delegate_to: localhost + register: nm_query_all_certs + +- name: Verify query_all_certs + assert: + that: + - cm_query_all_certs.changed == nm_query_all_certs.changed == false + # NOTE: Order of certs is not stable between calls + #- cm_query_all_certs == nm_query_all_certs + + +# QUERY OUR USER CERTIFICATE +- name: Query our certificate (check_mode) + aci_aaa_user_certificate: + <<: *cert_query + certificate_name: admin + check_mode: yes + register: cm_query_cert + +- name: Query our certificate (normal mode) + aci_aaa_user_certificate: + <<: *cert_query + certificate_name: admin + register: nm_query_cert + +- name: Verify query_cert + assert: + that: + - cm_query_cert.changed == nm_query_cert.changed == false + - cm_query_cert == nm_query_cert + + +# REMOVE CERTIFICATE +- name: Remove certificate (check_mode) + aci_tenant: *cert_absent + check_mode: yes + register: cm_remove_cert + +- name: Remove certificate (normal mode) + aci_tenant: *cert_absent + register: nm_remove_cert + +- name: Remove certificate again (check_mode) + aci_tenant: *cert_absent + check_mode: yes + register: cm_remove_cert_again + +- name: Remove certificate again (normal mode) + aci_tenant: *cert_absent + register: nm_remove_cert_again + +- name: Verify remove_cert + assert: + that: + - cm_remove_cert.changed == nm_remove_cert.changed == true + - cm_remove_cert_again.changed == nm_remove_cert_again.changed == false