diff --git a/designate/objects/rrdata_ns.py b/designate/objects/rrdata_ns.py index db3026c21..196d596e4 100644 --- a/designate/objects/rrdata_ns.py +++ b/designate/objects/rrdata_ns.py @@ -32,6 +32,16 @@ class NS(Record): } } + @classmethod + def get_recordset_schema_changes(cls): + return { + 'name': { + 'schema': { + 'format': 'ns-hostname', + }, + }, + } + def _to_string(self): return self.nsdname diff --git a/designate/schema/format.py b/designate/schema/format.py index 13b0ed466..ea6a04578 100644 --- a/designate/schema/format.py +++ b/designate/schema/format.py @@ -102,6 +102,18 @@ def is_hostname(instance): return True +@draft4_format_checker.checks("ns-hostname") +def is_ns_hostname(instance): + if not isinstance(instance, compat.str_types): + return True + + # BIND doesn't like *.host.com. see bug #1533299 + if not re.match(RE_ZONENAME, instance): + return False + + return True + + @draft3_format_checker.checks("ip-or-host") @draft4_format_checker.checks("ip-or-host") def is_ip_or_host(instance): diff --git a/designate/tests/test_schema/test_format.py b/designate/tests/test_schema/test_format.py index 337fc11c4..4a8e3b6dd 100644 --- a/designate/tests/test_schema/test_format.py +++ b/designate/tests/test_schema/test_format.py @@ -149,6 +149,76 @@ class SchemaFormatTest(TestCase): for hostname in invalid_hostnames: self.assertFalse(format.is_hostname(hostname)) + def test_is_ns_hostname(self): + valid_ns_hostnames = [ + 'example.com.', + 'www.example.com.', + '12345.example.com.', + '192-0-2-1.example.com.', + 'ip192-0-2-1.example.com.', + 'www.ip192-0-2-1.example.com.', + 'ip192-0-2-1.www.example.com.', + 'abc-123.example.com.', + '_tcp.example.com.', + '_service._tcp.example.com.', + ('1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2' + '.ip6.arpa.'), + '1.1.1.1.in-addr.arpa.', + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.', + ('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi.'), + ] + + invalid_ns_hostnames = [ + # Wildcard NS hostname, bug #1533299 + '*.example.com.', + '**.example.com.', + '*.*.example.org.', + 'a.*.example.org.', + # Exceeds single lable length limit + ('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkL' + '.'), + ('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkL' + '.'), + # Exceeds total length limit + ('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.' + 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq.'), + # Empty label part + 'abc..def.', + '..', + # Invalid character + 'abc$.def.', + 'abc.def$.', + # Labels must not start with a - + '-abc.', + 'abc.-def.', + 'abc.-def.ghi.', + # Labels must not end with a - + 'abc-.', + 'abc.def-.', + 'abc.def-.ghi.', + # Labels must not start or end with a - + '-abc-.', + 'abc.-def-.', + 'abc.-def-.ghi.', + # Trailing newline - Bug 1471158 + "www.example.com.\n", + ] + + for hostname in valid_ns_hostnames: + self.assertTrue(format.is_ns_hostname(hostname)) + + for hostname in invalid_ns_hostnames: + self.assertFalse(format.is_ns_hostname(hostname)) + def test_is_zonename(self): valid_zonenames = [ 'example.com.', diff --git a/functionaltests/api/v2/test_recordset.py b/functionaltests/api/v2/test_recordset.py index ba00e8039..2d8f3671b 100644 --- a/functionaltests/api/v2/test_recordset.py +++ b/functionaltests/api/v2/test_recordset.py @@ -183,6 +183,14 @@ class RecordsetTest(DesignateV2Test): for m in verify_models: self.assert_dns(m) + def test_create_wildcard_NS(self): + client = RecordsetClient.as_user('default') + + model = datagen.wildcard_ns_recordset(self.zone.name) + self._assert_exception( + exceptions.BadRequest, 'invalid_object', 400, + client.post_recordset, self.zone.id, model) + def test_cname_recordsets_cannot_have_more_than_one_record(self): post_model = datagen.random_cname_recordset(zone_name=self.zone.name) post_model.records = [ diff --git a/functionaltests/common/datagen.py b/functionaltests/common/datagen.py index 681d84040..833c3469a 100644 --- a/functionaltests/common/datagen.py +++ b/functionaltests/common/datagen.py @@ -221,3 +221,9 @@ def random_tld_data(): "name": random_string(prefix='tld') } return TLDModel.from_dict(data) + + +def wildcard_ns_recordset(zone_name): + name = "*.{0}".format(zone_name) + records = ["ns.example.com."] + return random_recordset_data('NS', zone_name, name, records)