summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/ansible/modules/network/meraki/meraki_mx_l3_firewall.py330
-rw-r--r--test/integration/targets/meraki_mx_l3_firewall/aliases1
-rw-r--r--test/integration/targets/meraki_mx_l3_firewall/tasks/main.yml159
3 files changed, 490 insertions, 0 deletions
diff --git a/lib/ansible/modules/network/meraki/meraki_mx_l3_firewall.py b/lib/ansible/modules/network/meraki/meraki_mx_l3_firewall.py
new file mode 100644
index 0000000000..b45badac91
--- /dev/null
+++ b/lib/ansible/modules/network/meraki/meraki_mx_l3_firewall.py
@@ -0,0 +1,330 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_mx_l3_firewall
+short_description: Manage MX appliance layer 3 firewalls in the Meraki cloud
+version_added: "2.7"
+description:
+- Allows for creation, management, and visibility into layer 3 firewalls implemented on Meraki MX firewalls.
+notes:
+- Module assumes a complete list of firewall rules are passed as a parameter.
+- If there is interest in this module allowing manipulation of a single firewall rule, please submit an issue against this module.
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ choices: ['present', 'query']
+ default: present
+ org_name:
+ description:
+ - Name of organization.
+ - If C(clone) is specified, C(org_name) is the name of the new organization.
+ org_id:
+ description:
+ - ID of organization.
+ net_name:
+ description:
+ - Name of network which MX firewall is in.
+ net_id:
+ description:
+ - ID of network which MX firewall is in.
+ rules:
+ description:
+ - List of firewall rules.
+ suboptions:
+ policy:
+ description:
+ - Policy to apply if rule is hit.
+ choices: [allow, deny]
+ protocol:
+ description:
+ - Protocol to match against.
+ choices: [any, icmp, tcp, udp]
+ dest_port:
+ description:
+ - Comma separated list of destination port numbers to match against.
+ dest_cidr:
+ description:
+ - Comma separated list of CIDR notation destination networks.
+ src_port:
+ description:
+ - Comma separated list of source port numbers to match against.
+ src_cidr:
+ description:
+ - Comma separated list of CIDR notation source networks.
+ comment:
+ description:
+ - Optional comment to describe the firewall rule.
+ syslog_enabled:
+ description:
+ - Whether to log hints against the firewall rule.
+ - Only applicable if a syslog server is specified against the network.
+
+ syslog_default_rule:
+ description:
+ - Whether to log hits against the default firewall rule.
+ - Only applicable if a syslog server is specified against the network.
+ - This is not shown in response from Meraki. Instead, refer to the C(syslog_enabled) value in the default rule.
+ type: bool
+ default: no
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: meraki
+'''
+
+EXAMPLES = r'''
+- name: Query firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+
+- name: Set two firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ rules:
+ - comment: Block traffic to server
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.2/32
+ dest_port: any
+ protocol: any
+ policy: deny
+ - comment: Allow traffic to group of servers
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.0/24
+ dest_port: any
+ protocol: any
+ policy: permit
+ delegate_to: localhost
+
+- name: Set one firewall rule and enable logging of the default rule
+ meraki_mx_l3_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ rules:
+ - comment: Block traffic to server
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.2/32
+ dest_port: any
+ protocol: any
+ policy: deny
+ syslog_default_rule: yes
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Firewall rules associated to network.
+ returned: success
+ type: complex
+ contains:
+ comment:
+ description: Comment to describe the firewall rule.
+ returned: always
+ type: string
+ sample: Block traffic to server
+ src_cidr:
+ description: Comma separated list of CIDR notation source networks.
+ returned: always
+ type: string
+ sample: 192.0.1.1/32,192.0.1.2/32
+ src_port:
+ description: Comma separated list of source ports.
+ returned: always
+ type: string
+ sample: 80,443
+ dest_cidr:
+ description: Comma separated list of CIDR notation destination networks.
+ returned: always
+ type: string
+ sample: 192.0.1.1/32,192.0.1.2/32
+ dest_port:
+ description: Comma separated list of destination ports.
+ returned: always
+ type: string
+ sample: 80,443
+ protocol:
+ description: Network protocol for which to match against.
+ returned: always
+ type: string
+ sample: tcp
+ policy:
+ description: Action to take when rule is matched.
+ returned: always
+ type: string
+ syslog_enabled:
+ description: Whether to log to syslog when rule is matched.
+ returned: always
+ type: bool
+ sample: true
+'''
+
+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 assemble_payload(meraki):
+ params_map = {'policy': 'policy',
+ 'protocol': 'protocol',
+ 'dest_port': 'destPort',
+ 'dest_cidr': 'destCidr',
+ 'src_port': 'srcPort',
+ 'src_cidr': 'srcCidr',
+ 'syslog_enabled': 'syslogEnabled',
+ 'comment': 'comment',
+ }
+ rules = []
+ for rule in meraki.params['rules']:
+ proposed_rule = dict()
+ for k, v in rule.items():
+ proposed_rule[params_map[k]] = v
+ rules.append(proposed_rule)
+ payload = {'rules': rules}
+ return payload
+
+
+def get_rules(meraki, net_id):
+ path = meraki.construct_path('get_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ return response
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ fw_rules = dict(policy=dict(type='str', choices=['allow', 'deny']),
+ protocol=dict(type='str', choices=['tcp', 'udp', 'icmp', 'any']),
+ dest_port=dict(type='str'),
+ dest_cidr=dict(type='str'),
+ src_port=dict(type='str'),
+ src_cidr=dict(type='str'),
+ comment=dict(type='str'),
+ syslog_enabled=dict(type='bool', default=False),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ rules=dict(type='list', default=None, elements='dict', options=fw_rules),
+ syslog_default_rule=dict(type='bool'),
+ )
+
+ # seed the result dict in the object
+ # we primarily care about changed and state
+ # change is if this module effectively modified the target
+ # state will include any data that you want your module to pass back
+ # for consumption, for example, in a subsequent task
+ result = dict(
+ changed=False,
+ )
+ # 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=True,
+ )
+ meraki = MerakiModule(module, function='mx_l3_firewall')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'mx_l3_firewall': '/networks/{net_id}/l3FirewallRules/'}
+ update_urls = {'mx_l3_firewall': '/networks/{net_id}/l3FirewallRules/'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['update'] = update_urls
+
+ payload = None
+
+ # 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
+ # FIXME: Work with Meraki so they can implement a check mode
+ if module.check_mode:
+ meraki.exit_json(**meraki.result)
+
+ # execute checks for argument completeness
+
+ # 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']
+ orgs = None
+ if org_id is None:
+ orgs = meraki.get_orgs()
+ for org in orgs:
+ if org['name'] == meraki.params['org_name']:
+ org_id = org['id']
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ if orgs is None:
+ orgs = meraki.get_orgs()
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'],
+ data=meraki.get_nets(org_id=org_id))
+
+ if meraki.params['state'] == 'query':
+ meraki.result['data'] = get_rules(meraki, net_id)
+ elif meraki.params['state'] == 'present':
+ rules = get_rules(meraki, net_id)
+ path = meraki.construct_path('get_all', net_id=net_id)
+ if meraki.params['rules']:
+ payload = assemble_payload(meraki)
+ else:
+ payload = dict()
+ update = False
+ if meraki.params['syslog_default_rule']:
+ payload['syslogDefaultRule'] = meraki.params['syslog_default_rule']
+ try:
+ if len(rules) - 1 != len(payload['rules']): # Quick and simple check to avoid more processing
+ update = True
+ if meraki.params['syslog_default_rule']:
+ if rules[len(rules) - 1]['syslogEnabled'] != meraki.params['syslog_default_rule']:
+ update = True
+ if update is False:
+ for r in range(len(rules) - 1):
+ if meraki.is_update_required(rules[r], payload[r]) is True:
+ update = True
+ except KeyError:
+ pass
+ if update is True:
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ 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_mx_l3_firewall/aliases b/test/integration/targets/meraki_mx_l3_firewall/aliases
new file mode 100644
index 0000000000..ad7ccf7ada
--- /dev/null
+++ b/test/integration/targets/meraki_mx_l3_firewall/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/test/integration/targets/meraki_mx_l3_firewall/tasks/main.yml b/test/integration/targets/meraki_mx_l3_firewall/tasks/main.yml
new file mode 100644
index 0000000000..731ecca47d
--- /dev/null
+++ b/test/integration/targets/meraki_mx_l3_firewall/tasks/main.yml
@@ -0,0 +1,159 @@
+# 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_organization:
+ auth_key: '{{ auth_key }}'
+ host: marrrraki.com
+ state: present
+ org_name: IntTestOrg
+ output_level: debug
+ delegate_to: localhost
+ register: invalid_domain
+ ignore_errors: yes
+
+ - name: Disable HTTP
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ use_https: false
+ state: query
+ output_level: debug
+ delegate_to: localhost
+ register: http
+ ignore_errors: yes
+
+ - name: Connection assertions
+ assert:
+ that:
+ - '"Failed to connect to" in invalid_domain.msg'
+ - '"http" in http.url'
+
+ - name: Create network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ type: appliance
+ delegate_to: localhost
+
+ - name: Query firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - debug:
+ msg: '{{query}}'
+
+ - assert:
+ that:
+ - query.data|length == 1
+
+ - name: Set one firewall rule
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: any
+ src_cidr: any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ register: create_one
+
+ - assert:
+ that:
+ - create_one.data|length == 2
+ - create_one.data.0.destCidr == '192.0.1.1/32'
+ - create_one.data.0.protocol == 'tcp'
+ - create_one.data.0.policy == 'deny'
+ - create_one.changed == True
+
+ - name: Check for idempotency
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: any
+ src_cidr: any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ register: create_one_idempotent
+
+ - debug:
+ msg: '{{create_one_idempotent}}'
+
+ - assert:
+ that:
+ - create_one_idempotent.changed == False
+
+ - name: Enable syslog for default rule
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: any
+ src_cidr: any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ syslog_default_rule: yes
+ delegate_to: localhost
+ register: default_syslog
+
+ - debug:
+ msg: '{{default_syslog}}'
+
+ - name: Query firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - debug:
+ msg: '{{query.data.1}}'
+
+ - assert:
+ that:
+ - query.data.1.syslogEnabled == True
+
+ always:
+ - name: Delete all firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules: []
+ delegate_to: localhost
+ register: delete_all