diff --git a/lib/ansible/modules/network/meraki/meraki_syslog.py b/lib/ansible/modules/network/meraki/meraki_syslog.py new file mode 100644 index 00000000000..e260f60f1ed --- /dev/null +++ b/lib/ansible/modules/network/meraki/meraki_syslog.py @@ -0,0 +1,288 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Kevin Breit (@kbreit) +# 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: meraki_syslog +short_description: Manage syslog server settings in the Meraki cloud. +version_added: "2.8" +description: +- Allows for creation and management of Syslog servers within Meraki. +notes: +- Changes to existing syslog servers replaces existing configuration. If you need to add to an + existing configuration set state to query to gather the existing configuration and then modify or add. +options: + auth_key: + description: + - Authentication key provided by the dashboard. Required if environmental variable MERAKI_KEY is not set. + type: str + state: + description: + - Query or edit syslog servers + - To delete a syslog server, do not include server in list of servers + choices: [present, query] + default: present + type: str + net_name: + description: + - Name of a network. + aliases: [name, network] + type: str + net_id: + description: + - ID number of a network. + type: str + org_name: + description: + - Name of organization associated to a network. + type: str + org_id: + description: + - ID of organization associated to a network. + type: str + servers: + description: + - List of syslog server settings + suboptions: + host: + description: + - IP address or hostname of Syslog server. + port: + description: + - Port number Syslog server is listening on. + default: "514" + roles: + description: + - List of applicable Syslog server roles. + choices: ['Wireless event log', + 'Appliance event log', + 'Switch event log', + 'Air Marshal events', + 'Flows', + 'URLs', + 'IDS alerts', + 'Security events'] + +author: + - Kevin Breit (@kbreit) +extends_documentation_fragment: meraki +''' + +EXAMPLES = r''' +- name: Query syslog configurations on network named MyNet in the YourOrg organization + meraki_syslog: + auth_key: abc12345 + status: query + org_name: YourOrg + net_name: MyNet + delegate_to: localhost + +- name: Add single syslog server with Appliance event log role + meraki_syslog: + auth_key: abc12345 + status: query + org_name: YourOrg + net_name: MyNet + servers: + - host: 192.0.1.2 + port: 514 + roles: + - Appliance event log + delegate_to: localhost + +- name: Add multiple syslog servers + meraki_syslog: + auth_key: abc12345 + status: query + org_name: YourOrg + net_name: MyNet + servers: + - host: 192.0.1.2 + port: 514 + roles: + - Appliance event log + - host: 192.0.1.3 + port: 514 + roles: + - Appliance event log + - Flows + delegate_to: localhost +''' + +RETURN = r''' +data: + description: Information about the created or manipulated object. + returned: info + type: complex + contains: + host: + description: Hostname or IP address of syslog server. + returned: success + type: string + sample: 192.0.1.1 + port: + description: Port number for syslog communication. + returned: success + type: string + sample: 443 + roles: + description: List of roles assigned to syslog server. + returned: success + type: list + sample: "Wireless event log, URLs" +''' + +import os +from ansible.module_utils.basic import AnsibleModule, json, env_fallback +from ansible.module_utils.urls import fetch_url +from ansible.module_utils._text import to_native +from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec + + +def is_net_valid(meraki, net_name, data): + for n in data: + if n['name'] == net_name: + return True + return False + + +def construct_tags(tags): + ''' Assumes tags are a comma separated list ''' + if tags is not None: + tags = tags.replace(' ', '') + tags = tags.split(',') + tag_list = str() + for t in tags: + tag_list = tag_list + " " + t + tag_list = tag_list + " " + return tag_list + return None + +# This code was used but relying on API and/or server_arg_spec instead +# def validate_roles(meraki, data): +# ''' Validates whether provided rules are valid ''' +# valid_roles = ['WIRELESS EVENT LOG', +# 'APPLIANCE EVENT LOG', +# 'SWITCH EVENT LOG', +# 'AIR MARSHAL EVENTS', +# 'FLOWS', +# 'URLS', +# 'IDS ALERTS', +# 'SECURITY EVENTS'] +# for server in data['servers']: +# for role in server['roles']: +# if role.upper() not in valid_roles: +# # meraki.fail_json(msg="Heck yes") +# meraki.fail_json(msg='{0} is not a valid Syslog role.'.format(role)) + + +def main(): + + # define the available arguments/parameters that a user can pass to + # the module + + server_arg_spec = dict(host=dict(type='str'), + port=dict(type='int', default="514"), + roles=dict(type='list', choices=['Wireless Event log', + 'Appliance event log', + 'Switch event log', + 'Air Marshal events', + 'Flows', + 'URLs', + 'IDS alerts', + 'Security events', + ]), + ) + + argument_spec = meraki_argument_spec() + argument_spec.update(net_id=dict(type='str'), + servers=dict(type='list', element='dict', options=server_arg_spec), + state=dict(type='str', choices=['present', 'query'], default='present'), + net_name=dict(type='str', aliases=['name', 'network']), + ) + + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=False, + ) + + meraki = MerakiModule(module, function='syslog') + module.params['follow_redirects'] = 'all' + payload = None + + syslog_urls = {'syslog': '/networks/{net_id}/syslogServers'} + meraki.url_catalog['query_update'] = syslog_urls + + if not meraki.params['org_name'] and not meraki.params['org_id']: + meraki.fail_json(msg='org_name or org_id parameters are required') + if meraki.params['state'] != 'query': + if not meraki.params['net_name'] or meraki.params['net_id']: + meraki.fail_json(msg='net_name or net_id is required for present or absent states') + if meraki.params['net_name'] and meraki.params['net_id']: + meraki.fail_json(msg='net_name and net_id are mutually exclusive') + + # if the user is working with this module in only check mode we do not + # want to make any changes to the environment, just return the current + # state with no modifications + if module.check_mode: + return meraki.result + + # manipulate or modify the state as needed (this is going to be the + # part where your module will do what it needs to do) + + org_id = meraki.params['org_id'] + if not org_id: + org_id = meraki.get_org_id(meraki.params['org_name']) + nets = meraki.get_nets(org_id=org_id) + net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets) + + if meraki.params['state'] == 'query': + path = meraki.construct_path('query_update', net_id=net_id) + r = meraki.request(path, method='GET') + if meraki.status == 200: + meraki.result['data'] = r + elif meraki.params['state'] == 'present': + # Construct payload + payload = dict() + payload['servers'] = meraki.params['servers'] + + # Convert port numbers to string for idempotency checks + for server in payload['servers']: + if server['port']: + server['port'] = str(server['port']) + + path = meraki.construct_path('query_update', net_id=net_id) + r = meraki.request(path, method='GET') + if meraki.status == 200: + original = dict() + original['servers'] = r + + if meraki.is_update_required(original, payload): + path = meraki.construct_path('query_update', net_id=net_id) + r = meraki.request(path, method='PUT', payload=json.dumps(payload)) + if meraki.status == 200: + meraki.result['data'] = r + meraki.result['changed'] = True + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + meraki.exit_json(**meraki.result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/meraki_syslog/tasks/main.yml b/test/integration/targets/meraki_syslog/tasks/main.yml new file mode 100644 index 00000000000..96ba0b6dcba --- /dev/null +++ b/test/integration/targets/meraki_syslog/tasks/main.yml @@ -0,0 +1,192 @@ +# Test code for the Meraki Organization module +# Copyright: (c) 2018, Kevin Breit (@kbreit) + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- block: + # - name: Test an API key is provided + # fail: + # msg: Please define an API key + # when: auth_key is not defined + + # - name: Use an invalid domain + # meraki_switchport: + # auth_key: '{{ auth_key }}' + # host: marrrraki.com + # state: query + # serial: Q2HP-2C6E-GTLD + # org_name: IntTestOrg + # delegate_to: localhost + # register: invaliddomain + # ignore_errors: yes + + # - name: Disable HTTP + # meraki_switchport: + # auth_key: '{{ auth_key }}' + # use_https: false + # state: query + # serial: Q2HP-2C6E- + # output_level: debug + # delegate_to: localhost + # register: http + # ignore_errors: yes + + # - name: Connection assertions + # assert: + # that: + # - '"Failed to connect to" in invaliddomain.msg' + # - '"http" in http.url' + + - set_fact: + syslog_test_net_name: 'syslog_{{test_net_name}}' + + - name: Create network with type appliance and no timezone + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: '{{syslog_test_net_name}}' + type: appliance + delegate_to: localhost + + - name: Query syslog settings + meraki_syslog: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{syslog_test_net_name}}' + state: query + delegate_to: localhost + register: query_all + + - debug: + msg: '{{query_all}}' + + - name: Set syslog server + meraki_syslog: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{syslog_test_net_name}}' + state: present + servers: + - host: 192.0.1.2 + port: 514 + roles: + - Appliance event log + delegate_to: localhost + register: create_server + + - debug: + msg: '{{create_server.data}}' + + - assert: + that: + - create_server['data'][0]['host'] == "192.0.1.2" + + - name: Set syslog server with idempotency + meraki_syslog: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{syslog_test_net_name}}' + state: present + servers: + - host: 192.0.1.2 + port: 514 + roles: + - Appliance event log + delegate_to: localhost + register: create_server_idempotency + + - debug: + msg: '{{create_server_idempotency}}' + + - assert: + that: + - create_server_idempotency.changed == False + + - name: Set multiple syslog servers # Broken + meraki_syslog: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{syslog_test_net_name}}' + state: present + servers: + - host: 192.0.1.3 + port: 514 + roles: + - Appliance event log + - host: 192.0.1.4 + port: 514 + roles: + - Appliance Event log + - Flows + - host: 192.0.1.5 + port: 514 + roles: + - Flows + delegate_to: localhost + register: create_multiple_servers + + - debug: + msg: '{{create_multiple_servers}}' + + - assert: + that: + - create_multiple_servers['data'][0]['host'] == "192.0.1.3" + - create_multiple_servers['data'][1]['host'] == "192.0.1.4" + - create_multiple_servers['data'][2]['host'] == "192.0.1.5" + - create_multiple_servers['data'] | length == 3 + + - name: Create syslog server with bad name + meraki_syslog: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{syslog_test_net_name}}' + state: present + servers: + - host: 192.0.1.6 + port: 514 + roles: + - Invalid role + delegate_to: localhost + register: invalid_role + ignore_errors: yes + + # - debug: + # msg: '{{invalid_role.body.errors.0}}' + + - assert: + that: + - '"Please select at least one valid role" in invalid_role.body.errors.0' + + - name: Add role to existing syslog server # Adding doesn't work, just creation + meraki_syslog: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{syslog_test_net_name}}' + state: present + servers: + - host: 192.0.1.2 + port: 514 + roles: + - flows + delegate_to: localhost + register: add_role + + - debug: + msg: '{{add_role.data.0.roles}}' + + - assert: + that: + - add_role.data.0.roles.0 == 'Flows' + # - add_role.data.0.roles | length == 2 + + always: + - name: Delete syslog test network + meraki_network: + auth_key: '{{ auth_key }}' + state: absent + org_name: '{{test_org_name}}' + net_name: '{{syslog_test_net_name}}' + delegate_to: localhost + register: delete_all + ignore_errors: yes