Browse Source

Add CAA support to Dyn, PowerDNS, and Route53

pull/103/head
Ross McFarland 8 years ago
parent
commit
1e68cd6ae9
No known key found for this signature in database GPG Key ID: 61C10C4FC8FE4A89
6 changed files with 68 additions and 29 deletions
  1. +16
    -0
      octodns/provider/dyn.py
  2. +23
    -2
      octodns/provider/powerdns.py
  3. +21
    -2
      octodns/provider/route53.py
  4. +3
    -8
      octodns/record.py
  5. +2
    -2
      requirements.txt
  6. +3
    -15
      tests/test_octodns_record.py

+ 16
- 0
octodns/provider/dyn.py View File

@ -111,6 +111,7 @@ class DynProvider(BaseProvider):
'a_records': 'A', 'a_records': 'A',
'aaaa_records': 'AAAA', 'aaaa_records': 'AAAA',
'alias_records': 'ALIAS', 'alias_records': 'ALIAS',
'caa_records': 'CAA',
'cname_records': 'CNAME', 'cname_records': 'CNAME',
'mx_records': 'MX', 'mx_records': 'MX',
'naptr_records': 'NAPTR', 'naptr_records': 'NAPTR',
@ -194,6 +195,14 @@ class DynProvider(BaseProvider):
'value': record.alias 'value': record.alias
} }
def _data_for_CAA(self, _type, records):
return {
'type': _type,
'ttl': records[0].ttl,
'values': [{'flags': r.flags, 'tag': r.tag, 'value': r.value}
for r in records],
}
def _data_for_CNAME(self, _type, records): def _data_for_CNAME(self, _type, records):
record = records[0] record = records[0]
return { return {
@ -382,6 +391,13 @@ class DynProvider(BaseProvider):
_kwargs_for_AAAA = _kwargs_for_A _kwargs_for_AAAA = _kwargs_for_A
def _kwargs_for_CAA(self, record):
return [{
'flags': v.flags,
'tag': v.tag,
'value': v.value,
} for v in record.values]
def _kwargs_for_CNAME(self, record): def _kwargs_for_CNAME(self, record):
return [{ return [{
'cname': record.value, 'cname': record.value,


+ 23
- 2
octodns/provider/powerdns.py View File

@ -14,8 +14,8 @@ from .base import BaseProvider
class PowerDnsBaseProvider(BaseProvider): class PowerDnsBaseProvider(BaseProvider):
SUPPORTS_GEO = False SUPPORTS_GEO = False
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR',
'SPF', 'SSHFP', 'SRV', 'TXT'))
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'MX', 'NAPTR', 'NS',
'PTR', 'SPF', 'SSHFP', 'SRV', 'TXT'))
TIMEOUT = 5 TIMEOUT = 5
def __init__(self, id, host, api_key, port=8081, scheme="http", *args, def __init__(self, id, host, api_key, port=8081, scheme="http", *args,
@ -61,6 +61,21 @@ class PowerDnsBaseProvider(BaseProvider):
_data_for_AAAA = _data_for_multiple _data_for_AAAA = _data_for_multiple
_data_for_NS = _data_for_multiple _data_for_NS = _data_for_multiple
def _data_for_CAA(self, rrset):
values = []
for record in rrset['records']:
flags, tag, value = record['content'].split(' ', 2)
values.append({
'flags': flags,
'tag': tag,
'value': value,
})
return {
'type': rrset['type'],
'values': values,
'ttl': rrset['ttl']
}
def _data_for_single(self, rrset): def _data_for_single(self, rrset):
return { return {
'type': rrset['type'], 'type': rrset['type'],
@ -194,6 +209,12 @@ class PowerDnsBaseProvider(BaseProvider):
_records_for_AAAA = _records_for_multiple _records_for_AAAA = _records_for_multiple
_records_for_NS = _records_for_multiple _records_for_NS = _records_for_multiple
def _records_for_CAA(self, record):
return [{
'content': '{} {} "{}"'.format(v.flags, v.tag, v.value),
'disabled': False
} for v in record.values]
def _records_for_single(self, record): def _records_for_single(self, record):
return [{'content': record.value, 'disabled': False}] return [{'content': record.value, 'disabled': False}]


+ 21
- 2
octodns/provider/route53.py View File

@ -90,6 +90,10 @@ class _Route53Record(object):
_values_for_AAAA = _values_for_values _values_for_AAAA = _values_for_values
_values_for_NS = _values_for_values _values_for_NS = _values_for_values
def _values_for_CAA(self, record):
return ['{} {} "{}"'.format(v.flags, v.tag, v.value)
for v in record.values]
def _values_for_value(self, record): def _values_for_value(self, record):
return [record.value] return [record.value]
@ -222,8 +226,8 @@ class Route53Provider(BaseProvider):
In general the account used will need full permissions on Route53. In general the account used will need full permissions on Route53.
''' '''
SUPPORTS_GEO = True SUPPORTS_GEO = True
SUPPORTS = set(('A', 'AAAA', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR', 'SPF',
'SRV', 'TXT'))
SUPPORTS = set(('A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR',
'SPF', 'SRV', 'TXT'))
# This should be bumped when there are underlying changes made to the # This should be bumped when there are underlying changes made to the
# health check config. # health check config.
@ -319,6 +323,21 @@ class Route53Provider(BaseProvider):
_data_for_A = _data_for_geo _data_for_A = _data_for_geo
_data_for_AAAA = _data_for_geo _data_for_AAAA = _data_for_geo
def _data_for_CAA(self, rrset):
values = []
for rr in rrset['ResourceRecords']:
flags, tag, value = rr['Value'].split(' ')
values.append({
'flags': flags,
'tag': tag,
'value': value[1:-1],
})
return {
'type': rrset['Type'],
'values': values,
'ttl': int(rrset['TTL'])
}
def _data_for_single(self, rrset): def _data_for_single(self, rrset):
return { return {
'type': rrset['Type'], 'type': rrset['Type'],


+ 3
- 8
octodns/record.py View File

@ -393,18 +393,13 @@ class CaaValue(object):
reasons = [] reasons = []
try: try:
flags = int(value.get('flags', 0)) flags = int(value.get('flags', 0))
if flags not in (0, 1):
if flags not in (0, 128):
reasons.append('invalid flags "{}"'.format(flags)) reasons.append('invalid flags "{}"'.format(flags))
except ValueError: except ValueError:
reasons.append('invalid flags "{}"'.format(value['flags'])) reasons.append('invalid flags "{}"'.format(value['flags']))
try:
tag = value['tag']
if tag not in ('issue', 'issuewild', 'iodef'):
reasons.append('invalid tag "{}"'.format(tag))
except KeyError:
if 'tag' not in value:
reasons.append('missing tag') reasons.append('missing tag')
if 'value' not in value: if 'value' not in value:
reasons.append('missing value') reasons.append('missing value')
@ -431,7 +426,7 @@ class CaaValue(object):
return cmp(self.flags, other.flags) return cmp(self.flags, other.flags)
def __repr__(self): def __repr__(self):
return "'{} {} {}'".format(self.flags, self.tag, self.value)
return '{} {} "{}"'.format(self.flags, self.tag, self.value)
class CaaRecord(_ValuesMixin, Record): class CaaRecord(_ValuesMixin, Record):


+ 2
- 2
requirements.txt View File

@ -4,10 +4,10 @@ PyYaml==3.12
azure-mgmt-dns==1.0.1 azure-mgmt-dns==1.0.1
azure-common==1.1.6 azure-common==1.1.6
boto3==1.4.6 boto3==1.4.6
botocore==1.6.0
botocore==1.6.8
dnspython==1.15.0 dnspython==1.15.0
docutils==0.14 docutils==0.14
dyn==1.7.10
dyn==1.8.0
futures==3.1.1 futures==3.1.1
incf.countryutils==1.0 incf.countryutils==1.0
ipaddress==1.0.18 ipaddress==1.0.18


+ 3
- 15
tests/test_octodns_record.py View File

@ -212,7 +212,7 @@ class TestRecord(TestCase):
'tag': 'issue', 'tag': 'issue',
'value': 'ca.example.net', 'value': 'ca.example.net',
}, { }, {
'flags': 1,
'flags': 128,
'tag': 'iodef', 'tag': 'iodef',
'value': 'mailto:security@example.com', 'value': 'mailto:security@example.com',
}] }]
@ -246,7 +246,7 @@ class TestRecord(TestCase):
self.assertFalse(a.changes(a, target)) self.assertFalse(a.changes(a, target))
# Diff in flags causes change # Diff in flags causes change
other = CaaRecord(self.zone, 'a', {'ttl': 30, 'values': a_values}) other = CaaRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
other.values[0].flags = 1
other.values[0].flags = 128
change = a.changes(other, target) change = a.changes(other, target)
self.assertEqual(change.existing, a) self.assertEqual(change.existing, a)
self.assertEqual(change.new, other) self.assertEqual(change.new, other)
@ -927,7 +927,7 @@ class TestRecordValidation(TestCase):
'type': 'CAA', 'type': 'CAA',
'ttl': 600, 'ttl': 600,
'value': { 'value': {
'flags': 1,
'flags': 128,
'tag': 'iodef', 'tag': 'iodef',
'value': 'http://foo.bar.com/' 'value': 'http://foo.bar.com/'
} }
@ -968,18 +968,6 @@ class TestRecordValidation(TestCase):
}) })
self.assertEquals(['missing tag'], ctx.exception.reasons) self.assertEquals(['missing tag'], ctx.exception.reasons)
# invalid tag
with self.assertRaises(ValidationError) as ctx:
Record.new(self.zone, '', {
'type': 'CAA',
'ttl': 600,
'value': {
'tag': 'xyz',
'value': 'http://foo.bar.com/',
}
})
self.assertEquals(['invalid tag "xyz"'], ctx.exception.reasons)
# missing value # missing value
with self.assertRaises(ValidationError) as ctx: with self.assertRaises(ValidationError) as ctx:
Record.new(self.zone, '', { Record.new(self.zone, '', {


Loading…
Cancel
Save