Sandro Bonazzola has uploaded a new change for review.

Change subject: packaging: setup: engine fqdn validation
......................................................................

packaging: setup: engine fqdn validation

- Moved FQDN customization in its own plugin.
- Added FQDN validation, requiring the engine host name
  to be resolvable (additional reverse resolve check can
  be enabled through environment if needed).

Change-Id: Ic4724d9257506d67c663a0a3f312e80ebb04f42c
Signed-off-by: Sandro Bonazzola <[email protected]>
---
M ovirt-hosted-engine-setup.spec.in
M src/ovirt_hosted_engine_setup/constants.py
M src/plugins/ovirt-hosted-engine-setup/core/misc.py
M src/plugins/ovirt-hosted-engine-setup/engine/Makefile.am
M src/plugins/ovirt-hosted-engine-setup/engine/__init__.py
A src/plugins/ovirt-hosted-engine-setup/engine/fqdn.py
6 files changed, 295 insertions(+), 22 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-hosted-engine-setup 
refs/changes/69/17569/1

diff --git a/ovirt-hosted-engine-setup.spec.in 
b/ovirt-hosted-engine-setup.spec.in
index 35fc085..6e3d6ed 100644
--- a/ovirt-hosted-engine-setup.spec.in
+++ b/ovirt-hosted-engine-setup.spec.in
@@ -49,6 +49,7 @@
 Requires:       virt-viewer
 Requires:       openssl
 Requires:       sudo
+Requires:       bind-utils
 BuildRequires:  gettext
 BuildRequires:  otopi-devel >= 1.1.0
 BuildRequires:  python2-devel
diff --git a/src/ovirt_hosted_engine_setup/constants.py 
b/src/ovirt_hosted_engine_setup/constants.py
index 4e80673..516fe7b 100644
--- a/src/ovirt_hosted_engine_setup/constants.py
+++ b/src/ovirt_hosted_engine_setup/constants.py
@@ -191,6 +191,7 @@
     )
     def OVIRT_HOSTED_ENGINE_FQDN(self):
         return 'OVEHOSTED_NETWORK/fqdn'
+    FQDN_REVERSE_VALIDATION = 'OVEHOSTED_NETWORK/fqdnReverseValidation'
 
     @ohostedattrs(
         answerfile=True,
diff --git a/src/plugins/ovirt-hosted-engine-setup/core/misc.py 
b/src/plugins/ovirt-hosted-engine-setup/core/misc.py
index 693e378..e563c00 100644
--- a/src/plugins/ovirt-hosted-engine-setup/core/misc.py
+++ b/src/plugins/ovirt-hosted-engine-setup/core/misc.py
@@ -26,7 +26,7 @@
 
 from otopi import constants as otopicons
 from otopi import util
-from otopi import context
+from otopi import context as otopicontext
 from otopi import plugin
 
 
@@ -64,7 +64,7 @@
         stage=plugin.Stages.STAGE_INIT,
         priority=plugin.Stages.PRIORITY_FIRST,
     )
-    def _confirm(self):
+    def _init(self):
         interactive = self.environment[
             ohostedcons.CoreEnv.DEPLOY_PROCEED
         ] is None
@@ -86,31 +86,12 @@
                 default=_('Yes')
             ) == _('Yes').lower()
         if not self.environment[ohostedcons.CoreEnv.DEPLOY_PROCEED]:
-            raise context.Abort('Aborted by user')
+            raise otopicontext.Abort('Aborted by user')
 
         self.environment.setdefault(
             ohostedcons.CoreEnv.REQUIREMENTS_CHECK_ENABLED,
             True
         )
 
-    @plugin.event(
-        stage=plugin.Stages.STAGE_CUSTOMIZATION,
-    )
-    def _customization(self):
-        self.environment[
-            ohostedcons.NetworkEnv.OVIRT_HOSTED_ENGINE_FQDN
-        ] = self.dialog.queryString(
-            name='ovehosted_network_fqdn',
-            note=_(
-                'Please provide the FQDN for the engine '
-                'you would like to use. This needs to match '
-                'the FQDN that you will use for the engine '
-                'installation within the VM: '
-            ),
-            prompt=True,
-            caseSensitive=True,
-        )
-
-        # Validate the FQDN ?
 
 # vim: expandtab tabstop=4 shiftwidth=4
diff --git a/src/plugins/ovirt-hosted-engine-setup/engine/Makefile.am 
b/src/plugins/ovirt-hosted-engine-setup/engine/Makefile.am
index ee4f8fe..6e70058 100644
--- a/src/plugins/ovirt-hosted-engine-setup/engine/Makefile.am
+++ b/src/plugins/ovirt-hosted-engine-setup/engine/Makefile.am
@@ -26,6 +26,7 @@
 mydir=$(ovirthostedengineplugindir)/ovirt-hosted-engine-setup/engine
 dist_my_PYTHON = \
        __init__.py \
+       fqdn.py \
        health.py \
        add_host.py \
        os_install.py \
diff --git a/src/plugins/ovirt-hosted-engine-setup/engine/__init__.py 
b/src/plugins/ovirt-hosted-engine-setup/engine/__init__.py
index 237aa0d..190d9f5 100644
--- a/src/plugins/ovirt-hosted-engine-setup/engine/__init__.py
+++ b/src/plugins/ovirt-hosted-engine-setup/engine/__init__.py
@@ -25,6 +25,7 @@
 
 
 from . import os_install
+from . import fqdn
 from . import health
 from . import add_host
 
@@ -32,6 +33,7 @@
 @util.export
 def createPlugins(context):
     os_install.Plugin(context=context)
+    fqdn.Plugin(context=context)
     health.Plugin(context=context)
     add_host.Plugin(context=context)
 
diff --git a/src/plugins/ovirt-hosted-engine-setup/engine/fqdn.py 
b/src/plugins/ovirt-hosted-engine-setup/engine/fqdn.py
new file mode 100644
index 0000000..c1fa394
--- /dev/null
+++ b/src/plugins/ovirt-hosted-engine-setup/engine/fqdn.py
@@ -0,0 +1,287 @@
+#
+# ovirt-hosted-engine-setup -- ovirt hosted engine setup
+# Copyright (C) 2013 Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+
+"""FQDN plugin."""
+
+
+import gettext
+import re
+import socket
+
+
+from otopi import util
+from otopi import plugin
+
+
+from ovirt_hosted_engine_setup import constants as ohostedcons
+
+
+_ = lambda m: gettext.dgettext(message=m, domain='ovirt-hosted-engine-setup')
+
+
[email protected]
+class Plugin(plugin.PluginBase):
+    """Misc plugin."""
+
+    _IPADDR_RE = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
+
+    _DOMAIN_RE = re.compile(
+        flags=re.VERBOSE,
+        pattern=r"""
+            ^
+            [\w\.\-\_]+
+            \w+
+            $
+        """
+    )
+
+    _DIG_LOOKUP_RE = re.compile(
+        flags=re.VERBOSE,
+        pattern=r"""
+            ^
+            [\w.-]+
+            \s+
+            \d+
+            \s+
+            IN
+            \s+
+            (A|CNAME)
+            \s+
+            [\w.-]+
+        """
+    )
+
+    _DIG_REVLOOKUP_RE = re.compile(
+        flags=re.VERBOSE,
+        pattern=r"""
+            ^
+            (?P<query>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).in-addr.arpa.
+            \s+
+            \d+
+            \s+
+            IN
+            \s+
+            PTR
+            \s+
+            (?P<answer>[\w.-]+)
+            \.
+            $
+        """
+    )
+
+    def __init__(self, context):
+        super(Plugin, self).__init__(context=context)
+
+    def _validateFQDN(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,
+                )
+            )
+
+        if not fqdn:
+            raise RuntimeError(
+                _('Please specify host FQDN')
+            )
+
+        if len(fqdn) > 1000:
+            raise RuntimeError(
+                _('FQDN has invalid length')
+            )
+
+        components = fqdn.split('.', 1)
+        if len(components) == 1 or not components[0]:
+            self.logger.warning(
+                _('Host name {fqdn} has no domain suffix').format(
+                    fqdn=fqdn,
+                )
+            )
+        else:
+            if not self._DOMAIN_RE.match(components[1]):
+                raise RuntimeError(
+                    _('Host name {fqdn} has invalid domain name').format(
+                        fqdn=fqdn,
+                    )
+                )
+
+    def _validateFQDNresolvability(self, fqdn):
+        try:
+            resolvedAddresses = set([
+                address[0] for __, __, __, __, address in
+                socket.getaddrinfo(
+                    fqdn,
+                    None
+                )
+            ])
+            self.logger.debug(
+                '{fqdn} resolves to: {addresses}'.format(
+                    fqdn=fqdn,
+                    addresses=resolvedAddresses,
+                )
+            )
+            resolvedAddressesAsString = ' '.join(resolvedAddresses)
+        except socket.error:
+            raise RuntimeError(
+                _('{fqdn} did not resolve into an IP address').format(
+                    fqdn=fqdn,
+                )
+            )
+
+        resolvedByDNS = self._resolvedByDNS(fqdn)
+        if not resolvedByDNS:
+            self.logger.warning(
+                _(
+                    'Failed to resolve {fqdn} using DNS, '
+                    'it can be resolved only locally'
+                ).format(
+                    fqdn=fqdn,
+                )
+            )
+        elif self.environment[ohostedcons.NetworkEnv.FQDN_REVERSE_VALIDATION]:
+            revResolved = False
+            for address in resolvedAddresses:
+                for name in self._dig_reverse_lookup(address):
+                    revResolved = name.lower() == fqdn.lower()
+                    if revResolved:
+                        break
+                if revResolved:
+                    break
+            if not revResolved:
+                raise RuntimeError(
+                    _(
+                        'The following addresses: {addresses} did not reverse'
+                        'resolve into {fqdn}'
+                    ).format(
+                        addresses=resolvedAddressesAsString,
+                        fqdn=fqdn
+                    )
+                )
+
+    def _resolvedByDNS(self, fqdn):
+        args = [
+            self.command.get('dig'),
+            fqdn
+        ]
+        rc, stdout, stderr = self.execute(
+            args=args,
+            raiseOnError=False
+        )
+        resolved = False
+        if rc == 0:
+            for line in stdout:
+                if self._DIG_LOOKUP_RE.search(line):
+                    resolved = True
+        return resolved
+
+    def _dig_reverse_lookup(self, addr):
+        names = set()
+        args = [
+            self.command.get('dig'),
+            '-x',
+            addr,
+        ]
+        rc, stdout, stderr = self.execute(
+            args=args,
+            raiseOnError=False
+        )
+        if rc == 0:
+            for line in stdout:
+                found = self._DIG_REVLOOKUP_RE.search(line)
+                if found:
+                    names.add(found.group('answer'))
+        return names
+
+    @plugin.event(
+        stage=plugin.Stages.STAGE_INIT,
+    )
+    def _init(self):
+        self.environment.setdefault(
+            ohostedcons.NetworkEnv.OVIRT_HOSTED_ENGINE_FQDN,
+            None
+        )
+        self.environment.setdefault(
+            ohostedcons.NetworkEnv.FQDN_REVERSE_VALIDATION,
+            False
+        )
+
+    @plugin.event(
+        stage=plugin.Stages.STAGE_SETUP,
+    )
+    def _setup(self):
+        # Can't use python api here, it will call sys.exit
+        self.command.detect('dig')
+
+    @plugin.event(
+        stage=plugin.Stages.STAGE_CUSTOMIZATION,
+    )
+    def _customization(self):
+        interactive = self.environment[
+            ohostedcons.NetworkEnv.OVIRT_HOSTED_ENGINE_FQDN
+        ] is None
+        valid = False
+        while not valid:
+            if interactive:
+                self.environment[
+                    ohostedcons.NetworkEnv.OVIRT_HOSTED_ENGINE_FQDN
+                ] = self.dialog.queryString(
+                    name='OVEHOSTED_NETWORK_FQDN',
+                    note=_(
+                        'Please provide the FQDN for the engine '
+                        'you would like to use. This needs to match '
+                        'the FQDN that you will use for the engine '
+                        'installation within the VM: '
+                    ),
+                    prompt=True,
+                    caseSensitive=True,
+                )
+            try:
+                self._validateFQDN(
+                    self.environment[
+                        ohostedcons.NetworkEnv.OVIRT_HOSTED_ENGINE_FQDN
+                    ]
+                )
+                self._validateFQDNresolvability(
+                    self.environment[
+                        ohostedcons.NetworkEnv.OVIRT_HOSTED_ENGINE_FQDN
+                    ]
+                )
+                valid = True
+            except RuntimeError as e:
+                self.logger.debug('exception', exc_info=True)
+                if interactive:
+                    self.logger.error(
+                        _('Host name is not valid: {error}').format(
+                            error=e,
+                        ),
+                    )
+                else:
+                    raise RuntimeError(
+                        _('Host name is not valid: {error}').format(
+                            error=e,
+                        ),
+                    )
+
+
+# vim: expandtab tabstop=4 shiftwidth=4


-- 
To view, visit http://gerrit.ovirt.org/17569
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic4724d9257506d67c663a0a3f312e80ebb04f42c
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-hosted-engine-setup
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