2020# You should have received a copy of the GNU General Public License
2121# along with this program. If not, see <http://www.gnu.org/licenses/>.
2222
23- from __future__ import absolute_import , division , print_function
23+ from __future__ import absolute_import , division , print_function , unicode_literals
2424
2525__metaclass__ = type
2626
184184 ipset_entries:
185185 description:
186186 List of addresses to add/remove from ipset.
187+ Must be compatible with the ipset type of the `ipset`
188+ being created or modified.
187189 Will only do something when set with ipset.
188190 required: false
189191 type: list
190192 elements: str
191193 default: []
194+ ipset_options:
195+ description:
196+ Dict of key/value pairs of ipset options for the given ipset.
197+ Will only do something when set with ipset.
198+ required: false
199+ type: dict
200+ default: {}
192201 permanent:
193202 description:
194203 The permanent bool flag.
280289"""
281290
282291from ansible .module_utils .basic import AnsibleModule
292+ from ansible .module_utils .six import string_types
283293import re
284294import os
285295
296+ try :
297+ import ipaddress
298+
299+ HAS_IPADDRESS = True
300+ except ImportError :
301+ HAS_IPADDRESS = False
302+
303+ try :
304+ from firewall .functions import check_mac
305+
306+ HAS_CHECK_MAC = True
307+ except ImportError :
308+ HAS_CHECK_MAC = False
309+
310+ def check_mac (mac ):
311+ return False
312+
313+
286314try :
287315 import firewall .config
288316
315343 NM_IMPORTED = False
316344
317345
346+ # The argument to ip_interface must be a unicode string
347+ # Must be "cast" in python2, python3 does not need this
348+ def ip_interface (entry , module ):
349+ try :
350+ entry_str = unicode (entry )
351+ except NameError : # unicode is not defined in python3
352+ entry_str = entry
353+ try :
354+ return ipaddress .ip_interface (entry_str )
355+ except ValueError :
356+ module .fail_json (msg = "Invalid IP address - " + entry )
357+ return None
358+
359+
318360def try_get_connection_of_interface (interface ):
319361 try :
320362 return nm_get_connection_of_interface (interface )
@@ -343,6 +385,46 @@ def try_set_zone_of_interface(module, _zone, interface):
343385 return (False , False )
344386
345387
388+ # Check that all of the ipset entries are ipv4, ipv6, or mac addresses
389+ # if not, fail the module
390+ # if they are, return "ipv4", "ipv6", or "mac"
391+ def get_ipset_entries_type (ipset_entries , module ):
392+ addr_type = None
393+ is_mixed_addr_types = False
394+ for entry in ipset_entries :
395+ if check_mac (entry ):
396+ if addr_type is None :
397+ addr_type = "mac"
398+ elif addr_type != "mac" :
399+ is_mixed_addr_types = True
400+ else :
401+ if HAS_IPADDRESS :
402+ addr = ip_interface (entry , module )
403+ # ip_interface will fail the module if the address is invalid
404+ if addr is None :
405+ continue
406+ else :
407+ module .fail_json (msg = "No IP address library found" )
408+ continue
409+ if addr .version == 4 :
410+ if addr_type is None :
411+ addr_type = "ipv4"
412+ elif addr_type != "ipv4" :
413+ is_mixed_addr_types = True
414+ elif addr .version == 6 :
415+ if addr_type is None :
416+ addr_type = "ipv6"
417+ elif addr_type != "ipv6" :
418+ is_mixed_addr_types = True
419+ else :
420+ module .fail_json (msg = "Invalid IP address - " + entry )
421+ if is_mixed_addr_types :
422+ module .fail_json (
423+ msg = "Address types cannot be mixed in ipset entries - " + str (ipset_entries )
424+ )
425+ return addr_type
426+
427+
346428# Above: adapted from firewall-cmd source code
347429
348430
@@ -613,21 +695,43 @@ def set_service(
613695 else :
614696 self .module .fail_json (msg = "INVALID SERVICE - " + item )
615697
616- def _create_ipset (self , ipset , ipset_type ):
698+ def _create_ipset (self , ipset , ipset_type , ipset_options ):
617699 if not ipset_type :
618700 self .module .fail_json (msg = "ipset_type needed when creating a new ipset" )
619701
620702 fw_ipset = None
621703 fw_ipset_settings = FirewallClientIPSetSettings ()
622704 fw_ipset_settings .setType (ipset_type )
705+ fw_ipset_settings .setOptions (ipset_options )
623706 if not self .module .check_mode :
624707 self .fw .config ().addIPSet (ipset , fw_ipset_settings )
625708 fw_ipset = self .fw .config ().getIPSetByName (ipset )
626709 fw_ipset_settings = fw_ipset .getSettings ()
627710
628711 return fw_ipset , fw_ipset_settings
629712
630- def set_ipset (self , ipset , description , short , ipset_type , ipset_entries ):
713+ def set_ipset (
714+ self , ipset , description , short , ipset_type , ipset_entries , ipset_options
715+ ):
716+ addr_type = get_ipset_entries_type (ipset_entries , self .module )
717+ if addr_type is None and ipset_entries :
718+ self .module .fail_json (
719+ msg = "ipset %s: Invalid IP address - %s " % (ipset , str (ipset_entries ))
720+ )
721+ if not ipset_options :
722+ ipset_options = {}
723+ if addr_type == "ipv6" and "family" not in ipset_options :
724+ ipset_options ["family" ] = "inet6"
725+ if addr_type == "ipv4" and ipset_options .get ("family" ) == "inet6" :
726+ self .module .fail_json (
727+ msg = "ipset %s: family=inet6 is not supported for IPv4 ipset_entries %s"
728+ % (ipset , ", " .join (ipset_entries ))
729+ )
730+ if addr_type == "ipv6" and ipset_options .get ("family" ) == "inet" :
731+ self .module .fail_json (
732+ msg = "ipset %s: family=inet is not supported for IPv6 ipset_entries %s"
733+ % (ipset , ", " .join (ipset_entries ))
734+ )
631735 ipset_exists = ipset in self .fw .config ().getIPSetNames ()
632736 fw_ipset = None
633737 fw_ipset_settings = None
@@ -641,7 +745,9 @@ def set_ipset(self, ipset, description, short, ipset_type, ipset_entries):
641745 % (ipset , fw_ipset_settings .getType ())
642746 )
643747 elif self .state == "present" :
644- fw_ipset , fw_ipset_settings = self ._create_ipset (ipset , ipset_type )
748+ fw_ipset , fw_ipset_settings = self ._create_ipset (
749+ ipset , ipset_type , ipset_options
750+ )
645751 self .changed = True
646752 ipset_exists = True
647753 if self .state == "present" :
@@ -1255,7 +1361,32 @@ def set_service(
12551361 op = "--add-service=" if enable else "--remove-service-from-zone="
12561362 self .change ("--zone" , self .zone , op + item )
12571363
1258- def set_ipset (self , ipset , description , short , ipset_type , ipset_entries ):
1364+ def set_ipset (
1365+ self , ipset , description , short , ipset_type , ipset_entries , ipset_options
1366+ ):
1367+ addr_type = get_ipset_entries_type (ipset_entries , self .module )
1368+ if addr_type is None and ipset_entries :
1369+ self .module .fail_json (
1370+ msg = "ipset %s: Invalid IP address - %s" % (ipset , str (ipset_entries ))
1371+ )
1372+ ipset_options_list = []
1373+ if ipset_options :
1374+ for kk , vv in ipset_options .items ():
1375+ ipset_options_list .append ("--option" )
1376+ ipset_options_list .append (kk + "=" + str (vv ))
1377+ if addr_type == "ipv6" and "family=inet6" not in ipset_options_list :
1378+ ipset_options_list .append ("--option" )
1379+ ipset_options_list .append ("family=inet6" )
1380+ if addr_type == "ipv4" and "family=inet6" in ipset_options_list :
1381+ self .module .fail_json (
1382+ msg = "ipset %s: family=inet6 is not supported for IPv4 ipset_entries %s"
1383+ % (ipset , ", " .join (ipset_entries ))
1384+ )
1385+ if addr_type == "ipv6" and "family=inet" in ipset_options_list :
1386+ self .module .fail_json (
1387+ msg = "ipset %s: family=inet is not supported for IPv6 ipset_entries %s"
1388+ % (ipset , ", " .join (ipset_entries ))
1389+ )
12591390 present = self .check_state (["present" , "absent" ], "ipset" )
12601391 known_ipsets = self .cmd ("--get-ipsets" ).split ()
12611392 ipset_exists = ipset in known_ipsets
@@ -1278,9 +1409,12 @@ def set_ipset(self, ipset, description, short, ipset_type, ipset_entries):
12781409 if not ipset_exists :
12791410 if not ipset_type :
12801411 self .module .fail_json (
1281- msg = "ipset_type needed when creating a new ipset"
1412+ msg = "ipset %s: ipset_type needed when creating a new ipset"
1413+ % ipset
12821414 )
1283- self .change ("--new-ipset" , ipset , "--type=%s" % ipset_type )
1415+ self .change (
1416+ "--new-ipset" , ipset , "--type=%s" % ipset_type , * ipset_options_list
1417+ )
12841418
12851419 existing_description = self .cmd ("--ipset" , ipset , "--get-description" )
12861420 if description is not None and description != existing_description :
@@ -1635,6 +1769,10 @@ def get_forward_port(module):
16351769def parse_forward_port (module , item ):
16361770 type_string = "forward_port"
16371771
1772+ _port = None
1773+ _protocol = None
1774+ _to_port = None
1775+ _to_addr = None
16381776 if isinstance (item , dict ):
16391777 if "port" not in item :
16401778 module .fail_json (
@@ -1653,7 +1791,7 @@ def parse_forward_port(module, item):
16531791 else :
16541792 _to_port = None
16551793 _to_addr = item .get ("toaddr" )
1656- elif isinstance (item , str ):
1794+ elif isinstance (item , string_types ):
16571795 args = item .split (";" )
16581796 if len (args ) == 3 :
16591797 __port , _to_port , _to_addr = args
@@ -1737,6 +1875,7 @@ def main():
17371875 ipset = dict (required = False , type = "str" , default = None ),
17381876 ipset_type = dict (required = False , type = "str" , default = None ),
17391877 ipset_entries = dict (required = False , type = "list" , elements = "str" , default = []),
1878+ ipset_options = dict (required = False , type = "dict" , default = {}),
17401879 permanent = dict (required = False , type = "bool" , default = None ),
17411880 runtime = dict (
17421881 required = False ,
@@ -1840,6 +1979,7 @@ def main():
18401979 ipset = module .params ["ipset" ]
18411980 ipset_type = module .params ["ipset_type" ]
18421981 ipset_entries = module .params ["ipset_entries" ]
1982+ ipset_options = module .params ["ipset_options" ]
18431983 permanent = module .params ["permanent" ]
18441984 runtime = module .params ["runtime" ]
18451985 state = module .params ["state" ]
@@ -1942,11 +2082,12 @@ def main():
19422082 destination_ipv6 ,
19432083 ipset_entries ,
19442084 ipset_type ,
2085+ ipset_options ,
19452086 )
19462087 ):
19472088 module .fail_json (
19482089 msg = "short, description, port, source_port, helper_module, "
1949- "protocol, destination, ipset_type or ipset_entries cannot be set "
2090+ "protocol, destination, ipset_type, ipset_entries, or ipset_options cannot be set "
19502091 "while zone is specified "
19512092 "and state is set to present or absent"
19522093 )
@@ -1975,7 +2116,7 @@ def main():
19752116 msg = "permanent must be enabled for service configuration. "
19762117 "Additionally, service runtime configuration is not possible"
19772118 )
1978- elif ipset_entries or ipset_type :
2119+ elif ipset_entries or ipset_type or ipset_options :
19792120 module .fail_json (
19802121 msg = "ipset parameters cannot be set when configuring services"
19812122 )
@@ -2090,7 +2231,9 @@ def main():
20902231 includes ,
20912232 )
20922233 if ipset_operation :
2093- backend .set_ipset (ipset , description , short , ipset_type , ipset_entries )
2234+ backend .set_ipset (
2235+ ipset , description , short , ipset_type , ipset_entries , ipset_options
2236+ )
20942237 if port and not service_operation :
20952238 backend .set_port (port )
20962239 if source_port and not service_operation :
0 commit comments