From 1e68cd6ae98a8d6891010174bd73992e68fbae58 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 26 Aug 2017 09:03:59 -0700 Subject: [PATCH] Add CAA support to Dyn, PowerDNS, and Route53 --- octodns/provider/dyn.py | 16 ++++++++++++++++ octodns/provider/powerdns.py | 25 +++++++++++++++++++++++-- octodns/provider/route53.py | 23 +++++++++++++++++++++-- octodns/record.py | 11 +++-------- requirements.txt | 4 ++-- tests/test_octodns_record.py | 18 +++--------------- 6 files changed, 68 insertions(+), 29 deletions(-) diff --git a/octodns/provider/dyn.py b/octodns/provider/dyn.py index e21b93e..3b7b9ea 100644 --- a/octodns/provider/dyn.py +++ b/octodns/provider/dyn.py @@ -111,6 +111,7 @@ class DynProvider(BaseProvider): 'a_records': 'A', 'aaaa_records': 'AAAA', 'alias_records': 'ALIAS', + 'caa_records': 'CAA', 'cname_records': 'CNAME', 'mx_records': 'MX', 'naptr_records': 'NAPTR', @@ -194,6 +195,14 @@ class DynProvider(BaseProvider): '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): record = records[0] return { @@ -382,6 +391,13 @@ class DynProvider(BaseProvider): _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): return [{ 'cname': record.value, diff --git a/octodns/provider/powerdns.py b/octodns/provider/powerdns.py index 62b6fd8..55ca0b1 100644 --- a/octodns/provider/powerdns.py +++ b/octodns/provider/powerdns.py @@ -14,8 +14,8 @@ from .base import BaseProvider class PowerDnsBaseProvider(BaseProvider): 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 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_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): return { 'type': rrset['type'], @@ -194,6 +209,12 @@ class PowerDnsBaseProvider(BaseProvider): _records_for_AAAA = _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): return [{'content': record.value, 'disabled': False}] diff --git a/octodns/provider/route53.py b/octodns/provider/route53.py index 6f9adc2..0600511 100644 --- a/octodns/provider/route53.py +++ b/octodns/provider/route53.py @@ -90,6 +90,10 @@ class _Route53Record(object): _values_for_AAAA = _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): return [record.value] @@ -222,8 +226,8 @@ class Route53Provider(BaseProvider): In general the account used will need full permissions on Route53. ''' 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 # health check config. @@ -319,6 +323,21 @@ class Route53Provider(BaseProvider): _data_for_A = _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): return { 'type': rrset['Type'], diff --git a/octodns/record.py b/octodns/record.py index cc9949f..aa41606 100644 --- a/octodns/record.py +++ b/octodns/record.py @@ -393,18 +393,13 @@ class CaaValue(object): reasons = [] try: flags = int(value.get('flags', 0)) - if flags not in (0, 1): + if flags not in (0, 128): reasons.append('invalid flags "{}"'.format(flags)) except ValueError: 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') - if 'value' not in value: reasons.append('missing value') @@ -431,7 +426,7 @@ class CaaValue(object): return cmp(self.flags, other.flags) def __repr__(self): - return "'{} {} {}'".format(self.flags, self.tag, self.value) + return '{} {} "{}"'.format(self.flags, self.tag, self.value) class CaaRecord(_ValuesMixin, Record): diff --git a/requirements.txt b/requirements.txt index 2aec6d0..d2be70f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,10 @@ PyYaml==3.12 azure-mgmt-dns==1.0.1 azure-common==1.1.6 boto3==1.4.6 -botocore==1.6.0 +botocore==1.6.8 dnspython==1.15.0 docutils==0.14 -dyn==1.7.10 +dyn==1.8.0 futures==3.1.1 incf.countryutils==1.0 ipaddress==1.0.18 diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 10e3869..215abe1 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -212,7 +212,7 @@ class TestRecord(TestCase): 'tag': 'issue', 'value': 'ca.example.net', }, { - 'flags': 1, + 'flags': 128, 'tag': 'iodef', 'value': 'mailto:security@example.com', }] @@ -246,7 +246,7 @@ class TestRecord(TestCase): self.assertFalse(a.changes(a, target)) # Diff in flags causes change 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) self.assertEqual(change.existing, a) self.assertEqual(change.new, other) @@ -927,7 +927,7 @@ class TestRecordValidation(TestCase): 'type': 'CAA', 'ttl': 600, 'value': { - 'flags': 1, + 'flags': 128, 'tag': 'iodef', 'value': 'http://foo.bar.com/' } @@ -968,18 +968,6 @@ class TestRecordValidation(TestCase): }) 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 with self.assertRaises(ValidationError) as ctx: Record.new(self.zone, '', {