From be7cd8be8c90ada880c9929b0e1168db5acbe820 Mon Sep 17 00:00:00 2001 From: Harald Jensas Date: Sat, 13 Jan 2018 15:17:57 +0100 Subject: [PATCH] Update validations to validate all subnets Implements: blueprint tripleo-routed-networks-ironic-inspector Implements: blueprint tripleo-routed-networks-deployment Change-Id: Ic6a2bb8ee4560623532cd0ff3eb906bf9daa8c8d --- instack_undercloud/undercloud.py | 19 +++++++ instack_undercloud/validator.py | 92 +++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 30 deletions(-) diff --git a/instack_undercloud/undercloud.py b/instack_undercloud/undercloud.py index 00d0f936d..b5d685987 100644 --- a/instack_undercloud/undercloud.py +++ b/instack_undercloud/undercloud.py @@ -764,6 +764,10 @@ def _check_sysctl(): raise RuntimeError('Missing sysctl options') +def _cidr_overlaps(a, b): + return a.first <= b.last and b.first <= a.last + + def _validate_network(): def error_handler(message): LOG.error('Undercloud configuration validation failed: %s', message) @@ -778,6 +782,21 @@ def _validate_network(): for opt in _subnets_opts}) validator.validate_config(params, error_handler) + # Validate subnet parameters + subnet_cidrs = [] + for subnet in CONF.subnets: + subnet_opts = CONF.get(subnet) + params = {opt.name: subnet_opts[opt.name] for opt in _subnets_opts} + + if any(_cidr_overlaps(x, netaddr.IPNetwork(subnet_opts.cidr)) + for x in subnet_cidrs): + message = ('CIDR of %s, %s, overlaps with another subnet.' % + (subnet, subnet_opts.cidr)) + error_handler(message) + subnet_cidrs.append(netaddr.IPNetwork(subnet_opts.cidr)) + + validator.validate_subnet(subnet, params, error_handler) + def _validate_no_ip_change(): """Disallow provisioning interface IP changes diff --git a/instack_undercloud/validator.py b/instack_undercloud/validator.py index d72f7daa7..e66f74081 100644 --- a/instack_undercloud/validator.py +++ b/instack_undercloud/validator.py @@ -14,6 +14,7 @@ import netaddr import netifaces +import six SUPPORTED_ARCHITECTURES = ['ppc64le'] @@ -75,23 +76,35 @@ def _validate_value_formats(params, error_callback): work properly. For example, local_ip must be in CIDR form, and the hostname must be a FQDN. """ - try: - local_ip = netaddr.IPNetwork(params['local_ip']) - if local_ip.prefixlen == 32: - raise netaddr.AddrFormatError('Invalid netmask') - # If IPv6 the ctlplane network uses the EUI-64 address format, - # which requires the prefix to be /64 - if local_ip.version == 6 and local_ip.prefixlen != 64: - raise netaddr.AddrFormatError('Prefix must be 64 for IPv6') - except netaddr.core.AddrFormatError as e: - message = ('local_ip "%s" not valid: "%s" ' - 'Value must be in CIDR format.' % - (params['local_ip'], str(e))) - error_callback(message) - hostname = params['undercloud_hostname'] - if hostname is not None and '.' not in hostname: - message = 'Hostname "%s" is not fully qualified.' % hostname - error_callback(message) + for param in ('local_ip', 'cidr'): + if param in params: + try: + ip_net = netaddr.IPNetwork(params[param]) + if (ip_net.prefixlen == 32) or (ip_net.prefixlen == 0): + message = ('"%s" "%s" not valid: Invalid netmask.' % + (param, params[param])) + error_callback(message) + # If IPv6 the ctlplane network uses the EUI-64 address format, + # which requires the prefix to be /64 + if ip_net.version == 6 and ip_net.prefixlen != 64: + message = ('"%s" "%s" not valid: ' + 'Prefix must be 64 for IPv6.' % + (param, params[param])) + error_callback(message) + except netaddr.core.AddrFormatError as e: + message = ('"%s" "%s" not valid: "%s" ' + 'Value must be in CIDR format.' % + (param, params[param], str(e))) + error_callback(message) + except TypeError as e: + message = ('"%s" "%s" invalid type: "%s" ' % + (param, params[param], str(e))) + error_callback(message) + if 'undercloud_hostname' in params: + hostname = params['undercloud_hostname'] + if hostname is not None and '.' not in hostname: + message = 'Hostname "%s" is not fully qualified.' % hostname + error_callback(message) def _validate_in_cidr(params, error_callback): @@ -108,26 +121,28 @@ def _validate_in_cidr(params, error_callback): message = 'Invalid IP address: %s' % params[name] error_callback(message) - params['just_local_ip'] = params['local_ip'].split('/')[0] + # NOTE(hjensas): Only check certs etc if not validating routed subnets + if 'local_ip' in params: + params['just_local_ip'] = params['local_ip'].split('/')[0] + validate_addr_in_cidr(params, 'just_local_ip', 'local_ip') + # NOTE(bnemec): The ui needs to be externally accessible, which means + # in many cases we can't have the public vip on the provisioning + # network. In that case users are on their own to ensure they've picked + # valid values for the VIP hosts. + if ((params['undercloud_service_certificate'] or + params['generate_service_certificate']) and + not params['enable_ui']): + validate_addr_in_cidr(params, 'undercloud_public_host', + require_ip=False) + validate_addr_in_cidr(params, 'undercloud_admin_host', + require_ip=False) # undercloud.conf uses inspection_iprange, the configuration wizard # tool passes the values separately. if 'inspection_iprange' in params: inspection_iprange = params['inspection_iprange'].split(',') params['inspection_start'] = inspection_iprange[0] params['inspection_end'] = inspection_iprange[1] - validate_addr_in_cidr(params, 'just_local_ip', 'local_ip') validate_addr_in_cidr(params, 'gateway') - # NOTE(bnemec): The ui needs to be externally accessible, which means in - # many cases we can't have the public vip on the provisioning network. - # In that case users are on their own to ensure they've picked valid - # values for the VIP hosts. - if ((params['undercloud_service_certificate'] or - params['generate_service_certificate']) and - not params['enable_ui']): - validate_addr_in_cidr(params, 'undercloud_public_host', - require_ip=False) - validate_addr_in_cidr(params, 'undercloud_admin_host', - require_ip=False) validate_addr_in_cidr(params, 'dhcp_start') validate_addr_in_cidr(params, 'dhcp_end') validate_addr_in_cidr(params, 'inspection_start', 'Inspection range start') @@ -177,3 +192,20 @@ def _validate_interface_exists(params, error_callback): message = ('Invalid local_interface specified. %s is not available.' % local_interface) error_callback(message) + + +def _validate_no_missing_subnet_param(name, params, error_callback): + if None in six.viewvalues(params): + missing = list((k) for k, v in params.iteritems() if not v) + message = 'subnet %s. Missing option(s): %s' % (name, missing) + error_callback(message) + + +def validate_subnet(name, params, error_callback): + local_params = dict(params) + _validate_no_missing_subnet_param(name, params, error_callback) + _validate_value_formats(local_params, error_callback) + _validate_in_cidr(local_params, error_callback) + _validate_dhcp_range(local_params, error_callback) + _validate_inspection_range(local_params, error_callback) + _validate_no_overlap(local_params, error_callback)