Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions domainconnectzone/DomainConnectImpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ def process_custom_record(template_record, zone_records):
'AAAA': ['A', 'AAAA', 'CNAME', 'REDIR301', 'REDIR302'],
'MX': ['MX', 'CNAME'],
'CNAME': ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'REDIR301', 'REDIR302'],
'APEXCNAME': ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'REDIR301', 'REDIR302'],
'REDIR301': ['A', 'AAAA', 'CNAME'],
'REDIR302': ['A', 'AAAA', 'CNAME'],
}
Expand Down Expand Up @@ -604,13 +605,43 @@ def process_other_record(template_record, zone_records):
return new_record


def process_apexcname_record(template_record, zone_records):
"""
Process an APEXCNAME record from a template. APEXCNAME always targets the
zone apex (@) and behaves like a CNAME for conflict resolution purposes.

:param template_record: The record from the template to process.
:type template_record: dict
- keys: 'type', 'pointsTo', 'ttl'; optional 'host' (must be '@' if present)

:param zone_records: A list of all records in the current zone.
:type zone_records: list

:return: The new APEXCNAME record.
:rtype: dict
"""
new_record = {'type': 'APEXCNAME',
'name': '@',
'data': template_record['pointsTo'],
'ttl': int(template_record['ttl'])}

for zone_record in zone_records:
zone_record_type = zone_record['type'].upper()
if zone_record_type in _delete_map['APEXCNAME'] and \
zone_record['name'] == '@' and \
'_replace' not in zone_record:
zone_record['_delete'] = 1

return new_record


def check_conflict_with_self(new_record, new_records):
# Mark records that conflict with self (affects only CNAME and NS)
# Mark records that conflict with self (affects only CNAME, APEXCNAME and NS)
for zone_record in new_records:
zone_record_type = zone_record['type'].upper()

error = False
if (new_record['type'] == 'CNAME' or zone_record['type'] == 'CNAME') \
if (new_record['type'] in ('CNAME', 'APEXCNAME') or zone_record['type'] in ('CNAME', 'APEXCNAME')) \
and zone_record['name'] == new_record['name']:
error = True

Expand All @@ -629,7 +660,7 @@ def check_conflict_with_self(new_record, new_records):
if error:
raise InvalidData(f"Template record {new_record['type']} {new_record['name']} conflicts with other tempate record {zone_record['type']} {zone_record['name']}")

_CORE_TYPES = {'A', 'AAAA', 'CNAME', 'MX', 'NS', 'SRV', 'TXT', 'SPFM',
_CORE_TYPES = {'A', 'AAAA', 'CNAME', 'APEXCNAME', 'MX', 'NS', 'SRV', 'TXT', 'SPFM',
'REDIR301', 'REDIR302'}

# Maps RR type → list of fields whose string values must NOT be lowercased
Expand Down Expand Up @@ -838,7 +869,7 @@ def process_records(template_records, zone_records, domain, host, params,
template_record_type = template_record['type'].upper()

# We can only handle certain record types
supported = ['A', 'AAAA', 'MX', 'CNAME', 'TXT', 'SRV', 'SPFM', 'NS']
supported = ['A', 'AAAA', 'MX', 'CNAME', 'APEXCNAME', 'TXT', 'SRV', 'SPFM', 'NS']
if redirect_records is not None:
supported += ['REDIR301', 'REDIR302']
is_custom = not template_record_type in supported and is_custom_record_type(template_record_type)
Expand All @@ -848,7 +879,7 @@ def process_records(template_records, zone_records, domain, host, params,

# Deal with the variables and validation

# Deal with the host/name
# Deal with the host/name
if template_record_type == 'SRV':
template_record['name'] = resolve_variables(
template_record['name'], domain, host, params, 'name')
Expand All @@ -861,6 +892,14 @@ def process_records(template_records, zone_records, domain, host, params,
raise InvalidData('Invalid data for SRV host: ' +
srvhost)

elif template_record_type == 'APEXCNAME':
# host is optional for APEXCNAME; if present it must be '@'
apex_host = template_record.get('host', '@')
if apex_host != '@':
raise InvalidData('Invalid data for APEXCNAME host: ' +
apex_host + ' (must be @ or omitted)')
template_record['host'] = '@'

else:
orig_host = template_record['host']
template_record['host'] = resolve_variables(
Expand Down Expand Up @@ -889,14 +928,14 @@ def process_records(template_records, zone_records, domain, host, params,
raise InvalidData(err_msg)

# Points To / Target
if template_record_type in ['A', 'AAAA', 'MX', 'CNAME', 'NS']:
if template_record_type in ['A', 'AAAA', 'MX', 'CNAME', 'APEXCNAME', 'NS']:
orig_pointsto = template_record['pointsTo']
if template_record_type == 'NS' and orig_pointsto == '@':
raise InvalidData('Invalid data for NS pointsTo: @ would create a circular delegation')
template_record['pointsTo'] = resolve_variables(
template_record['pointsTo'], domain, host, params, 'pointsTo')

if template_record_type in ['MX', 'CNAME', 'NS']:
if template_record_type in ['MX', 'CNAME', 'APEXCNAME', 'NS']:
if not is_valid_pointsTo_host(
template_record['pointsTo']):
raise InvalidData('Invalid data for ' +
Expand Down Expand Up @@ -1010,6 +1049,8 @@ def process_records(template_records, zone_records, domain, host, params,
new_record = process_srv_record(template_record, zone_records)
elif template_record_type in ['REDIR301', 'REDIR302']:
new_record = process_redir_record(template_record, zone_records)
elif template_record_type == 'APEXCNAME':
new_record = process_apexcname_record(template_record, zone_records)
elif is_custom:
new_record = process_custom_record(template_record, zone_records)
else:
Expand Down
119 changes: 119 additions & 0 deletions test/test_definitions/process_records_apexcname_tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
version: "1.0"
suite_type: process_records
description: "Domain Connect process_records compliance tests — APEXCNAME"

tests:

# ---------------------------------------------------------------------------
# APEXCNAME
# ---------------------------------------------------------------------------

- id: apexcname_basic
description: "APEXCNAME creates a record at the apex (@) with no host field"
input:
zone_records: []
template_records:
- {type: APEXCNAME, pointsTo: target.example.com, ttl: 600}
domain: foo.com
host: ""
params: {}
expect:
new_count: 1
delete_count: 0
records:
- {type: APEXCNAME, name: "@", data: target.example.com, ttl: 600}

- id: apexcname_host_at
description: "APEXCNAME with explicit host '@' is accepted"
input:
zone_records: []
template_records:
- {type: APEXCNAME, host: "@", pointsTo: target.example.com, ttl: 600}
domain: foo.com
host: ""
params: {}
expect:
new_count: 1
delete_count: 0
records:
- {type: APEXCNAME, name: "@", data: target.example.com, ttl: 600}

- id: apexcname_host_non_apex_rejected
description: "APEXCNAME with host other than '@' raises InvalidData"
input:
zone_records: []
template_records:
- {type: APEXCNAME, host: "sub", pointsTo: target.example.com, ttl: 600}
domain: foo.com
host: ""
params: {}
expect:
exception: InvalidData

- id: apexcname_delete_conflicts
description: "APEXCNAME deletes conflicting records at @ (A, AAAA, CNAME, MX, TXT)"
input:
zone_records:
- {type: A, name: "@", data: 1.2.3.4, ttl: 300}
- {type: AAAA, name: "@", data: "::1", ttl: 300}
- {type: CNAME, name: "@", data: old.example.com, ttl: 300}
- {type: MX, name: "@", data: mail.example.com, ttl: 300, priority: 10}
- {type: TXT, name: "@", data: "v=spf1 ~all", ttl: 300}
- {type: NS, name: "@", data: ns1.example.com, ttl: 300}
template_records:
- {type: APEXCNAME, pointsTo: target.example.com, ttl: 600}
domain: foo.com
host: ""
params: {}
expect:
new_count: 1
delete_count: 5
records:
- {type: APEXCNAME, name: "@", data: target.example.com, ttl: 600}
- {type: NS, name: "@", data: ns1.example.com, ttl: 300}

- id: apexcname_no_delete_other_hosts
description: "APEXCNAME only deletes records at @, not at other hosts"
input:
zone_records:
- {type: A, name: "@", data: 1.2.3.4, ttl: 300}
- {type: A, name: "sub", data: 5.6.7.8, ttl: 300}
template_records:
- {type: APEXCNAME, pointsTo: target.example.com, ttl: 600}
domain: foo.com
host: ""
params: {}
expect:
new_count: 1
delete_count: 1
records:
- {type: APEXCNAME, name: "@", data: target.example.com, ttl: 600}
- {type: A, name: "sub", data: 5.6.7.8, ttl: 300}

- id: apexcname_conflict_itself
description: "Two APEXCNAME records in same template raise InvalidData"
input:
zone_records: []
template_records:
- {type: APEXCNAME, pointsTo: target1.example.com, ttl: 600}
- {type: APEXCNAME, pointsTo: target2.example.com, ttl: 600}
domain: foo.com
host: ""
params: {}
expect:
exception: InvalidData

- id: apexcname_variable_substitution
description: "APEXCNAME supports %variable% substitution in pointsTo"
input:
zone_records: []
template_records:
- {type: APEXCNAME, pointsTo: "%target%", ttl: 600}
domain: foo.com
host: ""
params: {target: target.example.com}
expect:
new_count: 1
delete_count: 0
records:
- {type: APEXCNAME, name: "@", data: target.example.com, ttl: 600}
146 changes: 146 additions & 0 deletions test/test_definitions/process_records_redir_tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
version: "1.0"
suite_type: process_records
description: "Domain Connect process_records compliance tests — REDIR301/REDIR302"

tests:

# ---------------------------------------------------------------------------
# REDIR
# ---------------------------------------------------------------------------
- id: redir301_basic
description: "REDIR301 replaces A/AAAA/CNAME at host and adds redirect record"
input:
zone_records:
- {type: A, name: bar, data: abc, ttl: 400}
- {type: AAAA, name: bar, data: abc, ttl: 400}
- {type: CNAME, name: bar, data: abc, ttl: 400}
- {type: A, name: random.value, data: abc, ttl: 400}
template_records:
- {type: REDIR301, host: "@", target: "http://%target%"}
domain: foo.com
host: bar
params: {target: example.com}
redirect_records:
- {type: A, pointsTo: "127.0.0.1", ttl: 600}
- {type: AAAA, pointsTo: "::1", ttl: 600}
expect:
new_count: 3
delete_count: 3
records:
- {type: A, name: bar, data: "127.0.0.1", ttl: 600}
- {type: AAAA, name: bar, data: "::1", ttl: 600}
- {type: A, name: random.value, data: abc, ttl: 400}
- {type: REDIR301, name: bar, data: "http://example.com"}

- id: redir301_double
description: "Two REDIR301 records at different hosts both get redirect records"
input:
zone_records: []
template_records:
- {type: REDIR301, host: www, target: "http://%target%"}
- {type: REDIR301, host: "@", target: "http://www.%fqdn%"}
domain: foo.com
host: ""
params: {target: example.com}
redirect_records:
- {type: A, pointsTo: "127.0.0.1", ttl: 600}
- {type: AAAA, pointsTo: "::1", ttl: 600}
expect:
new_count: 6
delete_count: 0
records:
- {type: A, name: "@", data: "127.0.0.1", ttl: 600}
- {type: AAAA, name: "@", data: "::1", ttl: 600}
- {type: REDIR301, name: "@", data: "http://www.foo.com"}
- {type: A, name: www, data: "127.0.0.1", ttl: 600}
- {type: AAAA, name: www, data: "::1", ttl: 600}
- {type: REDIR301, name: www, data: "http://example.com"}

- id: redir301_with_groupid_filtered_out
description: "REDIR301 filtered out by group_ids does nothing"
input:
zone_records:
- {type: A, name: bar, data: abc, ttl: 400}
- {type: A, name: random.value, data: abc, ttl: 400}
template_records:
- {type: REDIR301, host: "@", target: "http://example.com", groupId: b}
domain: foo.com
host: bar
params: {}
group_ids: [a]
redirect_records:
- {type: A, pointsTo: "127.0.0.1", ttl: 600}
- {type: AAAA, pointsTo: "::1", ttl: 600}
expect:
new_count: 0
delete_count: 0
records:
- {type: A, name: bar, data: abc, ttl: 400}
- {type: A, name: random.value, data: abc, ttl: 400}

- id: redir302_basic
description: "REDIR302 replaces A/AAAA/CNAME at host and adds redirect record"
input:
zone_records:
- {type: A, name: bar, data: abc, ttl: 400}
- {type: AAAA, name: bar, data: abc, ttl: 400}
- {type: CNAME, name: bar, data: abc, ttl: 400}
- {type: A, name: random.value, data: abc, ttl: 400}
template_records:
- {type: REDIR302, host: "@", target: "http://example.com"}
domain: foo.com
host: bar
params: {}
redirect_records:
- {type: A, pointsTo: "127.0.0.1", ttl: 600}
- {type: AAAA, pointsTo: "::1", ttl: 600}
expect:
new_count: 3
delete_count: 3
records:
- {type: A, name: bar, data: "127.0.0.1", ttl: 600}
- {type: AAAA, name: bar, data: "::1", ttl: 600}
- {type: A, name: random.value, data: abc, ttl: 400}
- {type: REDIR302, name: bar, data: "http://example.com"}

- id: exception_redir301_empty_target
description: "REDIR301 with empty target raises InvalidData"
input:
zone_records: []
template_records:
- {type: REDIR301, host: "@", target: "", ttl: 600}
domain: foo.com
host: ""
params: {}
redirect_records:
- {type: A, pointsTo: "127.0.0.1", ttl: 600}
- {type: AAAA, pointsTo: "::1", ttl: 600}
expect:
exception: InvalidData

- id: exception_redir302_invalid_target
description: "REDIR302 with an invalid URL target raises InvalidData"
input:
zone_records: []
template_records:
- {type: REDIR302, host: "@", target: "http://ijfjiör@@@a:43244434::", ttl: 600}
domain: foo.com
host: ""
params: {}
redirect_records:
- {type: A, pointsTo: "127.0.0.1", ttl: 600}
- {type: AAAA, pointsTo: "::1", ttl: 600}
expect:
exception: InvalidData

- id: exception_redir301_missing_redirect_records
description: "REDIR301 without redirect_records raises InvalidTemplate"
input:
zone_records: []
template_records:
- {type: REDIR301, host: "@", target: "", ttl: 600}
domain: foo.com
host: ""
params: {}
expect:
exception: InvalidTemplate
Loading
Loading