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

Reply via email to