New module - meraki_nat (#52889)
* Initial commit for meraki_nat module - Query fully works - Present is still very much in development * Add initial code for present functionality, not complete * Add request documentation * Add examples and return documentation. * Added payload to requests - Module seems to need new idempotency check * Allow 1:1 and 1:many NAT to work - New idempotency check method is probably required to work * Make all three options work - Module isn't idempotent * Diff support - Added integration tests - Diff support isn't quite done * Fix diff output * Enable idempotency assertion in tests * Add test assertions for code coverage * Update documentation and tests - Split tests to separate file to avoid delegate_to * Fix blank line
This commit is contained in:
parent
5ed469f6b7
commit
f8f3986871
3 changed files with 1041 additions and 0 deletions
671
lib/ansible/modules/network/meraki/meraki_nat.py
Normal file
671
lib/ansible/modules/network/meraki/meraki_nat.py
Normal file
|
@ -0,0 +1,671 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2019, 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_nat
|
||||||
|
short_description: Manage NAT rules in Meraki cloud
|
||||||
|
version_added: "2.9"
|
||||||
|
description:
|
||||||
|
- Allows for creation, management, and visibility of NAT rules (1:1, 1:many, port forwarding) within Meraki.
|
||||||
|
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Create or modify an organization.
|
||||||
|
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_id:
|
||||||
|
description:
|
||||||
|
- ID of organization associated to a network.
|
||||||
|
type: str
|
||||||
|
subset:
|
||||||
|
description:
|
||||||
|
- Specifies which NAT components to query.
|
||||||
|
choices: ['1:1', '1:many', all, port_forwarding]
|
||||||
|
default: all
|
||||||
|
type: list
|
||||||
|
one_to_one:
|
||||||
|
description:
|
||||||
|
- List of 1:1 NAT rules.
|
||||||
|
type: list
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- A descriptive name for the rule.
|
||||||
|
type: str
|
||||||
|
public_ip:
|
||||||
|
description:
|
||||||
|
- The IP address that will be used to access the internal resource from the WAN.
|
||||||
|
type: str
|
||||||
|
lan_ip:
|
||||||
|
description:
|
||||||
|
- The IP address of the server or device that hosts the internal resource that you wish to make available on the WAN.
|
||||||
|
type: str
|
||||||
|
uplink:
|
||||||
|
description:
|
||||||
|
- The physical WAN interface on which the traffic will arrive.
|
||||||
|
choices: [both, internet1, internet2]
|
||||||
|
allowed_inbound:
|
||||||
|
description:
|
||||||
|
- The ports this mapping will provide access on, and the remote IPs that will be allowed access to the resource.
|
||||||
|
type: list
|
||||||
|
suboptions:
|
||||||
|
protocol:
|
||||||
|
description:
|
||||||
|
- Protocol to apply NAT rule to.
|
||||||
|
choices: [any, icmp-ping, tcp, udp]
|
||||||
|
type: str
|
||||||
|
default: any
|
||||||
|
destination_ports:
|
||||||
|
description:
|
||||||
|
- List of ports or port ranges that will be forwarded to the host on the LAN.
|
||||||
|
type: list
|
||||||
|
allowed_ips:
|
||||||
|
description:
|
||||||
|
- ranges of WAN IP addresses that are allowed to make inbound connections on the specified ports or port ranges, or 'any'.
|
||||||
|
type: list
|
||||||
|
one_to_many:
|
||||||
|
description:
|
||||||
|
- List of 1:many NAT rules.
|
||||||
|
type: list
|
||||||
|
suboptions:
|
||||||
|
public_ip:
|
||||||
|
description:
|
||||||
|
- The IP address that will be used to access the internal resource from the WAN.
|
||||||
|
type: str
|
||||||
|
uplink:
|
||||||
|
description:
|
||||||
|
- The physical WAN interface on which the traffic will arrive.
|
||||||
|
choices: [both, internet1, internet2]
|
||||||
|
type: str
|
||||||
|
port_rules:
|
||||||
|
description:
|
||||||
|
- List of associated port rules.
|
||||||
|
type: list
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- A description of the rule.
|
||||||
|
type: str
|
||||||
|
protocol:
|
||||||
|
description:
|
||||||
|
- Protocol to apply NAT rule to.
|
||||||
|
choices: [tcp, udp]
|
||||||
|
type: str
|
||||||
|
public_port:
|
||||||
|
description:
|
||||||
|
- Destination port of the traffic that is arriving on the WAN.
|
||||||
|
type: str
|
||||||
|
local_ip:
|
||||||
|
description:
|
||||||
|
- Local IP address to which traffic will be forwarded.
|
||||||
|
type: str
|
||||||
|
local_port:
|
||||||
|
description:
|
||||||
|
- Destination port of the forwarded traffic that will be sent from the MX to the specified host on the LAN.
|
||||||
|
- If you simply wish to forward the traffic without translating the port, this should be the same as the Public port.
|
||||||
|
type: str
|
||||||
|
allowed_ips:
|
||||||
|
description:
|
||||||
|
- Remote IP addresses or ranges that are permitted to access the internal resource via this port forwarding rule, or 'any'.
|
||||||
|
type: list
|
||||||
|
port_forwarding:
|
||||||
|
description:
|
||||||
|
- List of port forwarding rules.
|
||||||
|
type: list
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- A descriptive name for the rule.
|
||||||
|
type: str
|
||||||
|
lan_ip:
|
||||||
|
description:
|
||||||
|
- The IP address of the server or device that hosts the internal resource that you wish to make available on the WAN.
|
||||||
|
type: str
|
||||||
|
uplink:
|
||||||
|
description:
|
||||||
|
- The physical WAN interface on which the traffic will arrive.
|
||||||
|
choices: [both, internet1, internet2]
|
||||||
|
type: str
|
||||||
|
public_port:
|
||||||
|
description:
|
||||||
|
- A port or port ranges that will be forwarded to the host on the LAN.
|
||||||
|
type: str
|
||||||
|
local_port:
|
||||||
|
description:
|
||||||
|
- A port or port ranges that will receive the forwarded traffic from the WAN.
|
||||||
|
type: str
|
||||||
|
allowed_ips:
|
||||||
|
description:
|
||||||
|
- List of ranges of WAN IP addresses that are allowed to make inbound connections on the specified ports or port ranges (or any).
|
||||||
|
protocol:
|
||||||
|
description:
|
||||||
|
- Protocol to forward traffic for.
|
||||||
|
choices: [tcp, udp]
|
||||||
|
type: str
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Kevin Breit (@kbreit)
|
||||||
|
extends_documentation_fragment: meraki
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Query all NAT rules
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: abc123
|
||||||
|
org_name: YourOrg
|
||||||
|
net_name: YourNet
|
||||||
|
state: query
|
||||||
|
subset: all
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Query 1:1 NAT rules
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: abc123
|
||||||
|
org_name: YourOrg
|
||||||
|
net_name: YourNet
|
||||||
|
state: query
|
||||||
|
subset: '1:1'
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Create 1:1 rule
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: abc123
|
||||||
|
org_name: YourOrg
|
||||||
|
net_name: YourNet
|
||||||
|
state: present
|
||||||
|
one_to_one:
|
||||||
|
- name: Service behind NAT
|
||||||
|
public_ip: 1.2.1.2
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: internet1
|
||||||
|
allowed_inbound:
|
||||||
|
- protocol: tcp
|
||||||
|
destination_ports:
|
||||||
|
- 80
|
||||||
|
allowed_ips:
|
||||||
|
- 10.10.10.10
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Create 1:many rule
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: abc123
|
||||||
|
org_name: YourOrg
|
||||||
|
net_name: YourNet
|
||||||
|
state: present
|
||||||
|
one_to_many:
|
||||||
|
- public_ip: 1.1.1.1
|
||||||
|
uplink: internet1
|
||||||
|
port_rules:
|
||||||
|
- name: Test rule
|
||||||
|
protocol: tcp
|
||||||
|
public_port: 10
|
||||||
|
local_ip: 192.168.128.1
|
||||||
|
local_port: 11
|
||||||
|
allowed_ips:
|
||||||
|
- any
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Create port forwarding rule
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: abc123
|
||||||
|
org_name: YourOrg
|
||||||
|
net_name: YourNet
|
||||||
|
state: present
|
||||||
|
port_forwarding:
|
||||||
|
- name: Test map
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: both
|
||||||
|
protocol: tcp
|
||||||
|
allowed_ips:
|
||||||
|
- 1.1.1.1
|
||||||
|
public_port: 10
|
||||||
|
local_port: 11
|
||||||
|
delegate_to: localhost
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
data:
|
||||||
|
description: Information about the created or manipulated object.
|
||||||
|
returned: success
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
one_to_one:
|
||||||
|
description: Information about 1:1 NAT object.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
rules:
|
||||||
|
description: List of 1:1 NAT rules.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description: Name of NAT object.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: Web server behind NAT
|
||||||
|
lanIp:
|
||||||
|
description: Local IP address to be mapped.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: 192.168.128.22
|
||||||
|
publicIp:
|
||||||
|
description: Public IP address to be mapped.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: 148.2.5.100
|
||||||
|
uplink:
|
||||||
|
description: Internet port where rule is applied.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: internet1
|
||||||
|
allowedInbound:
|
||||||
|
description: List of inbound forwarding rules.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
protocol:
|
||||||
|
description: Protocol to apply NAT rule to.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: tcp
|
||||||
|
destinationPorts:
|
||||||
|
description: Ports to apply NAT rule to.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: 80
|
||||||
|
allowedIps:
|
||||||
|
description: List of IP addresses to be forwarded.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: list
|
||||||
|
example: 10.80.100.0/24
|
||||||
|
one_to_many:
|
||||||
|
description: Information about 1:many NAT object.
|
||||||
|
returned: success, when 1:many NAT object is in task
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
rules:
|
||||||
|
description: List of 1:many NAT rules.
|
||||||
|
returned: success, when 1:many NAT object is in task
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
publicIp:
|
||||||
|
description: Public IP address to be mapped.
|
||||||
|
returned: success, when 1:many NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: 148.2.5.100
|
||||||
|
uplink:
|
||||||
|
description: Internet port where rule is applied.
|
||||||
|
returned: success, when 1:many NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: internet1
|
||||||
|
portRules:
|
||||||
|
description: List of NAT port rules.
|
||||||
|
returned: success, when 1:many NAT object is in task
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description: Name of NAT object.
|
||||||
|
returned: success, when 1:many NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: Web server behind NAT
|
||||||
|
protocol:
|
||||||
|
description: Protocol to apply NAT rule to.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: tcp
|
||||||
|
publicPort:
|
||||||
|
description: Destination port of the traffic that is arriving on WAN.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: int
|
||||||
|
example: 9443
|
||||||
|
localIp:
|
||||||
|
description: Local IP address traffic will be forwarded.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: string
|
||||||
|
example: 192.0.2.10
|
||||||
|
localPort:
|
||||||
|
description: Destination port to be forwarded to.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: int
|
||||||
|
example: 443
|
||||||
|
allowedIps:
|
||||||
|
description: List of IP addresses to be forwarded.
|
||||||
|
returned: success, when 1:1 NAT object is in task
|
||||||
|
type: list
|
||||||
|
example: 10.80.100.0/24
|
||||||
|
port_forwarding:
|
||||||
|
description: Information about port forwarding rules.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
rules:
|
||||||
|
description: List of port forwarding rules.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
lanIp:
|
||||||
|
description: Local IP address to be mapped.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: string
|
||||||
|
example: 192.168.128.22
|
||||||
|
allowedIps:
|
||||||
|
description: List of IP addresses to be forwarded.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: list
|
||||||
|
example: 10.80.100.0/24
|
||||||
|
name:
|
||||||
|
description: Name of NAT object.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: string
|
||||||
|
example: Web server behind NAT
|
||||||
|
protocol:
|
||||||
|
description: Protocol to apply NAT rule to.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: string
|
||||||
|
example: tcp
|
||||||
|
publicPort:
|
||||||
|
description: Destination port of the traffic that is arriving on WAN.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: int
|
||||||
|
example: 9443
|
||||||
|
localPort:
|
||||||
|
description: Destination port to be forwarded to.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: int
|
||||||
|
example: 443
|
||||||
|
uplink:
|
||||||
|
description: Internet port where rule is applied.
|
||||||
|
returned: success, when port forwarding is in task
|
||||||
|
type: string
|
||||||
|
example: internet1
|
||||||
|
'''
|
||||||
|
|
||||||
|
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.common.dict_transformations import recursive_diff
|
||||||
|
from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
|
||||||
|
|
||||||
|
key_map = {'name': 'name',
|
||||||
|
'public_ip': 'publicIp',
|
||||||
|
'lan_ip': 'lanIp',
|
||||||
|
'uplink': 'uplink',
|
||||||
|
'allowed_inbound': 'allowedInbound',
|
||||||
|
'protocol': 'protocol',
|
||||||
|
'destination_ports': 'destinationPorts',
|
||||||
|
'allowed_ips': 'allowedIps',
|
||||||
|
'port_rules': 'portRules',
|
||||||
|
'public_port': 'publicPort',
|
||||||
|
'local_ip': 'localIp',
|
||||||
|
'local_port': 'localPort',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def construct_payload(params):
|
||||||
|
if isinstance(params, list):
|
||||||
|
items = []
|
||||||
|
for item in params:
|
||||||
|
items.append(construct_payload(item))
|
||||||
|
return items
|
||||||
|
elif isinstance(params, dict):
|
||||||
|
info = {}
|
||||||
|
for param in params:
|
||||||
|
info[key_map[param]] = construct_payload(params[param])
|
||||||
|
return info
|
||||||
|
elif isinstance(params, str) or isinstance(params, int):
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def list_int_to_str(data):
|
||||||
|
return [str(item) for item in data]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
# define the available arguments/parameters that a user can pass to
|
||||||
|
# the module
|
||||||
|
|
||||||
|
one_to_one_allowed_inbound_spec = dict(protocol=dict(type='str', choices=['tcp', 'udp', 'icmp-ping', 'any'], default='any'),
|
||||||
|
destination_ports=dict(type='list', element='str'),
|
||||||
|
allowed_ips=dict(type='list'),
|
||||||
|
)
|
||||||
|
|
||||||
|
one_to_many_port_inbound_spec = dict(protocol=dict(type='str', choices=['tcp', 'udp']),
|
||||||
|
name=dict(type='str'),
|
||||||
|
local_ip=dict(type='str'),
|
||||||
|
local_port=dict(type='str'),
|
||||||
|
allowed_ips=dict(type='list'),
|
||||||
|
public_port=dict(type='str'),
|
||||||
|
)
|
||||||
|
|
||||||
|
one_to_one_spec = dict(name=dict(type='str'),
|
||||||
|
public_ip=dict(type='str'),
|
||||||
|
lan_ip=dict(type='str'),
|
||||||
|
uplink=dict(type='str', choices=['internet1', 'internet2', 'both']),
|
||||||
|
allowed_inbound=dict(type='list', element='dict', options=one_to_one_allowed_inbound_spec),
|
||||||
|
)
|
||||||
|
|
||||||
|
one_to_many_spec = dict(public_ip=dict(type='str'),
|
||||||
|
uplink=dict(type='str', choices=['internet1', 'internet2', 'both']),
|
||||||
|
port_rules=dict(type='list', element='dict', options=one_to_many_port_inbound_spec),
|
||||||
|
)
|
||||||
|
|
||||||
|
port_forwarding_spec = dict(name=dict(type='str'),
|
||||||
|
lan_ip=dict(type='str'),
|
||||||
|
uplink=dict(type='str', choices=['internet1', 'internet2', 'both']),
|
||||||
|
protocol=dict(type='str', choices=['tcp', 'udp']),
|
||||||
|
public_port=dict(type='int'),
|
||||||
|
local_port=dict(type='int'),
|
||||||
|
allowed_ips=dict(type='list'),
|
||||||
|
)
|
||||||
|
|
||||||
|
argument_spec = meraki_argument_spec()
|
||||||
|
argument_spec.update(
|
||||||
|
net_id=dict(type='str'),
|
||||||
|
net_name=dict(type='str', aliases=['name', 'network']),
|
||||||
|
state=dict(type='str', choices=['present', 'query'], default='present'),
|
||||||
|
subset=dict(type='list', choices=['1:1', '1:many', 'all', 'port_forwarding'], default='all'),
|
||||||
|
one_to_one=dict(type='list', element='dict', options=one_to_one_spec),
|
||||||
|
one_to_many=dict(type='list', element='dict', options=one_to_many_spec),
|
||||||
|
port_forwarding=dict(type='list', element='dict', options=port_forwarding_spec),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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='nat')
|
||||||
|
module.params['follow_redirects'] = 'all'
|
||||||
|
|
||||||
|
one_to_one_payload = None
|
||||||
|
one_to_many_payload = None
|
||||||
|
port_forwarding_payload = None
|
||||||
|
if meraki.params['state'] == 'present':
|
||||||
|
if meraki.params['one_to_one'] is not None:
|
||||||
|
rules = []
|
||||||
|
for i in meraki.params['one_to_one']:
|
||||||
|
data = {'name': i['name'],
|
||||||
|
'publicIp': i['public_ip'],
|
||||||
|
'uplink': i['uplink'],
|
||||||
|
'lanIp': i['lan_ip'],
|
||||||
|
'allowedInbound': construct_payload(i['allowed_inbound'])
|
||||||
|
}
|
||||||
|
for inbound in data['allowedInbound']:
|
||||||
|
inbound['destinationPorts'] = list_int_to_str(inbound['destinationPorts'])
|
||||||
|
rules.append(data)
|
||||||
|
one_to_one_payload = {'rules': rules}
|
||||||
|
if meraki.params['one_to_many'] is not None:
|
||||||
|
rules = []
|
||||||
|
for i in meraki.params['one_to_many']:
|
||||||
|
data = {'publicIp': i['public_ip'],
|
||||||
|
'uplink': i['uplink'],
|
||||||
|
}
|
||||||
|
port_rules = []
|
||||||
|
for port_rule in i['port_rules']:
|
||||||
|
rule = {'name': port_rule['name'],
|
||||||
|
'protocol': port_rule['protocol'],
|
||||||
|
'publicPort': str(port_rule['public_port']),
|
||||||
|
'localIp': port_rule['local_ip'],
|
||||||
|
'localPort': str(port_rule['local_port']),
|
||||||
|
'allowedIps': port_rule['allowed_ips'],
|
||||||
|
}
|
||||||
|
port_rules.append(rule)
|
||||||
|
data['portRules'] = port_rules
|
||||||
|
rules.append(data)
|
||||||
|
one_to_many_payload = {'rules': rules}
|
||||||
|
if meraki.params['port_forwarding'] is not None:
|
||||||
|
port_forwarding_payload = {'rules': construct_payload(meraki.params['port_forwarding'])}
|
||||||
|
for rule in port_forwarding_payload['rules']:
|
||||||
|
rule['localPort'] = str(rule['localPort'])
|
||||||
|
rule['publicPort'] = str(rule['publicPort'])
|
||||||
|
|
||||||
|
onetomany_urls = {'nat': '/networks/{net_id}/oneToManyNatRules'}
|
||||||
|
onetoone_urls = {'nat': '/networks/{net_id}/oneToOneNatRules'}
|
||||||
|
port_forwarding_urls = {'nat': '/networks/{net_id}/portForwardingRules'}
|
||||||
|
meraki.url_catalog['1:many'] = onetomany_urls
|
||||||
|
meraki.url_catalog['1:1'] = onetoone_urls
|
||||||
|
meraki.url_catalog['port_forwarding'] = port_forwarding_urls
|
||||||
|
|
||||||
|
if meraki.params['net_name'] and meraki.params['net_id']:
|
||||||
|
meraki.fail_json(msg='net_name and net_id are mutually exclusive')
|
||||||
|
|
||||||
|
org_id = meraki.params['org_id']
|
||||||
|
if not org_id:
|
||||||
|
org_id = meraki.get_org_id(meraki.params['org_name'])
|
||||||
|
net_id = meraki.params['net_id']
|
||||||
|
if net_id is None:
|
||||||
|
nets = meraki.get_nets(org_id=org_id)
|
||||||
|
net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
|
||||||
|
|
||||||
|
if meraki.params['state'] == 'query':
|
||||||
|
if meraki.params['subset'][0] == 'all':
|
||||||
|
path = meraki.construct_path('1:many', net_id=net_id)
|
||||||
|
data = {'1:many': meraki.request(path, method='GET')}
|
||||||
|
path = meraki.construct_path('1:1', net_id=net_id)
|
||||||
|
data['1:1'] = meraki.request(path, method='GET')
|
||||||
|
path = meraki.construct_path('port_forwarding', net_id=net_id)
|
||||||
|
data['port_forwarding'] = meraki.request(path, method='GET')
|
||||||
|
meraki.result['data'] = data
|
||||||
|
else:
|
||||||
|
for subset in meraki.params['subset']:
|
||||||
|
path = meraki.construct_path(subset, net_id=net_id)
|
||||||
|
data = {subset: meraki.request(path, method='GET')}
|
||||||
|
try:
|
||||||
|
meraki.result['data'][subset] = data
|
||||||
|
except KeyError:
|
||||||
|
meraki.result['data'] = {subset: data}
|
||||||
|
elif meraki.params['state'] == 'present':
|
||||||
|
meraki.result['data'] = dict()
|
||||||
|
if one_to_one_payload is not None:
|
||||||
|
path = meraki.construct_path('1:1', net_id=net_id)
|
||||||
|
current = meraki.request(path, method='GET')
|
||||||
|
if meraki.is_update_required(current, one_to_one_payload):
|
||||||
|
if meraki.module.check_mode is True:
|
||||||
|
diff = recursive_diff(current, one_to_one_payload)
|
||||||
|
current.update(one_to_one_payload)
|
||||||
|
if 'diff' not in meraki.result:
|
||||||
|
meraki.result['diff'] = {'before': {}, 'after': {}}
|
||||||
|
meraki.result['diff']['before'].update({'one_to_one': diff[0]})
|
||||||
|
meraki.result['diff']['after'].update({'one_to_one': diff[1]})
|
||||||
|
meraki.result['data'] = {'one_to_one': current}
|
||||||
|
meraki.result['changed'] = True
|
||||||
|
else:
|
||||||
|
r = meraki.request(path, method='PUT', payload=json.dumps(one_to_one_payload))
|
||||||
|
if meraki.status == 200:
|
||||||
|
diff = recursive_diff(current, one_to_one_payload)
|
||||||
|
if 'diff' not in meraki.result:
|
||||||
|
meraki.result['diff'] = {'before': {}, 'after': {}}
|
||||||
|
meraki.result['diff']['before'].update({'one_to_one': diff[0]})
|
||||||
|
meraki.result['diff']['after'].update({'one_to_one': diff[1]})
|
||||||
|
meraki.result['data'] = {'one_to_one': r}
|
||||||
|
meraki.result['changed'] = True
|
||||||
|
else:
|
||||||
|
meraki.result['data']['one_to_one'] = current
|
||||||
|
if one_to_many_payload is not None:
|
||||||
|
path = meraki.construct_path('1:many', net_id=net_id)
|
||||||
|
current = meraki.request(path, method='GET')
|
||||||
|
if meraki.is_update_required(current, one_to_many_payload):
|
||||||
|
if meraki.module.check_mode is True:
|
||||||
|
diff = recursive_diff(current, one_to_many_payload)
|
||||||
|
current.update(one_to_many_payload)
|
||||||
|
if 'diff' not in meraki.result:
|
||||||
|
meraki.result['diff'] = {'before': {}, 'after': {}}
|
||||||
|
meraki.result['diff']['before'].update({'one_to_many': diff[0]})
|
||||||
|
meraki.result['diff']['after'].update({'one_to_many': diff[1]})
|
||||||
|
meraki.result['data']['one_to_many'] = current
|
||||||
|
meraki.result['changed'] = True
|
||||||
|
else:
|
||||||
|
r = meraki.request(path, method='PUT', payload=json.dumps(one_to_many_payload))
|
||||||
|
if meraki.status == 200:
|
||||||
|
diff = recursive_diff(current, one_to_many_payload)
|
||||||
|
if 'diff' not in meraki.result:
|
||||||
|
meraki.result['diff'] = {'before': {}, 'after': {}}
|
||||||
|
meraki.result['diff']['before'].update({'one_to_many': diff[0]})
|
||||||
|
meraki.result['diff']['after'].update({'one_to_many': diff[1]})
|
||||||
|
meraki.result['data'].update({'one_to_many': r})
|
||||||
|
meraki.result['changed'] = True
|
||||||
|
else:
|
||||||
|
meraki.result['data']['one_to_many'] = current
|
||||||
|
if port_forwarding_payload is not None:
|
||||||
|
path = meraki.construct_path('port_forwarding', net_id=net_id)
|
||||||
|
current = meraki.request(path, method='GET')
|
||||||
|
if meraki.is_update_required(current, port_forwarding_payload):
|
||||||
|
if meraki.module.check_mode is True:
|
||||||
|
diff = recursive_diff(current, port_forwarding_payload)
|
||||||
|
current.update(port_forwarding_payload)
|
||||||
|
if 'diff' not in meraki.result:
|
||||||
|
meraki.result['diff'] = {'before': {}, 'after': {}}
|
||||||
|
meraki.result['diff']['before'].update({'port_forwarding': diff[0]})
|
||||||
|
meraki.result['diff']['after'].update({'port_forwarding': diff[1]})
|
||||||
|
meraki.result['data']['port_forwarding'] = current
|
||||||
|
meraki.result['changed'] = True
|
||||||
|
else:
|
||||||
|
r = meraki.request(path, method='PUT', payload=json.dumps(port_forwarding_payload))
|
||||||
|
if meraki.status == 200:
|
||||||
|
if 'diff' not in meraki.result:
|
||||||
|
meraki.result['diff'] = {'before': {}, 'after': {}}
|
||||||
|
diff = recursive_diff(current, port_forwarding_payload)
|
||||||
|
meraki.result['diff']['before'].update({'port_forwarding': diff[0]})
|
||||||
|
meraki.result['diff']['after'].update({'port_forwarding': diff[1]})
|
||||||
|
meraki.result['data'].update({'port_forwarding': r})
|
||||||
|
meraki.result['changed'] = True
|
||||||
|
else:
|
||||||
|
meraki.result['data']['port_forwarding'] = current
|
||||||
|
|
||||||
|
# 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()
|
7
test/integration/targets/meraki_nat/tasks/main.yml
Normal file
7
test/integration/targets/meraki_nat/tasks/main.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# 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)
|
||||||
|
---
|
||||||
|
- name: Run test cases
|
||||||
|
include: tests.yml ansible_connection=local
|
363
test/integration/targets/meraki_nat/tasks/tests.yml
Normal file
363
test/integration/targets/meraki_nat/tasks/tests.yml
Normal file
|
@ -0,0 +1,363 @@
|
||||||
|
# Test code for the Meraki NAT module
|
||||||
|
# Copyright: (c) 2019, Kevin Breit (@kbreit)
|
||||||
|
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
---
|
||||||
|
- block:
|
||||||
|
- name: Create test network
|
||||||
|
meraki_network:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
type: appliance
|
||||||
|
|
||||||
|
- name: Create 1:1 rule with check mode
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
one_to_one:
|
||||||
|
- name: Service behind NAT
|
||||||
|
public_ip: 1.2.1.2
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: internet1
|
||||||
|
allowed_inbound:
|
||||||
|
- protocol: tcp
|
||||||
|
destination_ports:
|
||||||
|
- 80
|
||||||
|
allowed_ips:
|
||||||
|
- 10.10.10.10
|
||||||
|
register: create_one_one_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_one_one_check
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_one_one_check is changed
|
||||||
|
|
||||||
|
- name: Create 1:1 rule
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
one_to_one:
|
||||||
|
- name: Service behind NAT
|
||||||
|
public_ip: 1.2.1.2
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: internet1
|
||||||
|
allowed_inbound:
|
||||||
|
- protocol: tcp
|
||||||
|
destination_ports:
|
||||||
|
- 80
|
||||||
|
allowed_ips:
|
||||||
|
- 10.10.10.10
|
||||||
|
register: create_one_one
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_one_one
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_one_one is changed
|
||||||
|
|
||||||
|
- name: Create 1:1 rule with idempotency
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
one_to_one:
|
||||||
|
- name: Service behind NAT
|
||||||
|
public_ip: 1.2.1.2
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: internet1
|
||||||
|
allowed_inbound:
|
||||||
|
- protocol: tcp
|
||||||
|
destination_ports:
|
||||||
|
- 80
|
||||||
|
allowed_ips:
|
||||||
|
- 10.10.10.10
|
||||||
|
register: create_one_one_idempotent
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_one_one_idempotent
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_one_one_idempotent is not changed
|
||||||
|
|
||||||
|
- name: Create 1:many rule with check mode
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
one_to_many:
|
||||||
|
- public_ip: 1.1.1.1
|
||||||
|
uplink: internet1
|
||||||
|
port_rules:
|
||||||
|
- name: Test rule
|
||||||
|
protocol: tcp
|
||||||
|
public_port: 10
|
||||||
|
local_ip: 192.168.128.1
|
||||||
|
local_port: 11
|
||||||
|
allowed_ips:
|
||||||
|
- any
|
||||||
|
register: create_one_many_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_one_many_check
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_one_many_check is changed
|
||||||
|
|
||||||
|
- name: Create 1:many rule
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
one_to_many:
|
||||||
|
- public_ip: 1.1.1.1
|
||||||
|
uplink: internet1
|
||||||
|
port_rules:
|
||||||
|
- name: Test rule
|
||||||
|
protocol: tcp
|
||||||
|
public_port: 10
|
||||||
|
local_ip: 192.168.128.1
|
||||||
|
local_port: 11
|
||||||
|
allowed_ips:
|
||||||
|
- any
|
||||||
|
register: create_one_many
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_one_many
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_one_many is changed
|
||||||
|
|
||||||
|
- name: Create 1:many rule with idempotency
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
one_to_many:
|
||||||
|
- public_ip: 1.1.1.1
|
||||||
|
uplink: internet1
|
||||||
|
port_rules:
|
||||||
|
- name: Test rule
|
||||||
|
protocol: tcp
|
||||||
|
public_port: 10
|
||||||
|
local_ip: 192.168.128.1
|
||||||
|
local_port: 11
|
||||||
|
allowed_ips:
|
||||||
|
- any
|
||||||
|
register: create_one_many_idempotent
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_one_many_idempotent
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_one_many_idempotent is not changed
|
||||||
|
|
||||||
|
- name: Create port forwarding rule with check mode
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
port_forwarding:
|
||||||
|
- name: Test map
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: both
|
||||||
|
protocol: tcp
|
||||||
|
allowed_ips:
|
||||||
|
- 1.1.1.1
|
||||||
|
public_port: 10
|
||||||
|
local_port: 11
|
||||||
|
register: create_pf_check
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_pf_check
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_pf_check is changed
|
||||||
|
|
||||||
|
- name: Create port forwarding rule
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
port_forwarding:
|
||||||
|
- name: Test map
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: both
|
||||||
|
protocol: tcp
|
||||||
|
allowed_ips:
|
||||||
|
- 1.1.1.1
|
||||||
|
public_port: 10
|
||||||
|
local_port: 11
|
||||||
|
register: create_pf
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_pf
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_pf is changed
|
||||||
|
|
||||||
|
- name: Create port forwarding rule with idempotency
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
port_forwarding:
|
||||||
|
- name: Test map
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: both
|
||||||
|
protocol: tcp
|
||||||
|
allowed_ips:
|
||||||
|
- 1.1.1.1
|
||||||
|
public_port: 10
|
||||||
|
local_port: 11
|
||||||
|
register: create_pf_idempotent
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_pf_idempotent
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_pf_idempotent is not changed
|
||||||
|
- create_pf_idempotent.data.port_forwarding is defined
|
||||||
|
|
||||||
|
- name: Create multiple rules
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: present
|
||||||
|
port_forwarding:
|
||||||
|
- name: Test map
|
||||||
|
lan_ip: 192.168.128.1
|
||||||
|
uplink: both
|
||||||
|
protocol: tcp
|
||||||
|
allowed_ips:
|
||||||
|
- 1.1.1.2
|
||||||
|
public_port: 10
|
||||||
|
local_port: 11
|
||||||
|
one_to_many:
|
||||||
|
- public_ip: 1.1.1.3
|
||||||
|
uplink: internet1
|
||||||
|
port_rules:
|
||||||
|
- name: Test rule
|
||||||
|
protocol: tcp
|
||||||
|
public_port: 10
|
||||||
|
local_ip: 192.168.128.1
|
||||||
|
local_port: 11
|
||||||
|
allowed_ips:
|
||||||
|
- any
|
||||||
|
register: create_multiple
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: create_multiple
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_multiple is changed
|
||||||
|
- create_multiple.data.one_to_many is defined
|
||||||
|
- create_multiple.data.port_forwarding is defined
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- create_multiple is changed
|
||||||
|
- create_multiple.data.one_to_many is defined
|
||||||
|
- create_multiple.data.port_forwarding is defined
|
||||||
|
- create_multiple.diff.before.one_to_many is defined
|
||||||
|
- create_multiple.diff.before.port_forwarding is defined
|
||||||
|
- create_multiple.diff.after.one_to_many is defined
|
||||||
|
- create_multiple.diff.after.port_forwarding is defined
|
||||||
|
|
||||||
|
- name: Query all NAT rules
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: query
|
||||||
|
subset: all
|
||||||
|
register: query_all
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: query_all
|
||||||
|
|
||||||
|
- name: Query 1:1 NAT rules
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: query
|
||||||
|
subset: '1:1'
|
||||||
|
register: query_1to1
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: query_1to1
|
||||||
|
|
||||||
|
- name: Query 1:many NAT rules
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: query
|
||||||
|
subset: '1:many'
|
||||||
|
register: query_1tomany
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: query_1tomany
|
||||||
|
|
||||||
|
- name: Query port forwarding rules
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: query
|
||||||
|
subset: port_forwarding
|
||||||
|
register: query_pf
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: query_pf
|
||||||
|
|
||||||
|
- name: Query multiple rules
|
||||||
|
meraki_nat:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: query
|
||||||
|
subset:
|
||||||
|
- '1:1'
|
||||||
|
- '1:many'
|
||||||
|
register: query_multiple
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
var: query_multiple
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: Delete test network
|
||||||
|
meraki_network:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
org_name: '{{test_org_name}}'
|
||||||
|
net_name: '{{test_net_name}}'
|
||||||
|
state: absent
|
||||||
|
|
Loading…
Reference in a new issue