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:
Felix Fontein 2019-02-12 09:05:14 +01:00 committed by John R Barker
parent 2e3964b474
commit 09f78d2f6c
4 changed files with 166 additions and 3 deletions

View 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."

View file

@ -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']])

View file

@ -16,3 +16,6 @@
state: disabled
- name: "Loading tasks from {{ item }}"
include_tasks: "{{ item }}"
- name: Reset to factory defaults
ufw:
state: reset

View file

@ -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"