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
