class NetAppOntapSecurityCertificates(object):
    ''' object initialize and class methods '''

    def __init__(self):
        self.use_rest = False
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(dict(
            common_name=dict(required=False, type='str'),
            name=dict(required=False, type='str'),
            state=dict(required=False, choices=['present', 'absent'], default='present'),
            type=dict(required=False, choices=['client', 'server', 'client_ca', 'server_ca', 'root_ca']),
            svm=dict(required=False, type='str', aliases=['vserver']),
            public_certificate=dict(required=False, type='str'),
            private_key=dict(required=False, type='str'),
            signing_request=dict(required=False, type='str'),
            expiry_time=dict(required=False, type='str'),
            key_size=dict(required=False, type='int'),
            hash_function=dict(required=False, type='str'),
            intermediate_certificates=dict(required=False, type='list', elements='str'),
            ignore_name_if_not_supported=dict(required=False, type='bool', default=True)
        ))

        self.module = AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=True
        )

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

        if self.parameters.get('name') is None:
            if self.parameters.get('common_name') is None or self.parameters.get('type') is None:
                error = "'name' or ('common_name' and 'type') are required parameters."
                self.module.fail_json(msg=error)

        # ONTAP 9.6 and 9.7 do not support name.  We'll change this to True if we detect an issue.
        self.ignore_name_param = False

        # API should be used for ONTAP 9.6 or higher
        self.rest_api = OntapRestAPI(self.module)
        if self.rest_api.is_rest():
            self.use_rest = True
        else:
            self.module.fail_json(msg=self.rest_api.requires_ontap_9_6('na_ontap_security_certificates'))

    def get_certificate(self):
        """
        Fetch uuid if certificate exists.
        NOTE: because of a bug in ONTAP 9.6 and 9.7, name is not supported. We are
        falling back to using common_name and type, but unicity is not guaranteed.
        :return:
            Dictionary if certificate with same name is found
            None if not found
        """
        error = "'name' or ('common_name', 'type') are required."
        for key in ('name', 'common_name'):
            if self.parameters.get(key) is None:
                continue
            data = {'fields': 'uuid',
                    key: self.parameters[key],
                    }
            if self.parameters.get('svm') is not None:
                data['svm.name'] = self.parameters['svm']
            else:
                data['scope'] = 'cluster'
            if key == 'common_name':
                if self.parameters.get('type') is not None:
                    data['type'] = self.parameters['type']
                else:
                    error = "When using 'common_name', 'type' is required."
                    break

            api = "security/certificates"
            message, error = self.rest_api.get(api, data)
            if error:
                try:
                    name_not_supported_error = (key == 'name') and (error['message'] == 'Unexpected argument "name".')
                except (KeyError, TypeError):
                    name_not_supported_error = False
                if name_not_supported_error:
                    if self.parameters['ignore_name_if_not_supported'] and self.parameters.get('common_name') is not None:
                        # let's attempt a retry using common_name
                        self.ignore_name_param = True
                        continue
                    error = "ONTAP 9.6 and 9.7 do not support 'name'.  Use 'common_name' and 'type' as a work-around."
            # report success, or any other error as is
            break

        if error:
            self.module.fail_json(msg='Error calling API: %s - %s' % (api, error))

        if len(message['records']) == 1:
            return message['records'][0]
        if len(message['records']) > 1:
            error = 'Duplicate records with same common_name are preventing safe operations: %s' % repr(message)
            self.module.fail_json(msg=error)
        return None

    def create_or_install_certificate(self):
        """
        Create or install certificate
        :return: message (should be empty dict)
        """
        required_keys = ['type', 'common_name']
        optional_keys = ['public_certificate', 'private_key', 'expiry_time', 'key_size', 'hash_function']
        if not self.ignore_name_param:
            optional_keys.append('name')
        # special key: svm

        if not set(required_keys).issubset(set(self.parameters.keys())):
            self.module.fail_json(msg='Error creating or installing certificate: one or more of the following options are missing: %s'
                                  % (', '.join(required_keys)))

        data = dict()
        if self.parameters.get('svm') is not None:
            data['svm'] = {'name': self.parameters['svm']}
        for key in required_keys + optional_keys:
            if self.parameters.get(key) is not None:
                data[key] = self.parameters[key]
        api = "security/certificates"
        message, error = self.rest_api.post(api, data)
        if error:
            if self.parameters.get('svm') is None and error.get('target') == 'uuid':
                error['target'] = 'cluster'
            if error.get('message') == 'duplicate entry':
                error['message'] += '.  Same certificate may already exist under a different name.'
            self.module.fail_json(msg="Error creating or installing certificate: %s" % error)
        return message

    def sign_certificate(self, uuid):
        """
        sign certificate
        :return: a dictionary with key "public_certificate"
        """
        api = "security/certificates/%s/sign" % uuid
        data = {'signing_request': self.parameters['signing_request']}
        optional_keys = ['expiry_time', 'hash_function']
        for key in optional_keys:
            if self.parameters.get(key) is not None:
                data[key] = self.parameters[key]
        message, error = self.rest_api.post(api, data)
        if error:
            self.module.fail_json(msg="Error signing certificate: %s" % error)
        return message

    def delete_certificate(self, uuid):
        """
        Delete certificate
        :return: message (should be empty dict)
        """
        api = "security/certificates/%s" % uuid
        message, error = self.rest_api.delete(api)
        if error:
            self.module.fail_json(msg="Error deleting certificate: %s" % error)
        return message

    def apply(self):
        """
        Apply action to create/install/sign/delete certificate
        :return: None
        """
        # TODO: add telemetry for REST

        current = self.get_certificate()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        message = None
        if self.parameters.get('signing_request') is not None:
            error = None
            if self.parameters['state'] == 'absent':
                error = "'signing_request' is not supported with 'state' set to 'absent'"
            elif current is None:
                scope = 'cluster' if self.parameters.get('svm') is None else "svm: %s" % self.parameters.get('svm')
                error = "signing certificate with name '%s' not found on %s" % (self.parameters.get('name'), scope)
            elif cd_action is not None:
                error = "'signing_request' is exclusive with other actions: create, install, delete"
            if error is not None:
                self.module.fail_json(msg=error)
            self.na_helper.changed = True

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    message = self.create_or_install_certificate()
                elif cd_action == 'delete':
                    message = self.delete_certificate(current['uuid'])
                elif self.parameters.get('signing_request') is not None:
                    message = self.sign_certificate(current['uuid'])

        results = {'changed': self.na_helper.changed}
        if message:
            results['ontap_info'] = message
        self.module.exit_json(**results)
class NetAppONTAPMetroCluster(object):
    ''' ONTAP metrocluster operations '''
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(choices=['present'], default='present'),
                 dr_pairs=dict(required=True,
                               type='list',
                               elements='dict',
                               options=dict(node_name=dict(required=True,
                                                           type='str'),
                                            partner_node_name=dict(
                                                required=True, type='str'))),
                 partner_cluster_name=dict(required=True, type='str')))
        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.rest_api = OntapRestAPI(self.module)
        self.use_rest = self.rest_api.is_rest()

        if not self.use_rest:
            self.module.fail_json(
                msg=self.rest_api.requires_ontap_9_6('na_ontap_metrocluster'))

    def get_metrocluster(self):
        attrs = None
        api = 'cluster/metrocluster'
        options = {'fields': '*'}
        message, error = self.rest_api.get(api, options)
        if error:
            self.module.fail_json(msg=error)
        if message is not None:
            local = message['local']
            if local['configuration_state'] != "not_configured":
                attrs = {
                    'configuration_state': local['configuration_state'],
                    'partner_cluster_reachable':
                    local['partner_cluster_reachable'],
                    'partner_cluster_name': local['cluster']['name']
                }
        return attrs

    def create_metrocluster(self):
        api = 'cluster/metrocluster'
        options = {}
        dr_pairs = []
        for pair in self.parameters['dr_pairs']:
            dr_pairs.append({
                'node': {
                    'name': pair['node_name']
                },
                'partner': {
                    'name': pair['partner_node_name']
                }
            })
        partner_cluster = {'name': self.parameters['partner_cluster_name']}
        data = {'dr_pairs': dr_pairs, 'partner_cluster': partner_cluster}
        message, error = self.rest_api.post(api, data, options)
        if error is not None:
            self.module.fail_json(msg="%s" % error)
        message, error = self.rest_api.wait_on_job(message['job'],
                                                   self.parameters['hostname'])
        if error:
            self.module.fail_json(msg="%s" % error)

    def apply(self):
        current = self.get_metrocluster()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_metrocluster()
                # Since there is no modify or delete, we will return no change
                else:
                    self.module.fail_json(
                        msg="Modify and Delete currently not support in API")
        self.module.exit_json(changed=self.na_helper.changed)
Пример #3
0
class NetAppOntapMccipMediator(object):
    """
    Mediator object for Add/Remove/Display
    """
    def __init__(self):
        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(
                state=dict(required=False,
                           choices=['present', 'absent'],
                           default='present'),
                mediator_address=dict(required=True, type='str'),
                mediator_user=dict(required=True, type='str'),
                mediator_password=dict(required=True, type='str', no_log=True),
            ))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    supports_check_mode=True)

        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)
        self.rest_api = OntapRestAPI(self.module)
        self.use_rest = self.rest_api.is_rest()

        if not self.use_rest:
            self.module.fail_json(
                msg=self.rest_api.requires_ontap_9_6('na_ontap_mcc_mediator'))

    def add_mediator(self):
        """
        Adds an ONTAP Mediator to MCC configuration
        """
        api = 'cluster/mediators'
        params = {
            'ip_address': self.parameters['mediator_address'],
            'password': self.parameters['mediator_password'],
            'user': self.parameters['mediator_user']
        }
        dummy, error = self.rest_api.post(api, params)
        if error:
            self.module.fail_json(msg=error)

    def remove_mediator(self, current_uuid):
        """
        Removes the ONTAP Mediator from MCC configuration
        """
        api = 'cluster/mediators/%s' % current_uuid
        params = {
            'ip_address': self.parameters['mediator_address'],
            'password': self.parameters['mediator_password'],
            'user': self.parameters['mediator_user'],
            'uuid': current_uuid
        }
        dummy, error = self.rest_api.delete(api, params)
        if error:
            self.module.fail_json(msg=error)

    def get_mediator(self):
        """
        Determine if the MCC configuration has added an ONTAP Mediator
        """
        api = "cluster/mediators"
        message, error = self.rest_api.get(api, None)
        if error:
            self.module.fail_json(msg=error)
        if message['num_records'] > 0:
            return message['records'][0]['uuid']
        return None

    def apply(self):
        """
        Apply action to MCC Mediator
        """
        current = self.get_mediator()
        cd_action = self.na_helper.get_cd_action(current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.add_mediator()
                elif cd_action == 'delete':
                    self.remove_mediator(current)
        self.module.exit_json(changed=self.na_helper.changed)
Пример #4
0
class NetAppOntapWwpnAlias(object):
    ''' ONTAP WWPN alias operations '''
    def __init__(self):

        self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
        self.argument_spec.update(
            dict(state=dict(required=False,
                            choices=['present', 'absent'],
                            default='present'),
                 name=dict(required=True, type='str'),
                 wwpn=dict(required=False, type='str'),
                 vserver=dict(required=True, type='str')))

        self.module = AnsibleModule(argument_spec=self.argument_spec,
                                    required_if=[('state', 'present', ['wwpn'])
                                                 ],
                                    supports_check_mode=True)
        self.na_helper = NetAppModule()
        self.parameters = self.na_helper.set_parameters(self.module.params)

        # REST API should be used for ONTAP 9.6 or higher.
        self.rest_api = OntapRestAPI(self.module)
        if self.rest_api.is_rest():
            self.use_rest = True
        else:
            self.module.fail_json(
                msg=self.rest_api.requires_ontap_9_6('na_ontap_wwpn_alias'))

    def get_alias(self, uuid):
        params = {
            'fields': 'alias,wwpn',
            'alias': self.parameters['name'],
            'svm.uuid': uuid
        }
        api = 'network/fc/wwpn-aliases'
        message, error = self.rest_api.get(api, params)
        if error is not None:
            self.module.fail_json(msg="Error on fetching wwpn alias: %s" %
                                  error)
        if message['num_records'] > 0:
            return {
                'name': message['records'][0]['alias'],
                'wwpn': message['records'][0]['wwpn'],
            }
        else:
            return None

    def create_alias(self, uuid, is_modify=False):
        params = {
            'alias': self.parameters['name'],
            'wwpn': self.parameters['wwpn'],
            'svm.uuid': uuid
        }
        api = 'network/fc/wwpn-aliases'
        dummy, error = self.rest_api.post(api, params)
        if error is not None:
            if is_modify:
                self.module.fail_json(
                    msg=
                    "Error on modifying wwpn alias when trying to re-create alias: %s."
                    % error)
            else:
                self.module.fail_json(msg="Error on creating wwpn alias: %s." %
                                      error)

    def delete_alias(self, uuid, is_modify=False):
        api = 'network/fc/wwpn-aliases/%s/%s' % (uuid, self.parameters['name'])
        dummy, error = self.rest_api.delete(api)
        if error is not None:
            if is_modify:
                self.module.fail_json(
                    msg=
                    "Error on modifying wwpn alias when trying to delete alias: %s."
                    % error)
            else:
                self.module.fail_json(msg="Error on deleting wwpn alias: %s." %
                                      error)

    def get_svm_uuid(self):
        """
        Get a svm's UUID
        :return: uuid of the svm.
        """
        params = {'fields': 'uuid', 'name': self.parameters['vserver']}
        api = "svm/svms"
        message, error = self.rest_api.get(api, params)
        if error is not None:
            self.module.fail_json(msg="Error on fetching svm uuid: %s" % error)
        return message['records'][0]['uuid']

    def apply(self):
        cd_action, uuid, modify = None, None, None
        uuid = self.get_svm_uuid()
        current = self.get_alias(uuid)
        cd_action = self.na_helper.get_cd_action(current, self.parameters)
        if cd_action is None and self.parameters['state'] == 'present':
            modify = self.na_helper.get_modified_attributes(
                current, self.parameters)

        if self.na_helper.changed:
            if self.module.check_mode:
                pass
            else:
                if cd_action == 'create':
                    self.create_alias(uuid)
                elif cd_action == 'delete':
                    self.delete_alias(uuid)
                elif modify:
                    self.delete_alias(uuid, is_modify=True)
                    self.create_alias(uuid, is_modify=True)
        self.module.exit_json(changed=self.na_helper.changed)