2017-05-25 16:30:10 -07:00
|
|
|
DOCUMENTATION = '''
|
|
|
|
module: mt_ip_firewall
|
|
|
|
author:
|
|
|
|
- "Valentin Gurmeza"
|
|
|
|
- "Shaun Smiley"
|
|
|
|
version_added: "2.3"
|
|
|
|
short_description: Manage mikrotik /ip/firewall/
|
|
|
|
requirements:
|
|
|
|
- mt_api
|
|
|
|
description:
|
|
|
|
- Generic mikrotik firewall module
|
|
|
|
options:
|
|
|
|
hostname:
|
|
|
|
description:
|
|
|
|
- hotstname of mikrotik router
|
|
|
|
required: True
|
|
|
|
username:
|
|
|
|
description:
|
|
|
|
- username used to connect to mikrotik router
|
|
|
|
required: True
|
|
|
|
password:
|
|
|
|
description:
|
|
|
|
- password used for authentication to mikrotik router
|
|
|
|
required: True
|
|
|
|
rule:
|
|
|
|
description:
|
|
|
|
- a list containing dictionary parameters.
|
|
|
|
action, chain, comment, and place-before keys are required
|
|
|
|
parameter:
|
|
|
|
description:
|
|
|
|
- sub endpoint for mikrotik firewall
|
|
|
|
required: True
|
|
|
|
options:
|
|
|
|
- filter
|
|
|
|
- nat
|
2017-05-30 11:59:27 -07:00
|
|
|
- mangle
|
2017-05-25 16:30:10 -07:00
|
|
|
settings:
|
|
|
|
description:
|
|
|
|
- All Mikrotik compatible parameters for this particular endpoint.
|
|
|
|
Any yes/no values must be enclosed in double quotes
|
|
|
|
- if a firewall list containing dictionary parameters,
|
|
|
|
action, chain, comment, and place-before keys are required
|
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- absent or present
|
|
|
|
force:
|
|
|
|
description:
|
|
|
|
- True/False value to force remove the rule regardless of the position in the rule list.
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
- mt_ip_firewall:
|
|
|
|
hostname: "{{ inventory_hostname }}"
|
|
|
|
username: "{{ mt_user }}"
|
|
|
|
password: "{{ mt_pass }}"
|
|
|
|
state: present
|
|
|
|
parameter: filter
|
2017-12-06 18:38:55 +01:00
|
|
|
rule:
|
2017-05-25 16:30:10 -07:00
|
|
|
action: accept
|
|
|
|
chain: forward
|
|
|
|
comment: controlled by ansible
|
|
|
|
place-before: "2"
|
|
|
|
'''
|
|
|
|
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
2017-12-06 18:38:55 +01:00
|
|
|
from ansible.module_utils import mt_api
|
2017-05-25 16:30:10 -07:00
|
|
|
import re
|
|
|
|
from copy import copy
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
|
|
module = AnsibleModule(
|
2017-06-01 17:40:53 -07:00
|
|
|
argument_spec=dict(
|
|
|
|
hostname = dict(required=True),
|
|
|
|
username = dict(required=True),
|
2017-12-22 10:57:19 +01:00
|
|
|
password = dict(required=True, no_log=True),
|
2017-06-01 17:40:53 -07:00
|
|
|
rule = dict(required=False, type='dict'),
|
|
|
|
parameter = dict(required=True, type='str'),
|
|
|
|
state = dict(
|
|
|
|
required = False,
|
|
|
|
default = "present",
|
|
|
|
choices = ['present', 'absent'],
|
|
|
|
type = 'str'
|
|
|
|
),
|
|
|
|
),
|
|
|
|
supports_check_mode=True
|
2017-05-25 16:30:10 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
hostname = module.params['hostname']
|
|
|
|
username = module.params['username']
|
|
|
|
password = module.params['password']
|
|
|
|
rule = module.params['rule']
|
|
|
|
state = module.params['state']
|
|
|
|
api_path = '/ip/firewall/' + module.params['parameter']
|
2017-06-01 17:40:53 -07:00
|
|
|
check_mode = module.check_mode
|
2017-05-25 16:30:10 -07:00
|
|
|
# ##############################################
|
|
|
|
# Check if "place-before" is an integer
|
|
|
|
# #############################################
|
|
|
|
try:
|
|
|
|
desired_order = int(rule['place-before'])
|
|
|
|
except:
|
|
|
|
module.exit_json(
|
2017-06-01 17:40:53 -07:00
|
|
|
failed=True,
|
|
|
|
changed=False,
|
|
|
|
msg="place-before is not set or is not set to an integer",
|
|
|
|
)
|
2017-05-25 16:30:10 -07:00
|
|
|
changed = False
|
|
|
|
msg = ""
|
|
|
|
|
|
|
|
mk = mt_api.Mikrotik(hostname, username, password)
|
|
|
|
try:
|
|
|
|
mk.login()
|
|
|
|
except:
|
|
|
|
module.fail_json(
|
|
|
|
msg="Could not log into Mikrotik device." +
|
|
|
|
" Check the username and password.",
|
|
|
|
)
|
|
|
|
|
|
|
|
filter_response = mk.api_print(api_path)
|
|
|
|
current_rule = None
|
|
|
|
current_id = None
|
|
|
|
existing_order = None
|
|
|
|
last_item = len(filter_response) - 2
|
|
|
|
changed_msg = []
|
|
|
|
|
|
|
|
# Always set the comment to the order_number
|
|
|
|
if 'comment' in rule:
|
|
|
|
rule['comment'] = str(desired_order) + " " + str(rule['comment'])
|
|
|
|
else:
|
|
|
|
rule['comment'] = str(desired_order)
|
|
|
|
|
|
|
|
if desired_order <= last_item:
|
|
|
|
placed_at_the_end = False
|
|
|
|
else:
|
|
|
|
placed_at_the_end = True
|
|
|
|
# remove the place-before if we are placing
|
|
|
|
# the rule at the bottom of the chain
|
|
|
|
rule.pop('place-before', None)
|
|
|
|
|
|
|
|
# Check rule is not present
|
|
|
|
# find existing rule
|
|
|
|
# current_rule is what's on mikrotik right now
|
|
|
|
for index, current_param in enumerate(filter_response):
|
|
|
|
if 'comment' in current_param[1]:
|
|
|
|
if re.search(r"^" + str(desired_order) + "\s+", current_param[1]['comment']):
|
|
|
|
current_id = current_param[1]['.id']
|
|
|
|
existing_order = index
|
|
|
|
current_rule = current_param[1]
|
|
|
|
# remove the place-before since we'll be editing not moving it
|
|
|
|
rule.pop('place-before', None)
|
|
|
|
|
|
|
|
# ensure the rule if state is present
|
|
|
|
if state == "present":
|
|
|
|
# if we don't have an existing rule to match
|
|
|
|
# the desired we create a new one
|
|
|
|
if not current_rule:
|
2017-06-01 17:40:53 -07:00
|
|
|
if not check_mode:
|
|
|
|
mk.api_add(api_path, rule)
|
2017-05-25 16:30:10 -07:00
|
|
|
changed = True,
|
|
|
|
# if current_rule is true we need to ensure the changes
|
|
|
|
else:
|
|
|
|
out_params = {}
|
|
|
|
old_params = {}
|
|
|
|
for desired_param in rule:
|
|
|
|
rule[desired_param] = str(rule[desired_param])
|
|
|
|
if desired_param in current_rule:
|
|
|
|
if current_rule[desired_param] != rule[desired_param]:
|
|
|
|
out_params[desired_param] = rule[desired_param]
|
|
|
|
old_params[desired_param] = current_rule[desired_param]
|
|
|
|
else:
|
|
|
|
out_params[desired_param] = rule[desired_param]
|
|
|
|
if desired_param in current_rule:
|
|
|
|
old_params[desired_param] = current_rule[desired_param]
|
|
|
|
|
|
|
|
# When out_params has been set it means we found our diff
|
|
|
|
# and will set it on the mikrotik
|
|
|
|
if out_params:
|
|
|
|
if current_id is not None:
|
|
|
|
out_params['.id'] = current_id
|
|
|
|
|
2017-06-01 17:40:53 -07:00
|
|
|
if not check_mode:
|
|
|
|
mk.api_edit(
|
|
|
|
base_path = api_path,
|
|
|
|
params = out_params
|
|
|
|
)
|
2017-05-25 16:30:10 -07:00
|
|
|
|
|
|
|
# we don't need to show the .id in the changed message
|
|
|
|
if '.id' in out_params:
|
|
|
|
del out_params['.id']
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
changed_msg.append({
|
|
|
|
"new_params": out_params,
|
|
|
|
"old_params": old_params,
|
|
|
|
})
|
|
|
|
|
|
|
|
# ensure the rule is in right position
|
|
|
|
if current_id:
|
|
|
|
if int(existing_order) != int(desired_order):
|
|
|
|
api_path += '/move'
|
|
|
|
params = False
|
|
|
|
if placed_at_the_end:
|
|
|
|
if existing_order > last_item:
|
|
|
|
params = {
|
|
|
|
'.id': current_id,
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
params = {
|
|
|
|
'.id': current_id,
|
|
|
|
'destination': desired_order
|
|
|
|
}
|
|
|
|
if params:
|
2017-06-01 17:40:53 -07:00
|
|
|
if not check_mode:
|
|
|
|
mk.api_command(api_path, params)
|
2017-05-25 16:30:10 -07:00
|
|
|
changed_msg.append({
|
|
|
|
"moved": existing_order,
|
|
|
|
"to": old_params,
|
|
|
|
})
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
#####################################
|
|
|
|
# Remove the rule
|
|
|
|
#####################################
|
|
|
|
elif state == "absent":
|
|
|
|
if current_rule:
|
2017-06-01 17:40:53 -07:00
|
|
|
if not check_mode:
|
|
|
|
mk.api_remove(api_path, current_id)
|
2017-05-25 16:30:10 -07:00
|
|
|
changed = True
|
|
|
|
changed_msg.append("removed rule: " + str(desired_order))
|
|
|
|
else:
|
|
|
|
failed = True
|
|
|
|
|
|
|
|
if changed:
|
|
|
|
module.exit_json(
|
|
|
|
failed=False,
|
|
|
|
changed=True,
|
|
|
|
msg=changed_msg
|
|
|
|
)
|
|
|
|
elif not changed:
|
|
|
|
module.exit_json(
|
|
|
|
failed=False,
|
|
|
|
changed=False,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
module.fail_json()
|
|
|
|
|
|
|
|
##########################################
|
|
|
|
# To DO:
|
|
|
|
# Clean up duplicate items
|
|
|
|
###########################################
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|