Unverified Commit 44d9c02c authored by Tytus Kurek's avatar Tytus Kurek Committed by Graham Hayes

NAPTR DNS records

This patchset adds support for DNS NAPTR (Naming Authority
Pointer) Resource Record which is described in RFC 2915
(https://tools.ietf.org/html/rfc2915)

Change-Id: Ic7d26a6c653ce46e67438e983476782edd6ec890
Closes-Bug: 1779850
parent 7e7a43b8
......@@ -62,7 +62,8 @@ rectype2iparectype = {'A': ('arecord', '%(data)s'),
'NS': ('nsrecord', '%(data)s'),
'PTR': ('ptrrecord', '%(data)s'),
'SPF': ('spfrecord', '%(data)s'),
'SSHFP': ('sshfprecord', '%(data)s')}
'SSHFP': ('sshfprecord', '%(data)s'),
'NAPTR': ('naptrrecord', '%(data)s')}
IPA_INVALID_DATA = 3009
IPA_NOT_FOUND = 4001
......
......@@ -69,7 +69,7 @@ designate_opts = [
# Supported record types
cfg.ListOpt('supported-record-type', help='Supported record types',
default=['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS',
'PTR', 'SSHFP', 'SOA']),
'PTR', 'SSHFP', 'SOA', 'NAPTR']),
]
# Set some Oslo Log defaults
......
......@@ -51,6 +51,7 @@ from designate.objects.rrdata_a import A, AList # noqa
from designate.objects.rrdata_aaaa import AAAA, AAAAList # noqa
from designate.objects.rrdata_cname import CNAME, CNAMEList # noqa
from designate.objects.rrdata_mx import MX, MXList # noqa
from designate.objects.rrdata_naptr import NAPTR, NAPTRList # noqa
from designate.objects.rrdata_ns import NS, NSList # noqa
from designate.objects.rrdata_ptr import PTR, PTRList # noqa
from designate.objects.rrdata_soa import SOA, SOAList # noqa
......
......@@ -98,6 +98,9 @@ class StringFields(ovoo_fields.StringField):
RE_SSHFP_FINGERPRINT = r'^([0-9A-Fa-f]{10,40}|[0-9A-Fa-f]{64})\Z'
RE_TLDNAME = r'^(?!.{255,})(?:(?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-))' \
r'(?:\.(?:(?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-)))*\Z'
RE_NAPTR_FLAGS = r'^(?!.*(.).*\1)[APSU]+$'
RE_NAPTR_SERVICE = r'^([A-Za-z]([A-Za-z0-9]*)(\+[A-Za-z]([A-Za-z0-9]{0,31}))*)?' # noqa
RE_NAPTR_REGEXP = r'^([^0-9i\\])(.*)\1((.+)|(\\[1-9]))\1(i?)'
def __init__(self, nullable=False, read_only=False,
default=ovoo_fields.UnspecifiedDefault, description='',
......@@ -291,6 +294,49 @@ class TldField(StringFields):
return value
class NaptrFlagsField(StringFields):
def __init__(self, **kwargs):
super(NaptrFlagsField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(NaptrFlagsField, self).coerce(obj, attr, value)
if (len(value) > 255):
raise ValueError("NAPTR record flags field cannot be longer than"
" 255 characters" % value)
if not re.match(self.RE_NAPTR_FLAGS, "%s" % value):
raise ValueError("NAPTR record flags can be S, A, U and P" % value)
return value
class NaptrServiceField(StringFields):
def __init__(self, **kwargs):
super(NaptrServiceField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(NaptrServiceField, self).coerce(obj, attr, value)
if (len(value) > 255):
raise ValueError("NAPTR record service field cannot be longer than"
" 255 characters" % value)
if not re.match(self.RE_NAPTR_SERVICE, "%s" % value):
raise ValueError("%s NAPTR record service does not match" % value)
return value
class NaptrRegexpField(StringFields):
def __init__(self, **kwargs):
super(NaptrRegexpField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(NaptrRegexpField, self).coerce(obj, attr, value)
if (len(value) > 255):
raise ValueError("NAPTR record regexp field cannot be longer than"
" 255 characters" % value)
if value:
if not re.match(self.RE_NAPTR_REGEXP, "%s" % value):
raise ValueError("%s is not a NAPTR record regexp" % value)
return value
class Any(ovoo_fields.FieldType):
@staticmethod
def coerce(obj, attr, value):
......
# Copyright 2018 Canonical Ltd.
#
# Author: Tytus Kurek <tytus.kurek@canonical.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects.record import Record
from designate.objects.record import RecordList
from designate.objects import base
from designate.objects import fields
@base.DesignateRegistry.register
class NAPTR(Record):
"""
NAPTR Resource Record Type
Defined in: RFC2915
"""
fields = {
'order': fields.IntegerFields(minimum=0, maximum=65535),
'preference': fields.IntegerFields(minimum=0, maximum=65535),
'flags': fields.NaptrFlagsField(),
'service': fields.NaptrServiceField(),
'regexp': fields.NaptrRegexpField(),
'replacement': fields.DomainField(maxLength=255)
}
def _to_string(self):
return ("%(order)s %(preference)s %(flags)s %(service)s %(regexp)s "
"%(replacement)s" % self)
def _from_string(self, v):
order, preference, flags, service, regexp, replacement = v.split(' ')
self.order = int(order)
self.preference = int(preference)
self.flags = flags
self.service = service
self.regexp = regexp
self.replacement = replacement
# The record type is defined in the RFC. This will be used when the record
# is sent by mini-dns.
RECORD_TYPE = 35
@base.DesignateRegistry.register
class NAPTRList(RecordList):
LIST_ITEM_TYPE = NAPTR
fields = {
'objects': fields.ListOfObjectsField('NAPTR'),
}
# Copyright 2018 Canonical Ltd.
#
# Author: Tytus Kurek <tytus.kurek@canonical.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy import MetaData, Table, Enum
meta = MetaData()
def upgrade(migrate_engine):
meta.bind = migrate_engine
RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS',
'PTR', 'SSHFP', 'SOA', 'NAPTR']
records_table = Table('recordsets', meta, autoload=True)
records_table.columns.type.alter(name='type', type=Enum(*RECORD_TYPES))
def downgrade(migrate_engine):
meta.bind = migrate_engine
RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS',
'PTR', 'SSHFP', 'SOA']
records_table = Table('recordsets', meta, autoload=True)
# Delete all NAPTR records
records_table.filter_by(name='type', type='NAPTR').delete()
# Remove CAA from the ENUM
records_table.columns.type.alter(type=Enum(*RECORD_TYPES))
......@@ -29,7 +29,7 @@ CONF = cfg.CONF
RESOURCE_STATUSES = ['ACTIVE', 'PENDING', 'DELETED', 'ERROR']
RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', 'PTR',
'SSHFP', 'SOA']
'SSHFP', 'SOA', 'NAPTR']
TASK_STATUSES = ['ACTIVE', 'PENDING', 'DELETED', 'ERROR', 'COMPLETE']
TSIG_ALGORITHMS = ['hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256',
'hmac-sha384', 'hmac-sha512']
......
......@@ -1823,7 +1823,7 @@ class CentralServiceTest(CentralTestCase):
def test_update_recordset_immutable_type(self):
zone = self.create_zone()
# ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', 'PTR',
# 'SSHFP', 'SOA']
# 'SSHFP', 'SOA', 'NAPTR']
# Create a recordset
recordset = self.create_recordset(zone)
cname_recordset = self.create_recordset(zone, type='CNAME')
......
# Copyright 2018 Canonical Ltd.
#
# Author: Tytus Kurek <tytus.kurek@canonical.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
import oslotest.base
from designate import objects
LOG = logging.getLogger(__name__)
def debug(*a, **kw):
for v in a:
LOG.debug(repr(v))
for k in sorted(kw):
LOG.debug("%s: %s", k, repr(kw[k]))
class NAPTRRecordTest(oslotest.base.BaseTestCase):
def test_parse_naptr(self):
naptr_record = objects.NAPTR()
naptr_record._from_string(
'0 0 S SIP+D2U !^.*$!sip:customer-service@example.com! _sip._udp.example.com.') # noqa
self.assertEqual(0, naptr_record.order)
self.assertEqual(0, naptr_record.preference)
self.assertEqual('S', naptr_record.flags)
self.assertEqual('SIP+D2U', naptr_record.service)
self.assertEqual('!^.*$!sip:customer-service@example.com!',
naptr_record.regexp)
self.assertEqual('_sip._udp.example.com.', naptr_record.replacement)
......@@ -180,3 +180,9 @@ Objects SSHFP Record
:show-inheritance:
Objects NAPTR Record
====================
.. automodule:: designate.objects.rrdata_naptr
:members:
:undoc-members:
:show-inheritance:
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment