Add explicit coverage of argspec choices with strings that shadow YAML bools (#72122)

* Add explicit coverage of argspec choices with strings that shadow YAML bools

* ci_complete ci_coverage

* Remove incidental_ufw

* ci_complete ci_coverage
This commit is contained in:
Matt Martz 2020-10-06 16:00:28 -05:00 committed by GitHub
parent 865113aa29
commit cfa41898c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 23 additions and 1379 deletions

View file

@ -58,6 +58,13 @@ def main():
'thing': {}, 'thing': {},
}, },
}, },
'choices_with_strings_like_bools': {
'type': 'str',
'choices': [
'on',
'off',
],
},
}, },
required_if=( required_if=(
('state', 'present', ('path', 'content'), True), ('state', 'present', ('path', 'content'), True),

View file

@ -126,6 +126,18 @@
- thing: foo - thing: foo
register: argspec_suboptions_list_no_elements register: argspec_suboptions_list_no_elements
- argspec:
choices_with_strings_like_bools: on
register: argspec_choices_with_strings_like_bools_true
- argspec:
choices_with_strings_like_bools: 'on'
register: argspec_choices_with_strings_like_bools_true_bool
- argspec:
choices_with_strings_like_bools: off
register: argspec_choices_with_strings_like_bools_false
- assert: - assert:
that: that:
- argspec_required_if_fail is failed - argspec_required_if_fail is failed
@ -163,3 +175,7 @@
- >- - >-
argspec_suboptions_list_no_elements.suboptions_list_no_elements.0 == {'thing': 'foo'} argspec_suboptions_list_no_elements.suboptions_list_no_elements.0 == {'thing': 'foo'}
- argspec_choices_with_strings_like_bools_true.choices_with_strings_like_bools == 'on'
- argspec_choices_with_strings_like_bools_true_bool.choices_with_strings_like_bools == 'on'
- argspec_choices_with_strings_like_bools_false.choices_with_strings_like_bools == 'off'

View file

@ -1,13 +0,0 @@
shippable/posix/incidental
skip/aix
skip/power/centos
skip/osx
skip/macos
skip/freebsd
skip/rhel8.0
skip/rhel8.0b
skip/rhel8.1b
skip/docker
needs/root
destructive
needs/target/setup_epel

View file

@ -1,34 +0,0 @@
---
# Make sure ufw is installed
- name: Install EPEL repository (RHEL only)
include_role:
name: setup_epel
when: ansible_distribution == 'RedHat'
- name: Install iptables (SuSE only)
package:
name: iptables
become: yes
when: ansible_os_family == 'Suse'
- name: Install ufw
become: yes
package:
name: ufw
# Run the tests
- block:
- include_tasks: run-test.yml
with_fileglob:
- "tests/*.yml"
become: yes
# Cleanup
always:
- pause:
# ufw creates backups of the rule files with a timestamp; if reset is called
# twice in a row fast enough (so that both timestamps are taken in the same second),
# the second call will notice that the backup files are already there and fail.
# Waiting one second fixes this problem.
seconds: 1
- name: Reset ufw to factory defaults and disable
ufw:
state: reset

View file

@ -1,21 +0,0 @@
---
- pause:
# ufw creates backups of the rule files with a timestamp; if reset is called
# twice in a row fast enough (so that both timestamps are taken in the same second),
# the second call will notice that the backup files are already there and fail.
# Waiting one second fixes this problem.
seconds: 1
- name: Reset ufw to factory defaults
ufw:
state: reset
- name: Disable ufw
ufw:
# Some versions of ufw have a bug which won't disable on reset.
# That's why we explicitly deactivate here. See
# https://bugs.launchpad.net/ufw/+bug/1810082
state: disabled
- name: "Loading tasks from {{ item }}"
include_tasks: "{{ item }}"
- name: Reset to factory defaults
ufw:
state: reset

View file

@ -1,402 +0,0 @@
---
# ############################################
- name: Make sure it is off
ufw:
state: disabled
- name: Enable (check mode)
ufw:
state: enabled
check_mode: yes
register: enable_check
- name: Enable
ufw:
state: enabled
register: enable
- name: Enable (idempotency)
ufw:
state: enabled
register: enable_idem
- name: Enable (idempotency, check mode)
ufw:
state: enabled
check_mode: yes
register: enable_idem_check
- assert:
that:
- enable_check is changed
- enable is changed
- enable_idem is not changed
- enable_idem_check is not changed
# ############################################
- name: ipv4 allow (check mode)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
check_mode: yes
register: ipv4_allow_check
- name: ipv4 allow
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
register: ipv4_allow
- name: ipv4 allow (idempotency)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
register: ipv4_allow_idem
- name: ipv4 allow (idempotency, check mode)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
check_mode: yes
register: ipv4_allow_idem_check
- assert:
that:
- ipv4_allow_check is changed
- ipv4_allow is changed
- ipv4_allow_idem is not changed
- ipv4_allow_idem_check is not changed
# ############################################
- name: delete ipv4 allow (check mode)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
delete: yes
check_mode: yes
register: delete_ipv4_allow_check
- name: delete ipv4 allow
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
delete: yes
register: delete_ipv4_allow
- name: delete ipv4 allow (idempotency)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
delete: yes
register: delete_ipv4_allow_idem
- name: delete ipv4 allow (idempotency, check mode)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
delete: yes
check_mode: yes
register: delete_ipv4_allow_idem_check
- assert:
that:
- delete_ipv4_allow_check is changed
- delete_ipv4_allow is changed
- delete_ipv4_allow_idem is not changed
- delete_ipv4_allow_idem_check is not changed
# ############################################
- name: ipv6 allow (check mode)
ufw:
rule: allow
port: 23
to_ip: "::"
check_mode: yes
register: ipv6_allow_check
- name: ipv6 allow
ufw:
rule: allow
port: 23
to_ip: "::"
register: ipv6_allow
- name: ipv6 allow (idempotency)
ufw:
rule: allow
port: 23
to_ip: "::"
register: ipv6_allow_idem
- name: ipv6 allow (idempotency, check mode)
ufw:
rule: allow
port: 23
to_ip: "::"
check_mode: yes
register: ipv6_allow_idem_check
- assert:
that:
- ipv6_allow_check is changed
- ipv6_allow is changed
- ipv6_allow_idem is not changed
- ipv6_allow_idem_check is not changed
# ############################################
- name: delete ipv6 allow (check mode)
ufw:
rule: allow
port: 23
to_ip: "::"
delete: yes
check_mode: yes
register: delete_ipv6_allow_check
- name: delete ipv6 allow
ufw:
rule: allow
port: 23
to_ip: "::"
delete: yes
register: delete_ipv6_allow
- name: delete ipv6 allow (idempotency)
ufw:
rule: allow
port: 23
to_ip: "::"
delete: yes
register: delete_ipv6_allow_idem
- name: delete ipv6 allow (idempotency, check mode)
ufw:
rule: allow
port: 23
to_ip: "::"
delete: yes
check_mode: yes
register: delete_ipv6_allow_idem_check
- assert:
that:
- delete_ipv6_allow_check is changed
- delete_ipv6_allow is changed
- delete_ipv6_allow_idem is not changed
- delete_ipv6_allow_idem_check is not changed
# ############################################
- name: ipv4 allow (check mode)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
check_mode: yes
register: ipv4_allow_check
- name: ipv4 allow
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
register: ipv4_allow
- name: ipv4 allow (idempotency)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
register: ipv4_allow_idem
- name: ipv4 allow (idempotency, check mode)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
check_mode: yes
register: ipv4_allow_idem_check
- assert:
that:
- ipv4_allow_check is changed
- ipv4_allow is changed
- ipv4_allow_idem is not changed
- ipv4_allow_idem_check is not changed
# ############################################
- name: delete ipv4 allow (check mode)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
delete: yes
check_mode: yes
register: delete_ipv4_allow_check
- name: delete ipv4 allow
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
delete: yes
register: delete_ipv4_allow
- name: delete ipv4 allow (idempotency)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
delete: yes
register: delete_ipv4_allow_idem
- name: delete ipv4 allow (idempotency, check mode)
ufw:
rule: allow
port: 23
to_ip: 0.0.0.0
delete: yes
check_mode: yes
register: delete_ipv4_allow_idem_check
- assert:
that:
- delete_ipv4_allow_check is changed
- delete_ipv4_allow is changed
- delete_ipv4_allow_idem is not changed
- delete_ipv4_allow_idem_check is not changed
# ############################################
- name: ipv6 allow (check mode)
ufw:
rule: allow
port: 23
to_ip: "::"
check_mode: yes
register: ipv6_allow_check
- name: ipv6 allow
ufw:
rule: allow
port: 23
to_ip: "::"
register: ipv6_allow
- name: ipv6 allow (idempotency)
ufw:
rule: allow
port: 23
to_ip: "::"
register: ipv6_allow_idem
- name: ipv6 allow (idempotency, check mode)
ufw:
rule: allow
port: 23
to_ip: "::"
check_mode: yes
register: ipv6_allow_idem_check
- assert:
that:
- ipv6_allow_check is changed
- ipv6_allow is changed
- ipv6_allow_idem is not changed
- ipv6_allow_idem_check is not changed
# ############################################
- name: delete ipv6 allow (check mode)
ufw:
rule: allow
port: 23
to_ip: "::"
delete: yes
check_mode: yes
register: delete_ipv6_allow_check
- name: delete ipv6 allow
ufw:
rule: allow
port: 23
to_ip: "::"
delete: yes
register: delete_ipv6_allow
- name: delete ipv6 allow (idempotency)
ufw:
rule: allow
port: 23
to_ip: "::"
delete: yes
register: delete_ipv6_allow_idem
- name: delete ipv6 allow (idempotency, check mode)
ufw:
rule: allow
port: 23
to_ip: "::"
delete: yes
check_mode: yes
register: delete_ipv6_allow_idem_check
- assert:
that:
- delete_ipv6_allow_check is changed
- delete_ipv6_allow is changed
- delete_ipv6_allow_idem is not changed
- delete_ipv6_allow_idem_check is not changed
# ############################################
- name: Reload ufw
ufw:
state: reloaded
register: reload
- name: Reload ufw (check mode)
ufw:
state: reloaded
check_mode: yes
register: reload_check
- assert:
that:
- reload is changed
- reload_check is changed
# ############################################
- name: Disable (check mode)
ufw:
state: disabled
check_mode: yes
register: disable_check
- name: Disable
ufw:
state: disabled
register: disable
- name: Disable (idempotency)
ufw:
state: disabled
register: disable_idem
- name: Disable (idempotency, check mode)
ufw:
state: disabled
check_mode: yes
register: disable_idem_check
- assert:
that:
- disable_check is changed
- disable is changed
- disable_idem is not changed
- disable_idem_check is not changed
# ############################################
- name: Re-enable
ufw:
state: enabled
- name: Reset (check mode)
ufw:
state: reset
check_mode: yes
register: reset_check
- pause:
# Should not be needed, but since ufw is ignoring --dry-run for reset
# (https://bugs.launchpad.net/ufw/+bug/1810082) we have to wait here as well.
seconds: 1
- name: Reset
ufw:
state: reset
register: reset
- pause:
# ufw creates backups of the rule files with a timestamp; if reset is called
# twice in a row fast enough (so that both timestamps are taken in the same second),
# the second call will notice that the backup files are already there and fail.
# Waiting one second fixes this problem.
seconds: 1
- name: Reset (idempotency)
ufw:
state: reset
register: reset_idem
- pause:
# Should not be needed, but since ufw is ignoring --dry-run for reset
# (https://bugs.launchpad.net/ufw/+bug/1810082) we have to wait here as well.
seconds: 1
- name: Reset (idempotency, check mode)
ufw:
state: reset
check_mode: yes
register: reset_idem_check
- assert:
that:
- reset_check is changed
- reset is changed
- reset_idem is changed
- reset_idem_check is changed

View file

@ -1,150 +0,0 @@
---
- name: Enable ufw
ufw:
state: enabled
# ############################################
- name: Make sure logging is off
ufw:
logging: no
- name: Logging (check mode)
ufw:
logging: yes
check_mode: yes
register: logging_check
- name: Logging
ufw:
logging: yes
register: logging
- name: Get logging
shell: |
ufw status verbose | grep "^Logging:"
register: ufw_logging
environment:
LC_ALL: C
- name: Logging (idempotency)
ufw:
logging: yes
register: logging_idem
- name: Logging (idempotency, check mode)
ufw:
logging: yes
check_mode: yes
register: logging_idem_check
- name: Logging (change, check mode)
ufw:
logging: full
check_mode: yes
register: logging_change_check
- name: Logging (change)
ufw:
logging: full
register: logging_change
- name: Get logging
shell: |
ufw status verbose | grep "^Logging:"
register: ufw_logging_change
environment:
LC_ALL: C
- assert:
that:
- logging_check is changed
- logging is changed
- "ufw_logging.stdout == 'Logging: on (low)'"
- logging_idem is not changed
- logging_idem_check is not changed
- "ufw_logging_change.stdout == 'Logging: on (full)'"
- logging_change is changed
- logging_change_check is changed
# ############################################
- name: Default (check mode)
ufw:
default: reject
direction: incoming
check_mode: yes
register: default_check
- name: Default
ufw:
default: reject
direction: incoming
register: default
- name: Get defaults
shell: |
ufw status verbose | grep "^Default:"
register: ufw_defaults
environment:
LC_ALL: C
- name: Default (idempotency)
ufw:
default: reject
direction: incoming
register: default_idem
- name: Default (idempotency, check mode)
ufw:
default: reject
direction: incoming
check_mode: yes
register: default_idem_check
- name: Default (change, check mode)
ufw:
default: allow
direction: incoming
check_mode: yes
register: default_change_check
- name: Default (change)
ufw:
default: allow
direction: incoming
register: default_change
- name: Get defaults
shell: |
ufw status verbose | grep "^Default:"
register: ufw_defaults_change
environment:
LC_ALL: C
- name: Default (change again)
ufw:
default: deny
direction: incoming
register: default_change_2
- name: Default (change incoming implicitly, check mode)
ufw:
default: allow
check_mode: yes
register: default_change_implicit_check
- name: Default (change incoming implicitly)
ufw:
default: allow
register: default_change_implicit
- name: Get defaults
shell: |
ufw status verbose | grep "^Default:"
register: ufw_defaults_change_implicit
environment:
LC_ALL: C
- name: Default (change incoming implicitly, idempotent, check mode)
ufw:
default: allow
check_mode: yes
register: default_change_implicit_idem_check
- name: Default (change incoming implicitly, idempotent)
ufw:
default: allow
register: default_change_implicit_idem
- assert:
that:
- default_check is changed
- default is changed
- "'reject (incoming)' in ufw_defaults.stdout"
- default_idem is not changed
- default_idem_check is not changed
- default_change_check is changed
- default_change is changed
- "'allow (incoming)' in ufw_defaults_change.stdout"
- default_change_2 is changed
- default_change_implicit_check is changed
- default_change_implicit is changed
- default_change_implicit_idem_check is not changed
- default_change_implicit_idem is not changed
- "'allow (incoming)' in ufw_defaults_change_implicit.stdout"

View file

@ -1,80 +0,0 @@
---
- 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"

View file

@ -1,81 +0,0 @@
- name: Enable
ufw:
state: enabled
- name: Route with interface in and out
ufw:
rule: allow
route: yes
interface_in: foo
interface_out: bar
proto: tcp
from_ip: 10.1.1.1
to_ip: 10.8.8.8
from_port: 1111
to_port: 2222
- name: Route with interface in
ufw:
rule: allow
route: yes
interface_in: foo
proto: tcp
from_ip: 10.1.1.1
from_port: 1111
- name: Route with interface out
ufw:
rule: allow
route: yes
interface_out: bar
proto: tcp
from_ip: 10.1.1.1
from_port: 1111
- name: Non-route with interface in
ufw:
rule: allow
interface_in: foo
proto: tcp
from_ip: 10.1.1.1
from_port: 3333
- name: Non-route with interface out
ufw:
rule: allow
interface_out: bar
proto: tcp
from_ip: 10.1.1.1
from_port: 4444
- name: Check result
shell: ufw status |grep -E '(ALLOW|DENY|REJECT|LIMIT)' |sed -E 's/[ \t]+/ /g'
register: ufw_status
- assert:
that:
- '"10.8.8.8 2222/tcp on bar ALLOW FWD 10.1.1.1 1111/tcp on foo " in stdout'
- '"Anywhere ALLOW FWD 10.1.1.1 1111/tcp on foo " in stdout'
- '"Anywhere on bar ALLOW FWD 10.1.1.1 1111/tcp " in stdout'
- '"Anywhere on foo ALLOW 10.1.1.1 3333/tcp " in stdout'
- '"Anywhere ALLOW OUT 10.1.1.1 4444/tcp on bar " in stdout'
vars:
stdout: '{{ ufw_status.stdout_lines }}'
- name: Non-route with interface_in and interface_out
ufw:
rule: allow
interface_in: foo
interface_out: bar
proto: tcp
from_ip: 10.1.1.1
from_port: 1111
to_ip: 10.8.8.8
to_port: 2222
ignore_errors: yes
register: ufw_non_route_iface
- assert:
that:
- ufw_non_route_iface is failed
- '"Only route rules" in ufw_non_route_iface.msg'

View file

@ -1,598 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2014, Ahti Kitsik <ak@ahtik.com>
# Copyright: (c) 2014, Jarno Keskikangas <jarno.keskikangas@gmail.com>
# Copyright: (c) 2013, Aleksey Ovcharenko <aleksey.ovcharenko@gmail.com>
# Copyright: (c) 2013, James Martin <jmartin@basho.com>
# 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: ufw
short_description: Manage firewall with UFW
description:
- Manage firewall with UFW.
version_added: 1.6
author:
- Aleksey Ovcharenko (@ovcharenko)
- Jarno Keskikangas (@pyykkis)
- Ahti Kitsik (@ahtik)
notes:
- See C(man ufw) for more examples.
requirements:
- C(ufw) package
options:
state:
description:
- C(enabled) reloads firewall and enables firewall on boot.
- C(disabled) unloads firewall and disables firewall on boot.
- C(reloaded) reloads firewall.
- C(reset) disables and resets firewall to installation defaults.
type: str
choices: [ disabled, enabled, reloaded, reset ]
default:
description:
- Change the default policy for incoming or outgoing traffic.
type: str
choices: [ allow, deny, reject ]
aliases: [ policy ]
direction:
description:
- Select direction for a rule or default policy command. Mutually
exclusive with I(interface_in) and I(interface_out).
type: str
choices: [ in, incoming, out, outgoing, routed ]
logging:
description:
- Toggles logging. Logged packets use the LOG_KERN syslog facility.
type: str
choices: [ 'on', 'off', low, medium, high, full ]
insert:
description:
- 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.
type: str
choices: [ first-ipv4, first-ipv6, last-ipv4, last-ipv6, zero ]
default: zero
version_added: "2.8"
rule:
description:
- Add firewall rule
type: str
choices: [ allow, deny, limit, reject ]
log:
description:
- Log new connections matched to this rule
type: bool
from_ip:
description:
- Source IP address.
type: str
default: any
aliases: [ from, src ]
from_port:
description:
- Source port.
type: str
to_ip:
description:
- Destination IP address.
type: str
default: any
aliases: [ dest, to]
to_port:
description:
- Destination port.
type: str
aliases: [ port ]
proto:
description:
- TCP/IP protocol.
type: str
choices: [ any, tcp, udp, ipv6, esp, ah, gre, igmp ]
aliases: [ protocol ]
name:
description:
- Use profile located in C(/etc/ufw/applications.d).
type: str
aliases: [ app ]
delete:
description:
- Delete rule.
type: bool
interface:
description:
- Specify interface for the rule. The direction (in or out) used
for the interface depends on the value of I(direction). See
I(interface_in) and I(interface_out) for routed rules that needs
to supply both an input and output interface. Mutually
exclusive with I(interface_in) and I(interface_out).
type: str
aliases: [ if ]
interface_in:
description:
- Specify input interface for the rule. This is mutually
exclusive with I(direction) and I(interface). However, it is
compatible with I(interface_out) for routed rules.
type: str
aliases: [ if_in ]
version_added: "2.10"
interface_out:
description:
- Specify output interface for the rule. This is mutually
exclusive with I(direction) and I(interface). However, it is
compatible with I(interface_in) for routed rules.
type: str
aliases: [ if_out ]
version_added: "2.10"
route:
description:
- Apply the rule to routed/forwarded packets.
type: bool
comment:
description:
- Add a comment to the rule. Requires UFW version >=0.35.
type: str
version_added: "2.4"
'''
EXAMPLES = r'''
- name: Allow everything and enable UFW
ufw:
state: enabled
policy: allow
- name: Set logging
ufw:
logging: 'on'
# Sometimes it is desirable to let the sender know when traffic is
# being denied, rather than simply ignoring it. In these cases, use
# reject instead of deny. In addition, log rejected connections:
- ufw:
rule: reject
port: auth
log: yes
# ufw supports connection rate limiting, which is useful for protecting
# against brute-force login attacks. ufw will deny connections if an IP
# address has attempted to initiate 6 or more connections in the last
# 30 seconds. See http://www.debian-administration.org/articles/187
# for details. Typical usage is:
- ufw:
rule: limit
port: ssh
proto: tcp
# Allow OpenSSH. (Note that as ufw manages its own state, simply removing
# a rule=allow task can leave those ports exposed. Either use delete=yes
# or a separate state=reset task)
- ufw:
rule: allow
name: OpenSSH
- name: Delete OpenSSH rule
ufw:
rule: allow
name: OpenSSH
delete: yes
- name: Deny all access to port 53
ufw:
rule: deny
port: '53'
- name: Allow port range 60000-61000
ufw:
rule: allow
port: 60000:61000
proto: tcp
- name: Allow all access to tcp port 80
ufw:
rule: allow
port: '80'
proto: tcp
- name: Allow all access from RFC1918 networks to this host
ufw:
rule: allow
src: '{{ item }}'
loop:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- name: Deny access to udp port 514 from host 1.2.3.4 and include a comment
ufw:
rule: deny
proto: udp
src: 1.2.3.4
port: '514'
comment: Block syslog
- name: Allow incoming access to eth0 from 1.2.3.5 port 5469 to 1.2.3.4 port 5469
ufw:
rule: allow
interface: eth0
direction: in
proto: udp
src: 1.2.3.5
from_port: '5469'
dest: 1.2.3.4
to_port: '5469'
# Note that IPv6 must be enabled in /etc/default/ufw for IPv6 firewalling to work.
- name: Deny all traffic from the IPv6 2001:db8::/32 to tcp port 25 on this host
ufw:
rule: deny
proto: tcp
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:
rule: deny
route: yes
src: 1.2.3.0/24
dest: 4.5.6.0/24
'''
import re
from operator import itemgetter
from ansible.module_utils.basic import AnsibleModule
def compile_ipv4_regexp():
r = r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
r += r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"
return re.compile(r)
def compile_ipv6_regexp():
"""
validation pattern provided by :
https://stackoverflow.com/questions/53497/regular-expression-that-matches-
valid-ipv6-addresses#answer-17871737
"""
r = r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:"
r += r"|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}"
r += r"(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4})"
r += r"{1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]"
r += r"{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]"
r += r"{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4})"
r += r"{0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]"
r += r"|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}"
r += r"[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}"
r += r"[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
return re.compile(r)
def main():
command_keys = ['state', 'default', 'rule', 'logging']
module = AnsibleModule(
argument_spec=dict(
state=dict(type='str', choices=['enabled', 'disabled', 'reloaded', 'reset']),
default=dict(type='str', aliases=['policy'], choices=['allow', 'deny', 'reject']),
logging=dict(type='str', choices=['full', 'high', 'low', 'medium', 'off', 'on']),
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='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']),
interface_in=dict(type='str', aliases=['if_in']),
interface_out=dict(type='str', aliases=['if_out']),
log=dict(type='bool', default=False),
from_ip=dict(type='str', default='any', aliases=['from', 'src']),
from_port=dict(type='str'),
to_ip=dict(type='str', default='any', aliases=['dest', 'to']),
to_port=dict(type='str', aliases=['port']),
proto=dict(type='str', aliases=['protocol'], choices=['ah', 'any', 'esp', 'ipv6', 'tcp', 'udp', 'gre', 'igmp']),
name=dict(type='str', aliases=['app']),
comment=dict(type='str'),
),
supports_check_mode=True,
mutually_exclusive=[
['name', 'proto', 'logging'],
# Mutual exclusivity with `interface` implied by `required_by`.
['direction', 'interface_in'],
['direction', 'interface_out'],
],
required_one_of=([command_keys]),
required_by=dict(
interface=('direction', ),
),
)
cmds = []
ipv4_regexp = compile_ipv4_regexp()
ipv6_regexp = compile_ipv6_regexp()
def filter_line_that_not_start_with(pattern, content):
return ''.join([line for line in content.splitlines(True) if line.startswith(pattern)])
def filter_line_that_contains(pattern, content):
return [line for line in content.splitlines(True) if pattern in line]
def filter_line_that_not_contains(pattern, content):
return ''.join([line for line in content.splitlines(True) if not line.contains(pattern)])
def filter_line_that_match_func(match_func, content):
return ''.join([line for line in content.splitlines(True) if match_func(line) is not None])
def filter_line_that_contains_ipv4(content):
return filter_line_that_match_func(ipv4_regexp.search, content)
def filter_line_that_contains_ipv6(content):
return filter_line_that_match_func(ipv6_regexp.search, content)
def is_starting_by_ipv4(ip):
return ipv4_regexp.match(ip) is not None
def is_starting_by_ipv6(ip):
return ipv6_regexp.match(ip) is not None
def execute(cmd, ignore_error=False):
cmd = ' '.join(map(itemgetter(-1), filter(itemgetter(0), cmd)))
cmds.append(cmd)
(rc, out, err) = module.run_command(cmd, environ_update={"LANG": "C"})
if rc != 0 and not ignore_error:
module.fail_json(msg=err or out, commands=cmds)
return out
def get_current_rules():
user_rules_files = ["/lib/ufw/user.rules",
"/lib/ufw/user6.rules",
"/etc/ufw/user.rules",
"/etc/ufw/user6.rules",
"/var/lib/ufw/user.rules",
"/var/lib/ufw/user6.rules"]
cmd = [[grep_bin], ["-h"], ["'^### tuple'"]]
cmd.extend([[f] for f in user_rules_files])
return execute(cmd, ignore_error=True)
def ufw_version():
"""
Returns the major and minor version of ufw installed on the system.
"""
out = execute([[ufw_bin], ["--version"]])
lines = [x for x in out.split('\n') if x.strip() != '']
if len(lines) == 0:
module.fail_json(msg="Failed to get ufw version.", rc=0, out=out)
matches = re.search(r'^ufw.+(\d+)\.(\d+)(?:\.(\d+))?.*$', lines[0])
if matches is None:
module.fail_json(msg="Failed to get ufw version.", rc=0, out=out)
# Convert version to numbers
major = int(matches.group(1))
minor = int(matches.group(2))
rev = 0
if matches.group(3) is not None:
rev = int(matches.group(3))
return major, minor, rev
params = module.params
commands = dict((key, params[key]) for key in command_keys if params[key])
# Ensure ufw is available
ufw_bin = module.get_bin_path('ufw', True)
grep_bin = module.get_bin_path('grep', True)
# Save the pre state and rules in order to recognize changes
pre_state = execute([[ufw_bin], ['status verbose']])
pre_rules = get_current_rules()
changed = False
# Execute filter
for (command, value) in commands.items():
cmd = [[ufw_bin], [module.check_mode, '--dry-run']]
if command == 'state':
states = {'enabled': 'enable', 'disabled': 'disable',
'reloaded': 'reload', 'reset': 'reset'}
if value in ['reloaded', 'reset']:
changed = True
if module.check_mode:
# "active" would also match "inactive", hence the space
ufw_enabled = pre_state.find(" active") != -1
if (value == 'disabled' and ufw_enabled) or (value == 'enabled' and not ufw_enabled):
changed = True
else:
execute(cmd + [['-f'], [states[value]]])
elif command == 'logging':
extract = re.search(r'Logging: (on|off)(?: \(([a-z]+)\))?', pre_state)
if extract:
current_level = extract.group(2)
current_on_off_value = extract.group(1)
if value != "off":
if current_on_off_value == "off":
changed = True
elif value != "on" and value != current_level:
changed = True
elif current_on_off_value != "off":
changed = True
else:
changed = True
if not module.check_mode:
execute(cmd + [[command], [value]])
elif command == 'default':
if params['direction'] not in ['outgoing', 'incoming', 'routed', None]:
module.fail_json(msg='For default, direction must be one of "outgoing", "incoming" and "routed", or direction must not be specified.')
if module.check_mode:
regexp = r'Default: (deny|allow|reject) \(incoming\), (deny|allow|reject) \(outgoing\), (deny|allow|reject|disabled) \(routed\)'
extract = re.search(regexp, pre_state)
if extract is not None:
current_default_values = {}
current_default_values["incoming"] = extract.group(1)
current_default_values["outgoing"] = extract.group(2)
current_default_values["routed"] = extract.group(3)
v = current_default_values[params['direction'] or 'incoming']
if v not in (value, 'disabled'):
changed = True
else:
changed = True
else:
execute(cmd + [[command], [value], [params['direction']]])
elif command == 'rule':
if params['direction'] not in ['in', 'out', None]:
module.fail_json(msg='For rules, direction must be one of "in" and "out", or direction must not be specified.')
if not params['route'] and params['interface_in'] and params['interface_out']:
module.fail_json(msg='Only route rules can combine '
'interface_in and interface_out')
# Rules are constructed according to the long format
#
# ufw [--dry-run] [route] [delete] [insert NUM] allow|deny|reject|limit [in|out on INTERFACE] [log|log-all] \
# [from ADDRESS [port PORT]] [to ADDRESS [port PORT]] \
# [proto protocol] [app application] [comment COMMENT]
cmd.append([module.boolean(params['route']), 'route'])
cmd.append([module.boolean(params['delete']), 'delete'])
if params['insert'] is not None:
relative_to_cmd = params['insert_relative_to']
if relative_to_cmd == 'zero':
insert_to = params['insert']
else:
(dummy, numbered_state, dummy) = 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']])
cmd.append([params['interface_in'], "in on %s" % params['interface_in']])
cmd.append([params['interface_out'], "out on %s" % params['interface_out']])
cmd.append([module.boolean(params['log']), 'log'])
for (key, template) in [('from_ip', "from %s"), ('from_port', "port %s"),
('to_ip', "to %s"), ('to_port', "port %s"),
('proto', "proto %s"), ('name', "app '%s'")]:
value = params[key]
cmd.append([value, template % (value)])
ufw_major, ufw_minor, dummy = ufw_version()
# comment is supported only in ufw version after 0.35
if (ufw_major == 0 and ufw_minor >= 35) or ufw_major > 0:
cmd.append([params['comment'], "comment '%s'" % params['comment']])
rules_dry = execute(cmd)
if module.check_mode:
nb_skipping_line = len(filter_line_that_contains("Skipping", rules_dry))
if not (nb_skipping_line > 0 and nb_skipping_line == len(rules_dry.splitlines(True))):
rules_dry = filter_line_that_not_start_with("### tuple", rules_dry)
# ufw dry-run doesn't send all rules so have to compare ipv4 or ipv6 rules
if is_starting_by_ipv4(params['from_ip']) or is_starting_by_ipv4(params['to_ip']):
if filter_line_that_contains_ipv4(pre_rules) != filter_line_that_contains_ipv4(rules_dry):
changed = True
elif is_starting_by_ipv6(params['from_ip']) or is_starting_by_ipv6(params['to_ip']):
if filter_line_that_contains_ipv6(pre_rules) != filter_line_that_contains_ipv6(rules_dry):
changed = True
elif pre_rules != rules_dry:
changed = True
# Get the new state
if module.check_mode:
return module.exit_json(changed=changed, commands=cmds)
else:
post_state = execute([[ufw_bin], ['status'], ['verbose']])
if not changed:
post_rules = get_current_rules()
changed = (pre_state != post_state) or (pre_rules != post_rules)
return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip())
if __name__ == '__main__':
main()