From babff882c0794fab81665744bcc1aa155b90eed2 Mon Sep 17 00:00:00 2001 From: Dharini Chandrasekar Date: Fri, 19 Aug 2016 18:34:18 +0000 Subject: [PATCH] Fixing HostName and adding support for HostAddress When config options in different projects use IPOpt as the opt's type, it restricts operators to only IP addresses. When the opt is set to HostnameOpt type, currently even an incomplete or invalid IP passes as a valid hostname. Also, currently HostnameOpt does not make sure that there is a presense of at least one non-numeric character in the provided host name. According to RFC 1123, (https://tools.ietf.org/html/rfc1123), a valid host name can never have the dotted-decimal form #.#.#.#, since at least the highest-level component label will be alphabetic. This patch fixes the existing Hostname Opt to abide by the stated RFC and also adds a new opt type that would enable operators to provide either a hostname or an IP and at the same time perform checks on both IPOpt type and HostnameOpt type, by setting opt type to "HostAddressOpt" type. This would ensure that an invalid IP does not pass as a valid hostname and at the same time retains the rules required to be followed for the validation of an acceptable hostname. Change-Id: I77bdb64b7e6e56ce761d76696bc4448a9bd325eb Closes-Bug: #1619044 Closes-Bug: #1615028 --- doc/source/opts.rst | 1 + oslo_config/cfg.py | 15 +++++ oslo_config/generator.py | 3 +- oslo_config/sphinxext.py | 1 + oslo_config/tests/test_types.py | 40 +++++++++++- oslo_config/types.py | 61 ++++++++++++++++++- .../add-HostAddressOpt-6e7e2afe7c7863cb.yaml | 13 ++++ 7 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-HostAddressOpt-6e7e2afe7c7863cb.yaml diff --git a/doc/source/opts.rst b/doc/source/opts.rst index e82ecff0..ccdf9a3c 100644 --- a/doc/source/opts.rst +++ b/doc/source/opts.rst @@ -16,6 +16,7 @@ Option Definitions .. autoclass:: IPOpt .. autoclass:: PortOpt .. autoclass:: HostnameOpt +.. autoclass:: HostAddressOpt .. autoclass:: URIOpt .. autoclass:: DeprecatedOpt .. autoclass:: SubCommandOpt diff --git a/oslo_config/cfg.py b/oslo_config/cfg.py index bc572003..bb94b33a 100644 --- a/oslo_config/cfg.py +++ b/oslo_config/cfg.py @@ -59,6 +59,7 @@ Type Option :class:`oslo_config.types.Dict` :class:`oslo_config.cfg.DictOpt` :class:`oslo_config.types.IPAddress` :class:`oslo_config.cfg.IPOpt` :class:`oslo_config.types.Hostname` :class:`oslo_config.cfg.HostnameOpt` +:class:`oslo_config.types.HostAddress`:class:`oslo_config.cfg.HostAddressOpt` :class:`oslo_config.types.URI` :class:`oslo_config.cfg.URIOpt` ==================================== ====== @@ -1318,6 +1319,20 @@ class HostnameOpt(Opt): **kwargs) +class HostAddressOpt(Opt): + """Option for either an IP or a hostname. + + Accepts valid hostnames and valid IP addresses. + + .. versionadded:: 3.22 + """ + + def __init__(self, name, version=None, **kwargs): + super(HostAddressOpt, self).__init__(name, + type=types.HostAddress(version), + **kwargs) + + class URIOpt(Opt): """Opt with URI type diff --git a/oslo_config/generator.py b/oslo_config/generator.py index 22a9f288..e2025c95 100644 --- a/oslo_config/generator.py +++ b/oslo_config/generator.py @@ -80,7 +80,8 @@ def _format_defaults(opt): elif opt.default is None: default_str = '' elif (isinstance(opt, (cfg.StrOpt, cfg.IPOpt, - cfg.HostnameOpt, cfg.URIOpt))): + cfg.HostnameOpt, cfg.HostAddressOpt, + cfg.URIOpt))): default_str = opt.default elif isinstance(opt, cfg.BoolOpt): default_str = str(opt.default).lower() diff --git a/oslo_config/sphinxext.py b/oslo_config/sphinxext.py index cd741871..8439c07a 100644 --- a/oslo_config/sphinxext.py +++ b/oslo_config/sphinxext.py @@ -77,6 +77,7 @@ _TYPE_DESCRIPTIONS = { cfg.PortOpt: 'port number', cfg.HostnameOpt: 'hostname', cfg.URIOpt: 'URI', + cfg.HostAddressOpt: 'host address', cfg._ConfigFileOpt: 'list of filenames', cfg._ConfigDirOpt: 'list of directory names', } diff --git a/oslo_config/tests/test_types.py b/oslo_config/tests/test_types.py index 8457263b..41edf6bd 100644 --- a/oslo_config/tests/test_types.py +++ b/oslo_config/tests/test_types.py @@ -687,6 +687,32 @@ class IPv6AddressTypeTests(IPAddressTypeTests): self.assertInvalid('192.168.0.1') +class HostAddressTypeTests(TypeTestHelper, unittest.TestCase): + type = types.HostAddress() + + def test_invalid_host_addresses(self): + self.assertInvalid('-1') + self.assertInvalid('_foo') + self.assertInvalid('3.14') + self.assertInvalid('10.0') + self.assertInvalid('host..name') + self.assertInvalid('org.10') + self.assertInvalid('0.0.00') + + def test_valid_host_addresses(self): + self.assertConvertedValue('foo.bar', 'foo.bar') + self.assertConvertedValue('192.168.0.1', '192.168.0.1') + self.assertConvertedValue('abcd:ef::1', 'abcd:ef::1') + self.assertConvertedValue('home-site-here.org.com', + 'home-site-here.org.com') + self.assertConvertedValue('3com.com', '3com.com') + self.assertConvertedValue('10.org', '10.org') + self.assertConvertedValue('cell1.nova.site1', 'cell1.nova.site1') + self.assertConvertedValue('ab-c.com', 'ab-c.com') + self.assertConvertedValue('abc.com-org', 'abc.com-org') + self.assertConvertedValue('abc.0-0', 'abc.0-0') + + class HostnameTypeTests(TypeTestHelper, unittest.TestCase): type = types.Hostname() @@ -726,6 +752,12 @@ class HostnameTypeTests(TypeTestHelper, unittest.TestCase): self.assertInvalid(".host.name.com") self.assertInvalid("no spaces") + def test_invalid_hostnames_with_numeric_characters(self): + self.assertInvalid("10.0.0.0") + self.assertInvalid("3.14") + self.assertInvalid("org.10") + self.assertInvalid('0.0.00') + def test_no_start_end_hyphens(self): self.assertInvalid("-host.com") self.assertInvalid("-hostname.com-") @@ -739,9 +771,13 @@ class HostnameTypeTests(TypeTestHelper, unittest.TestCase): self.assertConvertedEqual('cell1.nova.site1') self.assertConvertedEqual('site01001') self.assertConvertedEqual('home-site-here.org.com') - self.assertConvertedEqual('192.168.0.1') - self.assertConvertedEqual('1.1.1') self.assertConvertedEqual('localhost') + self.assertConvertedEqual('3com.com') + self.assertConvertedEqual('10.org') + self.assertConvertedEqual('10ab.10ab') + self.assertConvertedEqual('ab-c.com') + self.assertConvertedEqual('abc.com-org') + self.assertConvertedEqual('abc.0-0') def test_max_segment_size(self): self.assertConvertedEqual('host.%s.com' % ('x' * 63)) diff --git a/oslo_config/types.py b/oslo_config/types.py index 4fd14003..aa33973a 100644 --- a/oslo_config/types.py +++ b/oslo_config/types.py @@ -703,7 +703,7 @@ class IPAddress(ConfigType): class Hostname(ConfigType): - """Hostname type. + """Host domain name type. A hostname refers to a valid DNS or hostname. It must not be longer than 253 characters, have a segment greater than 63 characters, nor start or @@ -723,10 +723,14 @@ class Hostname(ConfigType): - Contains at least one character and a maximum of 63 characters - Consists only of allowed characters: letters (A-Z and a-z), digits (0-9), and hyphen (-) + - Ensures that the final segment (representing the top level domain + name) contains at least one non-numeric character - Does not begin or end with a hyphen - maximum total length of 253 characters - For more details , please see: http://tools.ietf.org/html/rfc1035 + For more details , please see: http://tools.ietf.org/html/rfc1035, + https://www.ietf.org/rfc/rfc1912, and + https://tools.ietf.org/html/rfc1123 """ if len(value) == 0: @@ -737,6 +741,10 @@ class Hostname(ConfigType): if value.endswith("."): value = value[:-1] allowed = re.compile("(?!-)[A-Z0-9-]{1,63}(? + Configuration option type of ``HostAddressOpt`` added to accept and + validate both IP addresses and hostnames. Please refer to the + ``features`` section for more information. + +features: + - Configuration option type of ``HostAddressOpt`` added to accept both + valid IP address (IPv4 and IPv6) values as well as hostnames. + The ``HostAddressOpt`` will accept both IPv4 and IPv6 addresses + and ensure that strict checks are performed on the IP versions. + This option type will also accept and accurately validate hostnames + ensuring that no invalid IP passes as a valid hostname.