Adds fixes and features to bigip_virtual_server (#44019)

Merging in downstream work from the f5-ansible repository.
This commit is contained in:
Tim Rupp 2018-08-12 20:46:08 -07:00 committed by GitHub
parent 6ca4ea0c1f
commit 0d332dd095
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -150,11 +150,11 @@ options:
- If you want to add a profile to the list of profiles currently active - 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 on the virtual, then simply add it to the C(profiles) list. See
examples for an illustration of this. examples for an illustration of this.
- B(Profiles matter). There is a good chance that this module will fail to configure - B(Profiles matter). This module will fail to configure a BIG-IP if you mix up
a BIG-IP if you mix up your profiles, or, if you attempt to set an IP protocol your profiles, or, if you attempt to set an IP protocol which your current,
which your current, or new, profiles do not support. Both this module, and BIG-IP, or new, profiles do not support. Both this module, and BIG-IP, will tell you
will tell you when you are wrong, with an error resembling C(lists profiles when you are wrong, with an error resembling C(lists profiles incompatible
incompatible with its protocol). with its protocol).
- If you are unsure what correct profile combinations are, then have a BIG-IP - 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 available to you in which you can make changes and copy what the correct
combinations are. combinations are.
@ -331,12 +331,35 @@ options:
- The C(Log all requests) and C(Log illegal requests) are mutually exclusive and - 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. therefore, this module will raise an error if the two are specified together.
version_added: 2.6 version_added: 2.6
notes: security_nat_policy:
- Requires BIG-IP software version >= 11 description:
- Requires the netaddr Python package on the host. This is as easy as pip - Specify the Firewall NAT policies for the virtual server.
install netaddr. - You can specify one or more NAT policies to use.
requirements: - The most specific policy is used. For example, if you specify that the
- netaddr 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 extends_documentation_fragment: f5
author: author:
- Tim Rupp (@caphrim007) - 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 cleanup_tokens
from library.module_utils.network.f5.common import fq_name 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.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: try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError: 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 cleanup_tokens
from ansible.module_utils.network.f5.common import fq_name 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.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: try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
try:
import netaddr
HAS_NETADDR = True
except ImportError:
HAS_NETADDR = False
class Parameters(AnsibleF5Parameters): class Parameters(AnsibleF5Parameters):
api_map = { api_map = {
@ -660,7 +683,8 @@ class Parameters(AnsibleF5Parameters):
'ipProtocol': 'ip_protocol', 'ipProtocol': 'ip_protocol',
'fwEnforcedPolicy': 'firewall_enforced_policy', 'fwEnforcedPolicy': 'firewall_enforced_policy',
'fwStagedPolicy': 'firewall_staged_policy', 'fwStagedPolicy': 'firewall_staged_policy',
'securityLogProfiles': 'security_log_profiles' 'securityLogProfiles': 'security_log_profiles',
'securityNatPolicy': 'security_nat_policy',
} }
api_attributes = [ api_attributes = [
@ -669,7 +693,7 @@ class Parameters(AnsibleF5Parameters):
'disabled', 'disabled',
'enabled', 'enabled',
'fallbackPersistence', 'fallbackPersistence',
# 'ipProtocol', 'ipProtocol',
'metadata', 'metadata',
'persist', 'persist',
'policies', 'policies',
@ -692,6 +716,7 @@ class Parameters(AnsibleF5Parameters):
'fwEnforcedPolicy', 'fwEnforcedPolicy',
'fwStagedPolicy', 'fwStagedPolicy',
'securityLogProfiles', 'securityLogProfiles',
'securityNatPolicy',
] ]
updatables = [ updatables = [
@ -703,7 +728,7 @@ class Parameters(AnsibleF5Parameters):
'enabled', 'enabled',
'enabled_vlans', 'enabled_vlans',
'fallback_persistence_profile', 'fallback_persistence_profile',
# 'ip_protocol', 'ip_protocol',
'irules', 'irules',
'metadata', 'metadata',
'pool', 'pool',
@ -717,6 +742,7 @@ class Parameters(AnsibleF5Parameters):
'firewall_enforced_policy', 'firewall_enforced_policy',
'firewall_staged_policy', 'firewall_staged_policy',
'security_log_profiles', 'security_log_profiles',
'security_nat_policy',
] ]
returnables = [ returnables = [
@ -729,7 +755,7 @@ class Parameters(AnsibleF5Parameters):
'enabled', 'enabled',
'enabled_vlans', 'enabled_vlans',
'fallback_persistence_profile', 'fallback_persistence_profile',
# 'ip_protocol', 'ip_protocol',
'irules', 'irules',
'metadata', 'metadata',
'pool', 'pool',
@ -746,11 +772,23 @@ class Parameters(AnsibleF5Parameters):
'firewall_enforced_policy', 'firewall_enforced_policy',
'firewall_staged_policy', 'firewall_staged_policy',
'security_log_profiles', 'security_log_profiles',
'security_nat_policy',
] ]
profiles_mutex = [ profiles_mutex = [
'sip', 'sipsession', 'iiop', 'rtsp', 'http', 'diameter', 'sip',
'diametersession', 'radius', 'ftp', 'tftp', 'dns', 'pptp', 'fix' 'sipsession',
'iiop',
'rtsp',
'http',
'diameter',
'diametersession',
'radius',
'ftp',
'tftp',
'dns',
'pptp',
'fix',
] ]
ip_protocols_map = [ ip_protocols_map = [
@ -784,16 +822,8 @@ class Parameters(AnsibleF5Parameters):
result = self._filter_params(result) result = self._filter_params(result)
return 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): def _format_port_for_destination(self, ip, port):
addr = netaddr.IPAddress(ip) if validate_ip_v6_address(ip):
if addr.version == 6:
if port == 0: if port == 0:
result = '.any' result = '.any'
else: else:
@ -959,10 +989,10 @@ class ApiParameters(Parameters):
if self._values['source'] is None: if self._values['source'] is None:
return None return None
try: try:
addr = netaddr.IPNetwork(self._values['source']) addr = ip_interface(u'{0}'.format(self._values['source']))
result = '{0}/{1}'.format(str(addr.ip), addr.prefixlen) result = '{0}/{1}'.format(str(addr.ip), addr.network.prefixlen)
return result return result
except netaddr.core.AddrFormatError: except ValueError:
raise F5ModuleError( raise F5ModuleError(
"The source IP address must be specified in CIDR format: address/prefix" "The source IP address must be specified in CIDR format: address/prefix"
) )
@ -977,7 +1007,7 @@ class ApiParameters(Parameters):
return result return result
destination = re.sub(r'^/[a-zA-Z0-9_.-]+/', '', self._values['destination']) destination = re.sub(r'^/[a-zA-Z0-9_.-]+/', '', self._values['destination'])
if self.is_valid_ip(destination): if is_valid_ip(destination):
result = Destination( result = Destination(
ip=destination, ip=destination,
port=None, port=None,
@ -1004,7 +1034,7 @@ class ApiParameters(Parameters):
if port == 'any': if port == 'any':
port = 0 port = 0
ip = matches.group('ip') ip = matches.group('ip')
if not self.is_valid_ip(ip): if not is_valid_ip(ip):
raise F5ModuleError( raise F5ModuleError(
"The provided destination is not a valid IP address" "The provided destination is not a valid IP address"
) )
@ -1019,7 +1049,7 @@ class ApiParameters(Parameters):
matches = re.search(pattern, destination) matches = re.search(pattern, destination)
if matches: if matches:
ip = matches.group('ip') ip = matches.group('ip')
if not self.is_valid_ip(ip): if not is_valid_ip(ip):
raise F5ModuleError( raise F5ModuleError(
"The provided destination is not a valid IP address" "The provided destination is not a valid IP address"
) )
@ -1034,7 +1064,7 @@ class ApiParameters(Parameters):
if len(parts) == 4: if len(parts) == 4:
# IPv4 # IPv4
ip, port = destination.split(':') ip, port = destination.split(':')
if not self.is_valid_ip(ip): if not is_valid_ip(ip):
raise F5ModuleError( raise F5ModuleError(
"The provided destination is not a valid IP address" "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 # Can be a port of "any". This only happens with IPv6
if port == 'any': if port == 'any':
port = 0 port = 0
if not self.is_valid_ip(ip): if not is_valid_ip(ip):
raise F5ModuleError( raise F5ModuleError(
"The provided destination is not a valid IP address" "The provided destination is not a valid IP address"
) )
@ -1152,8 +1182,7 @@ class ApiParameters(Parameters):
def enabled(self): def enabled(self):
if 'enabled' in self._values: if 'enabled' in self._values:
return True return True
else: return False
return False
@property @property
def disabled(self): def disabled(self):
@ -1189,6 +1218,34 @@ class ApiParameters(Parameters):
result.sort() result.sort()
return result 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): class ModuleParameters(Parameters):
services_map = { services_map = {
@ -1241,7 +1298,7 @@ class ModuleParameters(Parameters):
@property @property
def destination(self): def destination(self):
addr = self._values['destination'].split("%")[0] addr = self._values['destination'].split("%")[0]
if not self.is_valid_ip(addr): if not is_valid_ip(addr):
raise F5ModuleError( raise F5ModuleError(
"The provided destination is not a valid IP address" "The provided destination is not a valid IP address"
) )
@ -1263,10 +1320,10 @@ class ModuleParameters(Parameters):
if self._values['source'] is None: if self._values['source'] is None:
return None return None
try: try:
addr = netaddr.IPNetwork(self._values['source']) addr = ip_interface(u'{0}'.format(self._values['source']))
result = '{0}/{1}'.format(str(addr.ip), addr.prefixlen) result = '{0}/{1}'.format(str(addr.ip), addr.network.prefixlen)
return result return result
except netaddr.core.AddrFormatError: except ValueError:
raise F5ModuleError( raise F5ModuleError(
"The source IP address must be specified in CIDR format: address/prefix" "The source IP address must be specified in CIDR format: address/prefix"
) )
@ -1537,6 +1594,45 @@ class ModuleParameters(Parameters):
result.sort() result.sort()
return result 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): class Changes(Parameters):
pass pass
@ -1642,6 +1738,22 @@ class UsableChanges(Changes):
) )
return self._values['security_log_profiles'] 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): class ReportableChanges(Changes):
@property @property
@ -1707,6 +1819,20 @@ class ReportableChanges(Changes):
return True return True
return False 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): class VirtualServerValidator(object):
def __init__(self, module=None, client=None, want=None, have=None): 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. F5ModuleError: Raised when the IP versions of source and destination differ.
""" """
if self.want.source and self.want.destination: if self.want.source and self.want.destination:
want = netaddr.IPNetwork(self.want.source) want = ip_interface(u'{0}'.format(self.want.source))
have = netaddr.IPNetwork(self.want.destination_tuple.ip) have = ip_interface(u'{0}'.format(self.want.destination_tuple.ip))
if want.version != have.version: if want.version != have.version:
raise F5ModuleError( raise F5ModuleError(
"The source and destination addresses for the virtual server must be be the same type (IPv4 or IPv6)." "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): def source(self):
if self.want.source is None: if self.want.source is None:
return None return None
want = netaddr.IPNetwork(self.want.source) want = ip_interface(u'{0}'.format(self.want.source))
have = netaddr.IPNetwork(self.have.destination_tuple.ip) have = ip_interface(u'{0}'.format(self.have.destination_tuple.ip))
if want.version != have.version: if want.version != have.version:
raise F5ModuleError( raise F5ModuleError(
"The source and destination addresses for the virtual server must be be the same type (IPv4 or IPv6)." "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): if set(self.want.security_log_profiles) != set(self.have.security_log_profiles):
return self.want.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): class ModuleManager(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -2699,7 +2842,15 @@ class ArgumentSpec(object):
), ),
firewall_staged_policy=dict(), firewall_staged_policy=dict(),
firewall_enforced_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 = {}
self.argument_spec.update(f5_argument_spec) self.argument_spec.update(f5_argument_spec)
@ -2719,8 +2870,6 @@ def main():
) )
if not HAS_F5SDK: if not HAS_F5SDK:
module.fail_json(msg="The python f5-sdk module is required") 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: try:
client = F5Client(**module.params) client = F5Client(**module.params)