2014-09-26 01:01:01 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
# 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>
|
Remove wildcard imports
Made the following changes:
* Removed wildcard imports
* Replaced long form of GPL header with short form
* Removed get_exception usage
* Added from __future__ boilerplate
* Adjust division operator to // where necessary
For the following files:
* web_infrastructure modules
* system modules
* linode, lxc, lxd, atomic, cloudscale, dimensiondata, ovh, packet,
profitbricks, pubnub, smartos, softlayer, univention modules
* compat dirs (disabled as its used intentionally)
2017-07-28 05:55:24 +00:00
|
|
|
# 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
|
|
|
|
|
2017-08-16 03:16:38 +00:00
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
2017-03-14 16:07:22 +00:00
|
|
|
'status': ['preview'],
|
|
|
|
'supported_by': 'community'}
|
|
|
|
|
2019-02-19 14:42:14 +00:00
|
|
|
DOCUMENTATION = r'''
|
2014-09-26 01:01:01 +00:00
|
|
|
---
|
|
|
|
module: ufw
|
|
|
|
short_description: Manage firewall with UFW
|
|
|
|
description:
|
|
|
|
- Manage firewall with UFW.
|
|
|
|
version_added: 1.6
|
2015-05-15 19:12:26 +00:00
|
|
|
author:
|
2017-11-07 08:38:59 +00:00
|
|
|
- Aleksey Ovcharenko (@ovcharenko)
|
|
|
|
- Jarno Keskikangas (@pyykkis)
|
|
|
|
- Ahti Kitsik (@ahtik)
|
2014-09-26 01:01:01 +00:00
|
|
|
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.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-11-07 08:38:59 +00:00
|
|
|
choices: [ disabled, enabled, reloaded, reset ]
|
2019-02-19 14:42:14 +00:00
|
|
|
default:
|
2014-09-26 01:01:01 +00:00
|
|
|
description:
|
|
|
|
- Change the default policy for incoming or outgoing traffic.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-11-07 08:38:59 +00:00
|
|
|
choices: [ allow, deny, reject ]
|
2019-02-19 14:42:14 +00:00
|
|
|
aliases: [ policy ]
|
2014-09-26 01:01:01 +00:00
|
|
|
direction:
|
|
|
|
description:
|
|
|
|
- Select direction for a rule or default policy command.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-11-07 08:38:59 +00:00
|
|
|
choices: [ in, incoming, out, outgoing, routed ]
|
2014-09-26 01:01:01 +00:00
|
|
|
logging:
|
|
|
|
description:
|
|
|
|
- Toggles logging. Logged packets use the LOG_KERN syslog facility.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
|
|
|
choices: [ 'on', 'off', low, medium, high, full ]
|
2014-09-26 01:01:01 +00:00
|
|
|
insert:
|
|
|
|
description:
|
2019-02-12 08:05:14 +00:00
|
|
|
- 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.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
|
|
|
choices: [ first-ipv4, first-ipv6, last-ipv4, last-ipv6, zero ]
|
2019-02-12 08:05:14 +00:00
|
|
|
default: zero
|
|
|
|
version_added: "2.8"
|
2014-09-26 01:01:01 +00:00
|
|
|
rule:
|
|
|
|
description:
|
|
|
|
- Add firewall rule
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
|
|
|
choices: [ allow, deny, limit, reject ]
|
2014-09-26 01:01:01 +00:00
|
|
|
log:
|
|
|
|
description:
|
|
|
|
- Log new connections matched to this rule
|
2017-11-07 08:38:59 +00:00
|
|
|
type: bool
|
2014-09-26 01:01:01 +00:00
|
|
|
from_ip:
|
|
|
|
description:
|
|
|
|
- Source IP address.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-11-07 08:38:59 +00:00
|
|
|
default: any
|
2019-02-19 14:42:14 +00:00
|
|
|
aliases: [ from, src ]
|
2014-09-26 01:01:01 +00:00
|
|
|
from_port:
|
|
|
|
description:
|
|
|
|
- Source port.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2014-09-26 01:01:01 +00:00
|
|
|
to_ip:
|
|
|
|
description:
|
|
|
|
- Destination IP address.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-11-07 08:38:59 +00:00
|
|
|
default: any
|
2019-02-19 14:42:14 +00:00
|
|
|
aliases: [ dest, to]
|
2014-09-26 01:01:01 +00:00
|
|
|
to_port:
|
|
|
|
description:
|
|
|
|
- Destination port.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-11-07 08:38:59 +00:00
|
|
|
aliases: [ port ]
|
2014-09-26 01:01:01 +00:00
|
|
|
proto:
|
|
|
|
description:
|
|
|
|
- TCP/IP protocol.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2019-02-11 14:46:38 +00:00
|
|
|
choices: [ any, tcp, udp, ipv6, esp, ah, gre, igmp ]
|
2019-02-19 14:42:14 +00:00
|
|
|
aliases: [ protocol ]
|
2014-09-26 01:01:01 +00:00
|
|
|
name:
|
|
|
|
description:
|
2017-11-07 08:38:59 +00:00
|
|
|
- Use profile located in C(/etc/ufw/applications.d).
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-11-07 08:38:59 +00:00
|
|
|
aliases: [ app ]
|
2014-09-26 01:01:01 +00:00
|
|
|
delete:
|
|
|
|
description:
|
|
|
|
- Delete rule.
|
2017-11-07 08:38:59 +00:00
|
|
|
type: bool
|
2014-09-26 01:01:01 +00:00
|
|
|
interface:
|
|
|
|
description:
|
|
|
|
- Specify interface for rule.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-11-07 08:38:59 +00:00
|
|
|
aliases: [ if ]
|
2014-11-14 16:09:24 +00:00
|
|
|
route:
|
|
|
|
description:
|
|
|
|
- Apply the rule to routed/forwarded packets.
|
2017-11-07 08:38:59 +00:00
|
|
|
type: bool
|
2017-06-16 12:08:09 +00:00
|
|
|
comment:
|
|
|
|
description:
|
|
|
|
- Add a comment to the rule. Requires UFW version >=0.35.
|
2019-02-19 14:42:14 +00:00
|
|
|
type: str
|
2017-06-16 12:08:09 +00:00
|
|
|
version_added: "2.4"
|
2014-09-26 01:01:01 +00:00
|
|
|
'''
|
|
|
|
|
2019-02-19 14:42:14 +00:00
|
|
|
EXAMPLES = r'''
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Allow everything and enable UFW
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
state: enabled
|
|
|
|
policy: allow
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Set logging
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
logging: on
|
2014-09-26 01:01:01 +00:00
|
|
|
|
|
|
|
# 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:
|
2016-12-02 15:48:22 +00:00
|
|
|
- ufw:
|
|
|
|
rule: reject
|
|
|
|
port: auth
|
|
|
|
log: yes
|
2014-09-26 01:01:01 +00:00
|
|
|
|
|
|
|
# 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:
|
2016-12-02 15:48:22 +00:00
|
|
|
- ufw:
|
|
|
|
rule: limit
|
|
|
|
port: ssh
|
|
|
|
proto: tcp
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2016-04-23 06:01:19 +00:00
|
|
|
# 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)
|
2016-12-02 15:48:22 +00:00
|
|
|
- ufw:
|
|
|
|
rule: allow
|
|
|
|
name: OpenSSH
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Delete OpenSSH rule
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: allow
|
|
|
|
name: OpenSSH
|
|
|
|
delete: yes
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Deny all access to port 53
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: deny
|
|
|
|
port: 53
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Allow port range 60000-61000
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: allow
|
2017-11-07 08:38:59 +00:00
|
|
|
port: 60000:61000
|
2019-03-26 15:39:43 +00:00
|
|
|
proto: tcp
|
2016-08-12 06:01:01 +00:00
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Allow all access to tcp port 80
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: allow
|
|
|
|
port: 80
|
|
|
|
proto: tcp
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Allow all access from RFC1918 networks to this host
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: allow
|
|
|
|
src: '{{ item }}'
|
2018-10-18 22:55:19 +00:00
|
|
|
loop:
|
2016-12-02 15:48:22 +00:00
|
|
|
- 10.0.0.0/8
|
|
|
|
- 172.16.0.0/12
|
|
|
|
- 192.168.0.0/16
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Deny access to udp port 514 from host 1.2.3.4 and include a comment
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: deny
|
|
|
|
proto: udp
|
|
|
|
src: 1.2.3.4
|
|
|
|
port: 514
|
2017-11-07 08:38:59 +00:00
|
|
|
comment: Block syslog
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Allow incoming access to eth0 from 1.2.3.5 port 5469 to 1.2.3.4 port 5469
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: allow
|
|
|
|
interface: eth0
|
|
|
|
direction: in
|
|
|
|
proto: udp
|
|
|
|
src: 1.2.3.5
|
|
|
|
from_port: 5469
|
|
|
|
dest: 1.2.3.4
|
|
|
|
to_port: 5469
|
2014-09-26 01:01:01 +00:00
|
|
|
|
|
|
|
# Note that IPv6 must be enabled in /etc/default/ufw for IPv6 firewalling to work.
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Deny all traffic from the IPv6 2001:db8::/32 to tcp port 25 on this host
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: deny
|
|
|
|
proto: tcp
|
2017-11-07 08:38:59 +00:00
|
|
|
src: 2001:db8::/32
|
2016-12-02 15:48:22 +00:00
|
|
|
port: 25
|
2014-11-14 16:09:24 +00:00
|
|
|
|
2019-02-12 08:05:14 +00:00
|
|
|
- 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
|
|
|
|
|
2014-11-14 16:09:24 +00:00
|
|
|
# Can be used to further restrict a global FORWARD policy set to allow
|
2017-11-07 08:38:59 +00:00
|
|
|
- name: Deny forwarded/routed traffic from subnet 1.2.3.0/24 to subnet 4.5.6.0/24
|
|
|
|
ufw:
|
2016-12-02 15:48:22 +00:00
|
|
|
rule: deny
|
|
|
|
route: yes
|
|
|
|
src: 1.2.3.0/24
|
|
|
|
dest: 4.5.6.0/24
|
2014-09-26 01:01:01 +00:00
|
|
|
'''
|
|
|
|
|
Remove wildcard imports
Made the following changes:
* Removed wildcard imports
* Replaced long form of GPL header with short form
* Removed get_exception usage
* Added from __future__ boilerplate
* Adjust division operator to // where necessary
For the following files:
* web_infrastructure modules
* system modules
* linode, lxc, lxd, atomic, cloudscale, dimensiondata, ovh, packet,
profitbricks, pubnub, smartos, softlayer, univention modules
* compat dirs (disabled as its used intentionally)
2017-07-28 05:55:24 +00:00
|
|
|
import re
|
2019-02-11 11:05:35 +00:00
|
|
|
|
2014-09-26 01:01:01 +00:00
|
|
|
from operator import itemgetter
|
|
|
|
|
Remove wildcard imports
Made the following changes:
* Removed wildcard imports
* Replaced long form of GPL header with short form
* Removed get_exception usage
* Added from __future__ boilerplate
* Adjust division operator to // where necessary
For the following files:
* web_infrastructure modules
* system modules
* linode, lxc, lxd, atomic, cloudscale, dimensiondata, ovh, packet,
profitbricks, pubnub, smartos, softlayer, univention modules
* compat dirs (disabled as its used intentionally)
2017-07-28 05:55:24 +00:00
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2019-02-11 11:05:35 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2014-09-26 01:01:01 +00:00
|
|
|
def main():
|
Introduce new 'required_by' argument_spec option (#28662)
* Introduce new "required_by' argument_spec option
This PR introduces a new **required_by** argument_spec option which allows you to say *"if parameter A is set, parameter B and C are required as well"*.
- The difference with **required_if** is that it can only add dependencies if a parameter is set to a specific value, not when it is just defined.
- The difference with **required_together** is that it has a commutative property, so: *"Parameter A and B are required together, if one of them has been defined"*.
As an example, we need this for the complex options that the xml module provides. One of the issues we often see is that users are not using the correct combination of options, and then are surprised that the module does not perform the requested action(s).
This would be solved by adding the correct dependencies, and mutual exclusives. For us this is important to get this shipped together with the new xml module in Ansible v2.4. (This is related to bugfix https://github.com/ansible/ansible/pull/28657)
```python
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', aliases=['dest', 'file']),
xmlstring=dict(type='str'),
xpath=dict(type='str'),
namespaces=dict(type='dict', default={}),
state=dict(type='str', default='present', choices=['absent',
'present'], aliases=['ensure']),
value=dict(type='raw'),
attribute=dict(type='raw'),
add_children=dict(type='list'),
set_children=dict(type='list'),
count=dict(type='bool', default=False),
print_match=dict(type='bool', default=False),
pretty_print=dict(type='bool', default=False),
content=dict(type='str', choices=['attribute', 'text']),
input_type=dict(type='str', default='yaml', choices=['xml',
'yaml']),
backup=dict(type='bool', default=False),
),
supports_check_mode=True,
required_by=dict(
add_children=['xpath'],
attribute=['value', 'xpath'],
content=['xpath'],
set_children=['xpath'],
value=['xpath'],
),
required_if=[
['count', True, ['xpath']],
['print_match', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
['add_children', 'content', 'count', 'pretty_print', 'print_match', 'set_children', 'value'],
],
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match','set_children', 'value'],
['path', 'xmlstring'],
],
)
```
* Rebase and fix conflict
* Add modules that use required_by functionality
* Update required_by schema
* Fix rebase issue
2019-02-15 00:57:45 +00:00
|
|
|
command_keys = ['state', 'default', 'rule', 'logging']
|
|
|
|
|
2014-09-26 01:01:01 +00:00
|
|
|
module = AnsibleModule(
|
2017-11-07 08:38:59 +00:00
|
|
|
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),
|
2019-02-12 08:05:14 +00:00
|
|
|
insert=dict(type='int'),
|
|
|
|
insert_relative_to=dict(choices=['zero', 'first-ipv4', 'last-ipv4', 'first-ipv6', 'last-ipv6'], default='zero'),
|
2017-11-07 08:38:59 +00:00
|
|
|
rule=dict(type='str', choices=['allow', 'deny', 'limit', 'reject']),
|
|
|
|
interface=dict(type='str', aliases=['if']),
|
|
|
|
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']),
|
2019-02-11 14:46:38 +00:00
|
|
|
proto=dict(type='str', aliases=['protocol'], choices=['ah', 'any', 'esp', 'ipv6', 'tcp', 'udp', 'gre', 'igmp']),
|
2017-11-07 08:38:59 +00:00
|
|
|
app=dict(type='str', aliases=['name']),
|
|
|
|
comment=dict(type='str'),
|
2014-09-26 01:01:01 +00:00
|
|
|
),
|
2017-11-07 08:38:59 +00:00
|
|
|
supports_check_mode=True,
|
|
|
|
mutually_exclusive=[
|
Introduce new 'required_by' argument_spec option (#28662)
* Introduce new "required_by' argument_spec option
This PR introduces a new **required_by** argument_spec option which allows you to say *"if parameter A is set, parameter B and C are required as well"*.
- The difference with **required_if** is that it can only add dependencies if a parameter is set to a specific value, not when it is just defined.
- The difference with **required_together** is that it has a commutative property, so: *"Parameter A and B are required together, if one of them has been defined"*.
As an example, we need this for the complex options that the xml module provides. One of the issues we often see is that users are not using the correct combination of options, and then are surprised that the module does not perform the requested action(s).
This would be solved by adding the correct dependencies, and mutual exclusives. For us this is important to get this shipped together with the new xml module in Ansible v2.4. (This is related to bugfix https://github.com/ansible/ansible/pull/28657)
```python
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', aliases=['dest', 'file']),
xmlstring=dict(type='str'),
xpath=dict(type='str'),
namespaces=dict(type='dict', default={}),
state=dict(type='str', default='present', choices=['absent',
'present'], aliases=['ensure']),
value=dict(type='raw'),
attribute=dict(type='raw'),
add_children=dict(type='list'),
set_children=dict(type='list'),
count=dict(type='bool', default=False),
print_match=dict(type='bool', default=False),
pretty_print=dict(type='bool', default=False),
content=dict(type='str', choices=['attribute', 'text']),
input_type=dict(type='str', default='yaml', choices=['xml',
'yaml']),
backup=dict(type='bool', default=False),
),
supports_check_mode=True,
required_by=dict(
add_children=['xpath'],
attribute=['value', 'xpath'],
content=['xpath'],
set_children=['xpath'],
value=['xpath'],
),
required_if=[
['count', True, ['xpath']],
['print_match', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
['add_children', 'content', 'count', 'pretty_print', 'print_match', 'set_children', 'value'],
],
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match','set_children', 'value'],
['path', 'xmlstring'],
],
)
```
* Rebase and fix conflict
* Add modules that use required_by functionality
* Update required_by schema
* Fix rebase issue
2019-02-15 00:57:45 +00:00
|
|
|
['app', 'proto', 'logging'],
|
2017-11-07 08:38:59 +00:00
|
|
|
],
|
Introduce new 'required_by' argument_spec option (#28662)
* Introduce new "required_by' argument_spec option
This PR introduces a new **required_by** argument_spec option which allows you to say *"if parameter A is set, parameter B and C are required as well"*.
- The difference with **required_if** is that it can only add dependencies if a parameter is set to a specific value, not when it is just defined.
- The difference with **required_together** is that it has a commutative property, so: *"Parameter A and B are required together, if one of them has been defined"*.
As an example, we need this for the complex options that the xml module provides. One of the issues we often see is that users are not using the correct combination of options, and then are surprised that the module does not perform the requested action(s).
This would be solved by adding the correct dependencies, and mutual exclusives. For us this is important to get this shipped together with the new xml module in Ansible v2.4. (This is related to bugfix https://github.com/ansible/ansible/pull/28657)
```python
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', aliases=['dest', 'file']),
xmlstring=dict(type='str'),
xpath=dict(type='str'),
namespaces=dict(type='dict', default={}),
state=dict(type='str', default='present', choices=['absent',
'present'], aliases=['ensure']),
value=dict(type='raw'),
attribute=dict(type='raw'),
add_children=dict(type='list'),
set_children=dict(type='list'),
count=dict(type='bool', default=False),
print_match=dict(type='bool', default=False),
pretty_print=dict(type='bool', default=False),
content=dict(type='str', choices=['attribute', 'text']),
input_type=dict(type='str', default='yaml', choices=['xml',
'yaml']),
backup=dict(type='bool', default=False),
),
supports_check_mode=True,
required_by=dict(
add_children=['xpath'],
attribute=['value', 'xpath'],
content=['xpath'],
set_children=['xpath'],
value=['xpath'],
),
required_if=[
['count', True, ['xpath']],
['print_match', True, ['xpath']],
],
required_one_of=[
['path', 'xmlstring'],
['add_children', 'content', 'count', 'pretty_print', 'print_match', 'set_children', 'value'],
],
mutually_exclusive=[
['add_children', 'content', 'count', 'print_match','set_children', 'value'],
['path', 'xmlstring'],
],
)
```
* Rebase and fix conflict
* Add modules that use required_by functionality
* Update required_by schema
* Fix rebase issue
2019-02-15 00:57:45 +00:00
|
|
|
required_one_of=([command_keys]),
|
|
|
|
required_by=dict(
|
|
|
|
interface=('direction', ),
|
|
|
|
),
|
2014-09-26 01:01:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
cmds = []
|
|
|
|
|
2019-02-11 11:05:35 +00:00
|
|
|
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):
|
2014-09-26 01:01:01 +00:00
|
|
|
cmd = ' '.join(map(itemgetter(-1), filter(itemgetter(0), cmd)))
|
|
|
|
|
|
|
|
cmds.append(cmd)
|
2019-02-11 11:05:35 +00:00
|
|
|
(rc, out, err) = module.run_command(cmd, environ_update={"LANG": "C"})
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2019-02-11 11:05:35 +00:00
|
|
|
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)
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2017-06-16 12:08:09 +00:00
|
|
|
def ufw_version():
|
|
|
|
"""
|
|
|
|
Returns the major and minor version of ufw installed on the system.
|
|
|
|
"""
|
2019-02-11 11:05:35 +00:00
|
|
|
out = execute([[ufw_bin], ["--version"]])
|
2017-06-16 12:08:09 +00:00
|
|
|
|
|
|
|
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))
|
2017-11-07 08:38:59 +00:00
|
|
|
rev = 0
|
2017-06-16 12:08:09 +00:00
|
|
|
if matches.group(3) is not None:
|
|
|
|
rev = int(matches.group(3))
|
|
|
|
|
|
|
|
return major, minor, rev
|
|
|
|
|
2014-09-26 01:01:01 +00:00
|
|
|
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)
|
2019-02-11 11:05:35 +00:00
|
|
|
grep_bin = module.get_bin_path('grep', True)
|
2014-09-26 01:01:01 +00:00
|
|
|
|
|
|
|
# Save the pre state and rules in order to recognize changes
|
2019-02-11 11:05:35 +00:00
|
|
|
pre_state = execute([[ufw_bin], ['status verbose']])
|
|
|
|
pre_rules = get_current_rules()
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2019-02-11 11:05:35 +00:00
|
|
|
changed = False
|
|
|
|
|
|
|
|
# Execute filter
|
2016-12-12 23:16:23 +00:00
|
|
|
for (command, value) in commands.items():
|
2019-02-11 11:05:35 +00:00
|
|
|
|
2014-09-26 01:01:01 +00:00
|
|
|
cmd = [[ufw_bin], [module.check_mode, '--dry-run']]
|
|
|
|
|
|
|
|
if command == 'state':
|
2017-11-07 08:38:59 +00:00
|
|
|
states = {'enabled': 'enable', 'disabled': 'disable',
|
|
|
|
'reloaded': 'reload', 'reset': 'reset'}
|
2019-02-11 11:05:35 +00:00
|
|
|
|
|
|
|
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]]])
|
2014-09-26 01:01:01 +00:00
|
|
|
|
|
|
|
elif command == 'logging':
|
2019-02-11 11:05:35 +00:00
|
|
|
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 value != "on" and (value != current_level or current_on_off_value == "off"):
|
|
|
|
changed = True
|
|
|
|
elif current_on_off_value != "off":
|
|
|
|
changed = True
|
|
|
|
else:
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
if not module.check_mode:
|
|
|
|
execute(cmd + [[command], [value]])
|
2014-09-26 01:01:01 +00:00
|
|
|
|
|
|
|
elif command == 'default':
|
2019-02-11 14:47:35 +00:00
|
|
|
if params['direction'] not in ['outgoing', 'incoming', 'routed']:
|
|
|
|
module.fail_json(msg='For default, direction must be one of "outgoing", "incoming" and "routed".')
|
2019-02-11 11:05:35 +00:00
|
|
|
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)
|
|
|
|
if current_default_values[params['direction']] != value:
|
|
|
|
changed = True
|
|
|
|
else:
|
|
|
|
changed = True
|
|
|
|
else:
|
|
|
|
execute(cmd + [[command], [value], [params['direction']]])
|
2014-09-26 01:01:01 +00:00
|
|
|
|
|
|
|
elif command == 'rule':
|
2019-02-11 14:47:35 +00:00
|
|
|
if params['direction'] not in ['in', 'out', None]:
|
|
|
|
module.fail_json(msg='For rules, direction must be one of "in" and "out".')
|
2014-09-26 01:01:01 +00:00
|
|
|
# Rules are constructed according to the long format
|
|
|
|
#
|
2018-05-11 15:21:27 +00:00
|
|
|
# ufw [--dry-run] [route] [delete] [insert NUM] allow|deny|reject|limit [in|out on INTERFACE] [log|log-all] \
|
2014-09-26 01:01:01 +00:00
|
|
|
# [from ADDRESS [port PORT]] [to ADDRESS [port PORT]] \
|
2017-06-16 12:08:09 +00:00
|
|
|
# [proto protocol] [app application] [comment COMMENT]
|
2014-11-14 16:09:24 +00:00
|
|
|
cmd.append([module.boolean(params['route']), 'route'])
|
2018-05-11 15:21:27 +00:00
|
|
|
cmd.append([module.boolean(params['delete']), 'delete'])
|
2019-02-12 08:05:14 +00:00
|
|
|
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])
|
2014-09-26 01:01:01 +00:00
|
|
|
cmd.append([value])
|
2016-08-16 13:09:17 +00:00
|
|
|
cmd.append([params['direction'], "%s" % params['direction']])
|
|
|
|
cmd.append([params['interface'], "on %s" % params['interface']])
|
2014-09-26 01:01:01 +00:00
|
|
|
cmd.append([module.boolean(params['log']), 'log'])
|
|
|
|
|
2017-11-07 08:38:59 +00:00
|
|
|
for (key, template) in [('from_ip', "from %s"), ('from_port', "port %s"),
|
|
|
|
('to_ip', "to %s"), ('to_port', "port %s"),
|
|
|
|
('proto', "proto %s"), ('app', "app '%s'")]:
|
2014-09-26 01:01:01 +00:00
|
|
|
value = params[key]
|
|
|
|
cmd.append([value, template % (value)])
|
|
|
|
|
2017-06-16 12:08:09 +00:00
|
|
|
ufw_major, ufw_minor, _ = 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']])
|
|
|
|
|
2019-02-11 11:05:35 +00:00
|
|
|
rules_dry = execute(cmd)
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2019-02-11 11:05:35 +00:00
|
|
|
if module.check_mode:
|
|
|
|
|
|
|
|
nb_skipping_line = len(filter_line_that_contains("Skipping", rules_dry))
|
2014-09-26 01:01:01 +00:00
|
|
|
|
2019-02-11 11:05:35 +00:00
|
|
|
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())
|
2014-09-26 01:01:01 +00:00
|
|
|
|
|
|
|
|
2016-12-05 16:24:19 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|