From 0d332dd095534835a425d39fa360fdc0462607a9 Mon Sep 17 00:00:00 2001 From: Tim Rupp Date: Sun, 12 Aug 2018 20:46:08 -0700 Subject: [PATCH] Adds fixes and features to bigip_virtual_server (#44019) Merging in downstream work from the f5-ansible repository. --- .../network/f5/bigip_virtual_server.py | 255 ++++++++++++++---- 1 file changed, 202 insertions(+), 53 deletions(-) diff --git a/lib/ansible/modules/network/f5/bigip_virtual_server.py b/lib/ansible/modules/network/f5/bigip_virtual_server.py index ac8bc66318f..e2f72b094ff 100644 --- a/lib/ansible/modules/network/f5/bigip_virtual_server.py +++ b/lib/ansible/modules/network/f5/bigip_virtual_server.py @@ -150,11 +150,11 @@ options: - If you want to add a profile to the list of profiles currently active on the virtual, then simply add it to the C(profiles) list. See examples for an illustration of this. - - B(Profiles matter). There is a good chance that this module will fail to configure - a BIG-IP if you mix up your profiles, or, if you attempt to set an IP protocol - which your current, or new, profiles do not support. Both this module, and BIG-IP, - will tell you when you are wrong, with an error resembling C(lists profiles - incompatible with its protocol). + - B(Profiles matter). This module will fail to configure a BIG-IP if you mix up + your profiles, or, if you attempt to set an IP protocol which your current, + or new, profiles do not support. Both this module, and BIG-IP, will tell you + when you are wrong, with an error resembling C(lists profiles incompatible + with its protocol). - If you are unsure what correct profile combinations are, then have a BIG-IP available to you in which you can make changes and copy what the correct combinations are. @@ -331,12 +331,35 @@ options: - The C(Log all requests) and C(Log illegal requests) are mutually exclusive and therefore, this module will raise an error if the two are specified together. version_added: 2.6 -notes: - - Requires BIG-IP software version >= 11 - - Requires the netaddr Python package on the host. This is as easy as pip - install netaddr. -requirements: - - netaddr + security_nat_policy: + description: + - Specify the Firewall NAT policies for the virtual server. + - You can specify one or more NAT policies to use. + - The most specific policy is used. For example, if you specify that the + virtual server use the device policy and the route domain policy, the route + domain policy overrides the device policy. + version_added: 2.7 + suboptions: + policy: + description: + - Policy to apply a NAT policy directly to the virtual server. + - The virtual server NAT policy is the most specific, and overrides a + route domain and device policy, if specified. + - To remove the policy, specify an empty string value. + use_device_policy: + description: + - Specify that the virtual server uses the device NAT policy, as specified + in the Firewall Options. + - The device policy is used if no route domain or virtual server NAT + setting is specified. + type: bool + use_route_domain_policy: + description: + - Specify that the virtual server uses the route domain policy, as + specified in the Route Domain Security settings. + - When specified, the route domain policy overrides the device policy, and + is overridden by a virtual server policy. + type: bool extends_documentation_fragment: f5 author: - Tim Rupp (@caphrim007) @@ -621,6 +644,9 @@ try: from library.module_utils.network.f5.common import cleanup_tokens from library.module_utils.network.f5.common import fq_name from library.module_utils.network.f5.common import f5_argument_spec + from library.module_utils.network.f5.ipaddress import is_valid_ip + from library.module_utils.network.f5.ipaddress import ip_interface + from library.module_utils.network.f5.ipaddress import validate_ip_v6_address try: from library.module_utils.network.f5.common import iControlUnexpectedHTTPError except ImportError: @@ -633,17 +659,14 @@ except ImportError: from ansible.module_utils.network.f5.common import cleanup_tokens from ansible.module_utils.network.f5.common import fq_name from ansible.module_utils.network.f5.common import f5_argument_spec + from ansible.module_utils.network.f5.ipaddress import is_valid_ip + from ansible.module_utils.network.f5.ipaddress import ip_interface + from ansible.module_utils.network.f5.ipaddress import validate_ip_v6_address try: from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError except ImportError: HAS_F5SDK = False -try: - import netaddr - HAS_NETADDR = True -except ImportError: - HAS_NETADDR = False - class Parameters(AnsibleF5Parameters): api_map = { @@ -660,7 +683,8 @@ class Parameters(AnsibleF5Parameters): 'ipProtocol': 'ip_protocol', 'fwEnforcedPolicy': 'firewall_enforced_policy', 'fwStagedPolicy': 'firewall_staged_policy', - 'securityLogProfiles': 'security_log_profiles' + 'securityLogProfiles': 'security_log_profiles', + 'securityNatPolicy': 'security_nat_policy', } api_attributes = [ @@ -669,7 +693,7 @@ class Parameters(AnsibleF5Parameters): 'disabled', 'enabled', 'fallbackPersistence', - # 'ipProtocol', + 'ipProtocol', 'metadata', 'persist', 'policies', @@ -692,6 +716,7 @@ class Parameters(AnsibleF5Parameters): 'fwEnforcedPolicy', 'fwStagedPolicy', 'securityLogProfiles', + 'securityNatPolicy', ] updatables = [ @@ -703,7 +728,7 @@ class Parameters(AnsibleF5Parameters): 'enabled', 'enabled_vlans', 'fallback_persistence_profile', - # 'ip_protocol', + 'ip_protocol', 'irules', 'metadata', 'pool', @@ -717,6 +742,7 @@ class Parameters(AnsibleF5Parameters): 'firewall_enforced_policy', 'firewall_staged_policy', 'security_log_profiles', + 'security_nat_policy', ] returnables = [ @@ -729,7 +755,7 @@ class Parameters(AnsibleF5Parameters): 'enabled', 'enabled_vlans', 'fallback_persistence_profile', - # 'ip_protocol', + 'ip_protocol', 'irules', 'metadata', 'pool', @@ -746,11 +772,23 @@ class Parameters(AnsibleF5Parameters): 'firewall_enforced_policy', 'firewall_staged_policy', 'security_log_profiles', + 'security_nat_policy', ] profiles_mutex = [ - 'sip', 'sipsession', 'iiop', 'rtsp', 'http', 'diameter', - 'diametersession', 'radius', 'ftp', 'tftp', 'dns', 'pptp', 'fix' + 'sip', + 'sipsession', + 'iiop', + 'rtsp', + 'http', + 'diameter', + 'diametersession', + 'radius', + 'ftp', + 'tftp', + 'dns', + 'pptp', + 'fix', ] ip_protocols_map = [ @@ -784,16 +822,8 @@ class Parameters(AnsibleF5Parameters): result = self._filter_params(result) return result - def is_valid_ip(self, value): - try: - netaddr.IPAddress(value) - return True - except (netaddr.core.AddrFormatError, ValueError): - return False - def _format_port_for_destination(self, ip, port): - addr = netaddr.IPAddress(ip) - if addr.version == 6: + if validate_ip_v6_address(ip): if port == 0: result = '.any' else: @@ -959,10 +989,10 @@ class ApiParameters(Parameters): if self._values['source'] is None: return None try: - addr = netaddr.IPNetwork(self._values['source']) - result = '{0}/{1}'.format(str(addr.ip), addr.prefixlen) + addr = ip_interface(u'{0}'.format(self._values['source'])) + result = '{0}/{1}'.format(str(addr.ip), addr.network.prefixlen) return result - except netaddr.core.AddrFormatError: + except ValueError: raise F5ModuleError( "The source IP address must be specified in CIDR format: address/prefix" ) @@ -977,7 +1007,7 @@ class ApiParameters(Parameters): return result destination = re.sub(r'^/[a-zA-Z0-9_.-]+/', '', self._values['destination']) - if self.is_valid_ip(destination): + if is_valid_ip(destination): result = Destination( ip=destination, port=None, @@ -1004,7 +1034,7 @@ class ApiParameters(Parameters): if port == 'any': port = 0 ip = matches.group('ip') - if not self.is_valid_ip(ip): + if not is_valid_ip(ip): raise F5ModuleError( "The provided destination is not a valid IP address" ) @@ -1019,7 +1049,7 @@ class ApiParameters(Parameters): matches = re.search(pattern, destination) if matches: ip = matches.group('ip') - if not self.is_valid_ip(ip): + if not is_valid_ip(ip): raise F5ModuleError( "The provided destination is not a valid IP address" ) @@ -1034,7 +1064,7 @@ class ApiParameters(Parameters): if len(parts) == 4: # IPv4 ip, port = destination.split(':') - if not self.is_valid_ip(ip): + if not is_valid_ip(ip): raise F5ModuleError( "The provided destination is not a valid IP address" ) @@ -1053,7 +1083,7 @@ class ApiParameters(Parameters): # Can be a port of "any". This only happens with IPv6 if port == 'any': port = 0 - if not self.is_valid_ip(ip): + if not is_valid_ip(ip): raise F5ModuleError( "The provided destination is not a valid IP address" ) @@ -1152,8 +1182,7 @@ class ApiParameters(Parameters): def enabled(self): if 'enabled' in self._values: return True - else: - return False + return False @property def disabled(self): @@ -1189,6 +1218,34 @@ class ApiParameters(Parameters): result.sort() return result + @property + def sec_nat_use_device_policy(self): + if self._values['security_nat_policy'] is None: + return None + if 'useDevicePolicy' not in self._values['security_nat_policy']: + return None + if self._values['security_nat_policy']['useDevicePolicy'] == "no": + return False + return True + + @property + def sec_nat_use_rd_policy(self): + if self._values['security_nat_policy'] is None: + return None + if 'useRouteDomainPolicy' not in self._values['security_nat_policy']: + return None + if self._values['security_nat_policy']['useRouteDomainPolicy'] == "no": + return False + return True + + @property + def sec_nat_policy(self): + if self._values['security_nat_policy'] is None: + return None + if 'policy' not in self._values['security_nat_policy']: + return None + return self._values['security_nat_policy']['policy'] + class ModuleParameters(Parameters): services_map = { @@ -1241,7 +1298,7 @@ class ModuleParameters(Parameters): @property def destination(self): addr = self._values['destination'].split("%")[0] - if not self.is_valid_ip(addr): + if not is_valid_ip(addr): raise F5ModuleError( "The provided destination is not a valid IP address" ) @@ -1263,10 +1320,10 @@ class ModuleParameters(Parameters): if self._values['source'] is None: return None try: - addr = netaddr.IPNetwork(self._values['source']) - result = '{0}/{1}'.format(str(addr.ip), addr.prefixlen) + addr = ip_interface(u'{0}'.format(self._values['source'])) + result = '{0}/{1}'.format(str(addr.ip), addr.network.prefixlen) return result - except netaddr.core.AddrFormatError: + except ValueError: raise F5ModuleError( "The source IP address must be specified in CIDR format: address/prefix" ) @@ -1537,6 +1594,45 @@ class ModuleParameters(Parameters): result.sort() return result + @property + def sec_nat_use_device_policy(self): + if self._values['security_nat_policy'] is None: + return None + if 'use_device_policy' not in self._values['security_nat_policy']: + return None + return self._values['security_nat_policy']['use_device_policy'] + + @property + def sec_nat_use_rd_policy(self): + if self._values['security_nat_policy'] is None: + return None + if 'use_route_domain_policy' not in self._values['security_nat_policy']: + return None + return self._values['security_nat_policy']['use_route_domain_policy'] + + @property + def sec_nat_policy(self): + if self._values['security_nat_policy'] is None: + return None + if 'policy' not in self._values['security_nat_policy']: + return None + if self._values['security_nat_policy']['policy'] == '': + return '' + return fq_name(self.partition, self._values['security_nat_policy']['policy']) + + @property + def security_nat_policy(self): + result = dict() + if self.sec_nat_policy: + result['policy'] = self.sec_nat_policy + if self.sec_nat_use_device_policy is not None: + result['use_device_policy'] = self.sec_nat_use_device_policy + if self.sec_nat_use_rd_policy is not None: + result['use_route_domain_policy'] = self.sec_nat_use_rd_policy + if result: + return result + return None + class Changes(Parameters): pass @@ -1642,6 +1738,22 @@ class UsableChanges(Changes): ) return self._values['security_log_profiles'] + @property + def security_nat_policy(self): + if self._values['security_nat_policy'] is None: + return None + result = dict() + sec = self._values['security_nat_policy'] + if 'policy' in sec: + result['policy'] = sec['policy'] + if 'use_device_policy' in sec: + result['useDevicePolicy'] = 'yes' if sec['use_device_policy'] else 'no' + if 'use_route_domain_policy' in sec: + result['useRouteDomainPolicy'] = 'yes' if sec['use_route_domain_policy'] else 'no' + if result: + return result + return None + class ReportableChanges(Changes): @property @@ -1707,6 +1819,20 @@ class ReportableChanges(Changes): return True return False + @property + def ip_protocol(self): + if self._values['ip_protocol'] is None: + return None + try: + int(self._values['ip_protocol']) + except ValueError: + return self._values['ip_protocol'] + + protocol = next((x[0] for x in self.ip_protocols_map if x[1] == self._values['ip_protocol']), None) + if protocol: + return protocol + return self._values['ip_protocol'] + class VirtualServerValidator(object): def __init__(self, module=None, client=None, want=None, have=None): @@ -1860,8 +1986,8 @@ class VirtualServerValidator(object): F5ModuleError: Raised when the IP versions of source and destination differ. """ if self.want.source and self.want.destination: - want = netaddr.IPNetwork(self.want.source) - have = netaddr.IPNetwork(self.want.destination_tuple.ip) + want = ip_interface(u'{0}'.format(self.want.source)) + have = ip_interface(u'{0}'.format(self.want.destination_tuple.ip)) if want.version != have.version: raise F5ModuleError( "The source and destination addresses for the virtual server must be be the same type (IPv4 or IPv6)." @@ -2248,8 +2374,8 @@ class Difference(object): def source(self): if self.want.source is None: return None - want = netaddr.IPNetwork(self.want.source) - have = netaddr.IPNetwork(self.have.destination_tuple.ip) + want = ip_interface(u'{0}'.format(self.want.source)) + have = ip_interface(u'{0}'.format(self.have.destination_tuple.ip)) if want.version != have.version: raise F5ModuleError( "The source and destination addresses for the virtual server must be be the same type (IPv4 or IPv6)." @@ -2480,6 +2606,23 @@ class Difference(object): if set(self.want.security_log_profiles) != set(self.have.security_log_profiles): return self.want.security_log_profiles + @property + def security_nat_policy(self): + result = dict() + if self.want.sec_nat_use_device_policy is not None: + if self.want.sec_nat_use_device_policy != self.have.sec_nat_use_device_policy: + result['use_device_policy'] = self.want.sec_nat_use_device_policy + if self.want.sec_nat_use_rd_policy is not None: + if self.want.sec_nat_use_rd_policy != self.have.sec_nat_use_rd_policy: + result['use_route_domain_policy'] = self.want.sec_nat_use_rd_policy + if self.want.sec_nat_policy is not None: + if self.want.sec_nat_policy == '' and self.have.sec_nat_policy is None: + pass + elif self.want.sec_nat_policy != self.have.sec_nat_policy: + result['policy'] = self.want.sec_nat_policy + if result: + return dict(security_nat_policy=result) + class ModuleManager(object): def __init__(self, *args, **kwargs): @@ -2699,7 +2842,15 @@ class ArgumentSpec(object): ), firewall_staged_policy=dict(), firewall_enforced_policy=dict(), - security_log_profiles=dict(type='list') + security_log_profiles=dict(type='list'), + security_nat_policy=dict( + type='dict', + options=dict( + policy=dict(), + use_device_policy=dict(type='bool'), + use_route_domain_policy=dict(type='bool') + ) + ) ) self.argument_spec = {} self.argument_spec.update(f5_argument_spec) @@ -2719,8 +2870,6 @@ def main(): ) if not HAS_F5SDK: module.fail_json(msg="The python f5-sdk module is required") - if not HAS_NETADDR: - module.fail_json(msg="The python netaddr module is required") try: client = F5Client(**module.params)