Sandro Bonazzola has uploaded a new change for review. Change subject: packaging: setup: added FQDN validation to engine-setup-2 ......................................................................
packaging: setup: added FQDN validation to engine-setup-2 Added missing FQDN falidation to engine-setup-2 Change-Id: Ie9764f32ba5e30062532bf11c67756677333c44f Signed-off-by: Sandro Bonazzola <[email protected]> --- M packaging/setup/plugins/ovirt-engine-setup/config/protocols.py 1 file changed, 211 insertions(+), 13 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/99/14699/1 diff --git a/packaging/setup/plugins/ovirt-engine-setup/config/protocols.py b/packaging/setup/plugins/ovirt-engine-setup/config/protocols.py index 7b23116..fac6c51 100644 --- a/packaging/setup/plugins/ovirt-engine-setup/config/protocols.py +++ b/packaging/setup/plugins/ovirt-engine-setup/config/protocols.py @@ -19,6 +19,7 @@ """Protocols plugin.""" +import re import socket import gettext _ = lambda m: gettext.dgettext(message=m, domain='ovirt-engine-setup') @@ -36,9 +37,187 @@ @util.export class Plugin(plugin.PluginBase): """Protocols plugin.""" + _IPADDR_PATTERN = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' + _IPADDR_RE = re.compile(_IPADDR_PATTERN) + _DOMAIN_RE = re.compile(r'^[\w\-\_]+\.[\w\.\-\_]+\w+$') + _INTERFACE_RE = re.compile( + r'^\d+:\s+(?P<interface>\w+):\s+<(?P<options>[^>]+).*' + ) + _ADDRESS_RE = re.compile( + r'\s+inet (?P<address>' + + _IPADDR_PATTERN + + r').+' + r'\s+(?P<interface>\w+)$' + ) + _NS_LOOKUP_RE = re.compile( + r'Address: (' + + _IPADDR_PATTERN + + r')' + ) + _NS_REVLOOKUP_RE = re.compile(r'[\w\.-]+\s+name\s\=\s([\w\.\-]+)\.') def __init__(self, context): super(Plugin, self).__init__(context=context) + + def _getConfiguredIps(self): + interfaces = {} + addresses = {} + rc, stdout, stderr = self.execute( + args=( + self.command.get('ip'), + 'addr', + ), + ) + for line in stdout: + interfacematch = self._INTERFACE_RE.match(line) + addressmatch = self._ADDRESS_RE.match(line) + if interfacematch is not None: + interfaces[ + interfacematch.group('interface') + ] = 'LOOPBACK' in interfacematch.group('options') + elif addressmatch is not None: + addresses.setdefault( + addressmatch.group('interface'), + [] + ).append( + addressmatch.group('address') + ) + iplist = [] + for interface, loopback in interfaces.iteritems(): + if not loopback: + iplist.extend(addresses.get(interface, [])) + if len(iplist) < 1: + raise RuntimeError( + _('Could not find any configured IP address on the host') + ) + return set(iplist) + + def _nslookup(self, addr, reverse=False): + pattern = (self._NS_REVLOOKUP_RE if reverse else self._NS_LOOKUP_RE) + addresses = set() + rc, stdout, stderr = self.execute( + args=( + self.command.get('nslookup'), + addr, + ), + raiseOnError=False + ) + if rc == 0: + #do not go over the first 2 lines in nslookup output + for line in stdout[2:]: + found = pattern.search(line) + if found: + foundAddress = found.group(1) + addresses.add(foundAddress) + return addresses + + def _validateFQDNisNotIP(self, fqdn): + if self._IPADDR_RE.match(fqdn): + raise RuntimeError( + _( + '{fqdn} is an IP address and not a FQDN. ' + 'A FQDN is needed to be able to generate ' + 'certificates correctly.' + ).format( + fqdn=fqdn, + ) + ) + + def _validateFQDNdomain(self, fqdn): + if ( + len(fqdn) < 1 or + len(fqdn) > 1000 or + not self._DOMAIN_RE.match(fqdn) + ): + raise RuntimeError( + _('{fqdn} has not a valid domain name').format( + fqdn=fqdn, + ) + ) + + def _validateFQDNresolvability(self, fqdn): + + #get set of IPs + ipAddresses = self._getConfiguredIps() + #resolve fqdn + resolvedAddresses = self._nslookup(fqdn) + resolvedFromDNS = False + if len(resolvedAddresses) > 0: + resolvedFromDNS = True + else: + try: + resolvedAddresses = set(socket.gethostbyname_ex(fqdn)[2]) + except socket.error: + #can't be resolved by /etc/hosts + raise RuntimeError( + _('{fqdn} did not resolve into an IP address').format( + fqdn=fqdn, + ) + ) + + self.logger.warning( + _( + 'Failed to resolve {fqdn} using DNS, ' + 'it can be resolved only locally' + ).format( + fqdn=fqdn, + ) + ) + + prettyString = ' '.join( + [str(addr) for addr in resolvedAddresses] + ) + + #compare found IP with list of local IPs and match. + if not resolvedAddresses.issubset(ipAddresses): + raise RuntimeError( + _( + "The following address(es): {addresses} can't be mapped " + 'to non loopback devices on this host' + ).format( + addresses=prettyString + ) + ) + + #reverse resolved IP and compare with given fqdn + if resolvedFromDNS: + counter = 0 + for address in resolvedAddresses: + addressSet = self._nslookup( + addr=address, + reverse=True + ) + reResolvedAddress = None + revResolved = False + if len(addressSet) > 0: + reResolvedAddress = addressSet.pop() + if reResolvedAddress.lower() == fqdn.lower(): + counter += 1 + revResolved = True + if not revResolved: + self.logger.warning( + _( + '{address} did not reverse-resolve into {fqdn}' + ).format( + address=address, + fqdn=fqdn, + ) + ) + if counter < 1: + raise RuntimeError( + _( + 'The following addresses: {addresses} did not reverse' + 'resolve into {fqdn}' + ).format( + addresses=prettyString, + fqdn=fqdn + ) + ) + + def _validateFQDN(self, fqdn): + self._validateFQDNisNotIP(fqdn) + self._validateFQDNdomain(fqdn) + self._validateFQDNresolvability(fqdn) @plugin.event( stage=plugin.Stages.STAGE_INIT, @@ -77,6 +256,8 @@ stage=plugin.Stages.STAGE_SETUP, ) def _setup(self): + self.command.detect('ip') + self.command.detect('nslookup') if self.environment[osetupcons.CoreEnv.DEVELOPER_MODE]: self.environment[ osetupcons.ConfigEnv.HTTP_PORT @@ -100,19 +281,36 @@ ], ) def _customization(self): - if self.environment[osetupcons.ConfigEnv.FQDN] is None: - fqdn = socket.getfqdn() - self.environment[ - osetupcons.ConfigEnv.FQDN - ] = self.dialog.queryString( - name='OVESETUP_NETWORK_FQDN', - note=_( - 'Host fully qualified DNS name of ' - 'this server [@DEFAULT@]: ' - ), - prompt=True, - default=fqdn, - ) + interactive = self.environment[osetupcons.ConfigEnv.FQDN] is None + validFQDN = False + while not validFQDN: + if interactive: + fqdn = socket.getfqdn() + self.environment[ + osetupcons.ConfigEnv.FQDN + ] = self.dialog.queryString( + name='OVESETUP_NETWORK_FQDN', + note=_( + 'Host fully qualified DNS name of ' + 'this server [@DEFAULT@]: ' + ), + prompt=True, + default=fqdn, + ) + try: + self._validateFQDN( + self.environment[osetupcons.ConfigEnv.FQDN] + ) + validFQDN = True + except RuntimeError as error: + if interactive: + self.logger.error( + _('FQDN is not valid: {error}').format( + error=str(error) + ) + ) + else: + raise @plugin.event( stage=plugin.Stages.STAGE_MISC, -- To view, visit http://gerrit.ovirt.org/14699 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ie9764f32ba5e30062532bf11c67756677333c44f Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Sandro Bonazzola <[email protected]> _______________________________________________ Engine-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/engine-patches
