ufw: allow to insert rules relative to first/last IPv4/IPv6 rules (#49796)
* Insert should have type int. * Add insert_relative_to option. * Add changelog. * Add tests. * Improve comment.
This commit is contained in:
parent
2e3964b474
commit
09f78d2f6c
4 changed files with 166 additions and 3 deletions
3
changelogs/fragments/49796-ufw-insert-relative-to.yml
Normal file
3
changelogs/fragments/49796-ufw-insert-relative-to.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
minor_changes:
|
||||
- "ufw - type of option ``insert`` is now enforced to be ``int``."
|
||||
- "ufw - new ``insert_relative_to`` option allows to specify rule insertion position relative to first/last IPv4/IPv6 address."
|
|
@ -52,7 +52,34 @@ options:
|
|||
choices: [ on, off, low, medium, high, full ]
|
||||
insert:
|
||||
description:
|
||||
- Insert the corresponding rule as rule number NUM
|
||||
- Insert the corresponding rule as rule number NUM.
|
||||
- Note that ufw numbers rules starting with 1.
|
||||
type: int
|
||||
insert_relative_to:
|
||||
description:
|
||||
- Allows to interpret the index in I(insert) relative to a position.
|
||||
- C(zero) interprets the rule number as an absolute index (i.e. 1 is
|
||||
the first rule).
|
||||
- C(first-ipv4) interprets the rule number relative to the index of the
|
||||
first IPv4 rule, or relative to the position where the first IPv4 rule
|
||||
would be if there is currently none.
|
||||
- C(last-ipv4) interprets the rule number relative to the index of the
|
||||
last IPv4 rule, or relative to the position where the last IPv4 rule
|
||||
would be if there is currently none.
|
||||
- C(first-ipv6) interprets the rule number relative to the index of the
|
||||
first IPv6 rule, or relative to the position where the first IPv6 rule
|
||||
would be if there is currently none.
|
||||
- C(last-ipv6) interprets the rule number relative to the index of the
|
||||
last IPv6 rule, or relative to the position where the last IPv6 rule
|
||||
would be if there is currently none.
|
||||
choices:
|
||||
- zero
|
||||
- first-ipv4
|
||||
- last-ipv4
|
||||
- first-ipv6
|
||||
- last-ipv6
|
||||
default: zero
|
||||
version_added: "2.8"
|
||||
rule:
|
||||
description:
|
||||
- Add firewall rule
|
||||
|
@ -197,6 +224,30 @@ EXAMPLES = '''
|
|||
src: 2001:db8::/32
|
||||
port: 25
|
||||
|
||||
- name: Deny all IPv6 traffic to tcp port 20 on this host
|
||||
# this should be the first IPv6 rule
|
||||
ufw:
|
||||
rule: deny
|
||||
proto: tcp
|
||||
port: 20
|
||||
to_ip: "::"
|
||||
insert: 0
|
||||
insert_relative_to: first-ipv6
|
||||
|
||||
- name: Deny all IPv4 traffic to tcp port 20 on this host
|
||||
# This should be the third to last IPv4 rule
|
||||
# (insert: -1 addresses the second to last IPv4 rule;
|
||||
# so the new rule will be inserted before the second
|
||||
# to last IPv4 rule, and will be come the third to last
|
||||
# IPv4 rule.)
|
||||
ufw:
|
||||
rule: deny
|
||||
proto: tcp
|
||||
port: 20
|
||||
to_ip: "::"
|
||||
insert: -1
|
||||
insert_relative_to: last-ipv4
|
||||
|
||||
# Can be used to further restrict a global FORWARD policy set to allow
|
||||
- name: Deny forwarded/routed traffic from subnet 1.2.3.0/24 to subnet 4.5.6.0/24
|
||||
ufw:
|
||||
|
@ -247,7 +298,8 @@ def main():
|
|||
direction=dict(type='str', choices=['in', 'incoming', 'out', 'outgoing', 'routed']),
|
||||
delete=dict(type='bool', default=False),
|
||||
route=dict(type='bool', default=False),
|
||||
insert=dict(type='str'),
|
||||
insert=dict(type='int'),
|
||||
insert_relative_to=dict(choices=['zero', 'first-ipv4', 'last-ipv4', 'first-ipv6', 'last-ipv6'], default='zero'),
|
||||
rule=dict(type='str', choices=['allow', 'deny', 'limit', 'reject']),
|
||||
interface=dict(type='str', aliases=['if']),
|
||||
log=dict(type='bool', default=False),
|
||||
|
@ -427,7 +479,32 @@ def main():
|
|||
# [proto protocol] [app application] [comment COMMENT]
|
||||
cmd.append([module.boolean(params['route']), 'route'])
|
||||
cmd.append([module.boolean(params['delete']), 'delete'])
|
||||
cmd.append([params['insert'], "insert %s" % params['insert']])
|
||||
if params['insert'] is not None:
|
||||
relative_to_cmd = params['insert_relative_to']
|
||||
if relative_to_cmd == 'zero':
|
||||
insert_to = params['insert']
|
||||
else:
|
||||
(_, numbered_state, _) = module.run_command([ufw_bin, 'status', 'numbered'])
|
||||
numbered_line_re = re.compile(R'^\[ *([0-9]+)\] ')
|
||||
lines = [(numbered_line_re.match(line), '(v6)' in line) for line in numbered_state.splitlines()]
|
||||
lines = [(int(matcher.group(1)), ipv6) for (matcher, ipv6) in lines if matcher]
|
||||
last_number = max([no for (no, ipv6) in lines]) if lines else 0
|
||||
has_ipv4 = any([not ipv6 for (no, ipv6) in lines])
|
||||
has_ipv6 = any([ipv6 for (no, ipv6) in lines])
|
||||
if relative_to_cmd == 'first-ipv4':
|
||||
relative_to = 1
|
||||
elif relative_to_cmd == 'last-ipv4':
|
||||
relative_to = max([no for (no, ipv6) in lines if not ipv6]) if has_ipv4 else 1
|
||||
elif relative_to_cmd == 'first-ipv6':
|
||||
relative_to = max([no for (no, ipv6) in lines if not ipv6]) + 1 if has_ipv4 else 1
|
||||
elif relative_to_cmd == 'last-ipv6':
|
||||
relative_to = last_number if has_ipv6 else last_number + 1
|
||||
insert_to = params['insert'] + relative_to
|
||||
if insert_to > last_number:
|
||||
# ufw does not like it when the insert number is larger than the
|
||||
# maximal rule number for IPv4/IPv6.
|
||||
insert_to = None
|
||||
cmd.append([insert_to is not None, "insert %s" % insert_to])
|
||||
cmd.append([value])
|
||||
cmd.append([params['direction'], "%s" % params['direction']])
|
||||
cmd.append([params['interface'], "on %s" % params['interface']])
|
||||
|
|
|
@ -16,3 +16,6 @@
|
|||
state: disabled
|
||||
- name: "Loading tasks from {{ item }}"
|
||||
include_tasks: "{{ item }}"
|
||||
- name: Reset to factory defaults
|
||||
ufw:
|
||||
state: reset
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
---
|
||||
- name: Enable
|
||||
ufw:
|
||||
state: enabled
|
||||
register: enable
|
||||
|
||||
# ## CREATE RULES ############################
|
||||
- name: ipv4
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 22
|
||||
to_ip: 0.0.0.0
|
||||
- name: ipv4
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 23
|
||||
to_ip: 0.0.0.0
|
||||
|
||||
- name: ipv6
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 122
|
||||
to_ip: "::"
|
||||
- name: ipv6
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 123
|
||||
to_ip: "::"
|
||||
|
||||
- name: first-ipv4
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 10
|
||||
to_ip: 0.0.0.0
|
||||
insert: 0
|
||||
insert_relative_to: first-ipv4
|
||||
- name: last-ipv4
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 11
|
||||
to_ip: 0.0.0.0
|
||||
insert: 0
|
||||
insert_relative_to: last-ipv4
|
||||
|
||||
- name: first-ipv6
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 110
|
||||
to_ip: "::"
|
||||
insert: 0
|
||||
insert_relative_to: first-ipv6
|
||||
- name: last-ipv6
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 111
|
||||
to_ip: "::"
|
||||
insert: 0
|
||||
insert_relative_to: last-ipv6
|
||||
|
||||
# ## CHECK RESULT ############################
|
||||
- name: Get rules
|
||||
shell: |
|
||||
ufw status | grep DENY | cut -f 1-2 -d ' ' | grep -E "^(0\.0\.0\.0|::) [123]+"
|
||||
# Note that there was also a rule "ff02::fb mDNS" on at least one CI run;
|
||||
# to ignore these, the extra filtering (grepping for DENY and the regex) makes
|
||||
# sure to remove all rules not added here.
|
||||
register: ufw_status
|
||||
- assert:
|
||||
that:
|
||||
- ufw_status.stdout_lines == expected_stdout
|
||||
vars:
|
||||
expected_stdout:
|
||||
- "0.0.0.0 10"
|
||||
- "0.0.0.0 22"
|
||||
- "0.0.0.0 11"
|
||||
- "0.0.0.0 23"
|
||||
- ":: 110"
|
||||
- ":: 122"
|
||||
- ":: 111"
|
||||
- ":: 123"
|
Loading…
Reference in a new issue