def add_zonemd(zone, zonemd_algorithm='sha384', zonemd_ttl=None): """ Add a ZONEMD record to a zone. This also removes any existing ZONEMD records in the zone. The ZONEMD record will be at the zone apex, and have an all-zero digest. If the TTL is not specified, then the TTL of the SOA record is used. @var zone: The zone object to update. @type zone: dns.zone.Zone @var zonemd_algorithm: The name of the algorithm to use, "sha384", or the number of the algorithm to use. @type zonemd_algorithm: str @var zonemd_ttl: The TTL to use for the ZONEMD record, or None to get this from the zone SOA. @type zonemd_ttl: int @rtype: dns.rdataset.Rdataset @raises ZoneDigestUnknownAlgorithm: zonemd_algorithm is unknown Returns the placeholder ZONEMD record added, as a ZONEMD object. """ if zonemd_algorithm in ('sha384', 1): algorithm = 1 else: msg = 'Unknown digest ' + zonemd_algorithm raise ZoneDigestUnknownAlgorithm(msg) empty_digest = _EMPTY_DIGEST_BY_ALGORITHM[algorithm] # Remove any existing ZONEMD from the zone. # Also find the first name, which will be the zone name. for name in zone: zone.delete_rdataset(name, ZONEMD_RTYPE) zone_name = min(zone.keys()) # Get the zone name. zone_name = min(zone.keys()) # Get the SOA. soa_rdataset = zone.get_rdataset(zone_name, dns.rdatatype.SOA) soa = soa_rdataset.items[0] # Get the TTL to use for our placeholder ZONEMD. if zonemd_ttl is None: zonemd_ttl = soa_rdataset.ttl # Build placeholder ZONEMD and add to the zone. placeholder = dns.rdataset.Rdataset(dns.rdataclass.IN, ZONEMD_RTYPE) placeholder.update_ttl(zonemd_ttl) placeholder_rdata = ZONEMD(dns.rdataclass.IN, soa.serial, algorithm, empty_digest) placeholder.add(placeholder_rdata) zone.replace_rdataset(zone_name, placeholder) return placeholder_rdata
def update_zonemd(zone, zonemd_algorithm='sha384'): """ Calculate the digest of the zone and update the ZONEMD record's digest value with that. The ZONEMD record must already be present, for example having been added by the add_zonemd() function. This function does *not* change the serial value of the ZONEMD record. @var zone: The zone object to update. @type zone: dns.zone.Zone @var zonemd_algorithm: The name of the algorithm to use, "sha384". @type zonemd_algorithm: str @rtype: dns.rdataset.Rdataset @raises ZoneDigestUnknownAlgorithm: zonemd_algorithm is unknown Returns the ZONEMD record added, as a ZONEMD object. """ zone_name = min(zone.keys()) digest = calculate_zonemd(zone, zonemd_algorithm) zonemd = zone.find_rdataset(zone_name, ZONEMD_RTYPE).items[0] zonemd.digest = digest return zonemd
def get_all_records( dnsServer, zoneName ): zone = dns.zone.from_xfr(dns.query.xfr(dnsServer, zoneName)) allKeys = zone.keys() result = [] for eachKey in allKeys: record_str = zone[eachKey].to_text(eachKey) #print '\n\n', record_str, '\n\n' for rstr in record_str.split('\n'): record_list = rstr.split(' ') record_dict = {} record_dict['ttl'] = record_list[1] record_dict['class'] = record_list[2] record_dict['type'] = record_list[3] if record_dict['type'] == 'SOA': record_dict['name'] = zoneName record_dict['server'] = record_list[4] record_dict['admin'] = record_list[5] record_dict['serial'] = record_list[6] record_dict['refresh'] = record_list[7] record_dict['retry'] = record_list[8] record_dict['expire'] = record_list[9] record_dict['minimum'] = record_list[10] else: record_dict['name'] = record_list[0] record_dict['value'] = record_list[4] result.append(record_dict) #return Response(json.dumps(result, separators=(',',':'), encoding='utf-8')) return result
def validate_zonemd(zone): """ Validate the digest of the zone. @var zone: The zone object to validate. @type zone: dns.zone.Zone @rtype: (bool, str) tuple Returns a tuple of (success code, error message). The success code is True if the digest is correct, and False otherwise. The error message is "" if there is no error, otherwise a description of the problem. """ # Get the SOA and ZONEMD records for the zone. zone_name = min(zone.keys()) soa_rdataset = zone.get_rdataset(zone_name, dns.rdatatype.SOA) soa = soa_rdataset.items[0] # zonemd = zone.find_rdataset(zone_name, ZONEMD_RTYPE).items[0] original_digests = {} for zonemd in zone.find_rdataset(zone_name, ZONEMD_RTYPE).items: # Verify that the SOA matches between the SOA and the ZONEMD. if soa.serial != zonemd.serial: err = ("SOA serial " + str(soa.serial) + " does not " + "match ZONEMD serial " + str(zonemd.serial)) return False, err # Save the original digest. if zonemd.algorithm in original_digests: err = ("Digest algorithm " + str(zonemd.algorithm) + "used more than once") return False, err original_digests[zonemd.algorithm] = zonemd.digest # Put a placeholder in for the ZONEMD. if zonemd.algorithm in _EMPTY_DIGEST_BY_ALGORITHM: zonemd.digest = _EMPTY_DIGEST_BY_ALGORITHM[zonemd.algorithm] else: zonemd.digest = b'\0' * len(zonemd.digest) # Calculate the digest. digest = calculate_zonemd(zone) # Restore ZONEMD. for zonemd in zone.find_rdataset(zone_name, ZONEMD_RTYPE).items: zonemd.digest = original_digests[zonemd.algorithm] # Verify the digest in the zone matches the calculated value. if digest != original_digests[ZONEMD_DIGEST_SHA384]: zonemd_b2a = binascii.b2a_hex(original_digest[ZONEMD_DIGEST_SHA384]) zonemd_hex = zonemd_b2a.decode() digest_hex = binascii.b2a_hex(digest).decode() err = ("ZONEMD digest " + zonemd_hex + " does not " + "match calculated digest " + digest_hex) return False, err # Everything matches, enjoy your zone. return True, ""
def calculate_zonemd(zone, zonemd_algorithm='sha384'): """ Calculate the digest of the zone. Returns the digest for the zone. @var zone: The zone object to digest. @type zone: dns.zone.Zone @var zonemd_algorithm: The name of the algorithm to use, either "sha384", or the number of the algorithm to use. @type zonemd_algorithm: str @raises ZoneDigestUnknownAlgorithm: zonemd_algorithm is unknown @rtype: bytes """ if zonemd_algorithm in ('sha384', ZONEMD_DIGEST_SHA384): hashing = hashlib.sha384() # Sort the names in the zone. This is needed for canonization. sorted_names = sorted(zone.keys()) # Iterate across each name in canonical order. for name in sorted_names: # Save the wire format of the name for later use. wire_name = name.canonicalize().to_wire() # Iterate across each RRSET in canonical order. sorted_rdatasets = sorted(zone.find_node(name).rdatasets, key=lambda rdataset: rdataset.rdtype) for rdataset in sorted_rdatasets: # Skip the RRSIG for ZONEMD. if rdataset.rdtype == dns.rdatatype.RRSIG: if rdataset.covers == ZONEMD_RTYPE: continue # Save the wire format of the type, class, and TTL for later use. wire_set = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass, rdataset.ttl) # Extract the wire format of the RDATA and sort them. wire_rdatas = [] for rdata in rdataset: wire_rdatas.append(rdata.to_digestable()) wire_rdatas.sort() # Finally update the digest for each RR. for wire_rr in wire_rdatas: hashing.update(wire_name) hashing.update(wire_set) hashing.update(struct.pack('!H', len(wire_rr))) hashing.update(wire_rr) return hashing.digest()
def validate_zonemd(zone): """ Validate the digest of the zone. @var zone: The zone object to validate. @type zone: dns.zone.Zone @rtype: (bool, str) tuple Returns a tuple of (success code, error message). The success code is True if the digest is correct, and False otherwise. The error message is "" if there is no error, otherwise a description of the problem. """ # Get the SOA and ZONEMD records for the zone. zone_name = min(zone.keys()) soa_rdataset = zone.get_rdataset(zone_name, dns.rdatatype.SOA) soa = soa_rdataset.items[0] zonemd = zone.find_rdataset(zone_name, ZONEMD_RTYPE).items[0] # Verify that the SOA matches between the SOA and the ZONEMD. if soa.serial != zonemd.serial: err = ("SOA serial " + str(soa.serial) + " does not " + "match ZONEMD serial " + str(zonemd.serial)) return False, err # Verify that we understand the digest algorithm. if zonemd.algorithm not in _EMPTY_DIGEST_BY_ALGORITHM: err = "Unknown digest algorithm " + str(zonemd.algorithm) return False, err # Put a placeholder in for the ZONEMD. original_digest = zonemd.digest zonemd.digest = _EMPTY_DIGEST_BY_ALGORITHM[zonemd.algorithm] # Calculate the digest and restore ZONEMD. digest = calculate_zonemd(zone, zonemd.algorithm) zonemd.digest = original_digest # Verify the digest in the zone matches the calculated value. if digest != zonemd.digest: zonemd_hex = binascii.b2a_hex(zonemd.digest).decode() digest_hex = binascii.b2a_hex(digest).decode() err = ("ZONEMD digest " + zonemd_hex + " does not " + "match calculated digest " + digest_hex) return False, err # Everything matches, enjoy your zone. return True, ""
def sync_zone(inwx_conn, origin, zone): print(origin) dnsorigin = dns.name.from_text(origin, origin=dns.name.empty) # create zone checkRet = inwx_conn.nameserver.list({'domain': origin}) if not checkRet['resData']['domains']: print(" + Creating a new zone for %s" % origin) inwx_conn.nameserver.create({'domain': origin, 'type': 'MASTER', 'ns': NS}) apizone = inwx_conn.nameserver.info({'domain': origin})['resData']['record'] # remove old entries from inwx nameserver for record in apizone: name = '@' if record['name'] == origin else record['name'].rsplit(".%s" % origin, 1)[0] if record['type'] == 'NS' and name == '@': # do not touch NS records on root of zone continue elif not dns.name.from_text(name, origin=dns.name.empty) in zone: print(" + Deleting record from %s (%r)" % (origin, record)) inwx_conn.nameserver.deleteRecord({'id': record['id']}) continue elif record['type'] not in ['A', 'AAAA', 'MX', 'SRV', 'TXT', 'CNAME', 'NS', 'PTR', 'SSHFP']: continue found = False for key in zone.keys(): for dataset in zone[key].rdatasets: if dataset.ttl != record['ttl']: continue if dataset.rdtype in [dns.rdatatype.SOA]: continue for item in dataset.items: tmprecord = dns_item_to_record(dataset, item, dnsorigin, key) found = True for reckey in tmprecord: if tmprecord[reckey] != record[reckey]: found = False break if found: break if found: break if found: break if not found: print(" + Deleting record from %s (%r)" % (origin, record)) inwx_conn.nameserver.deleteRecord({'id': record['id']}) # create new entries from zonefile for key in zone.keys(): for dataset in zone[key].rdatasets: for item in dataset.items: if dataset.rdtype in [dns.rdatatype.SOA]: continue # do not touch nameservers on root of zone if key.to_text() == '@' and dataset.rdtype == dns.rdatatype.NS: continue found = False tmprecord = dns_item_to_record(dataset, item, dnsorigin, key) for record in apizone: found = True for reckey in tmprecord: if tmprecord[reckey] != record[reckey]: found = False break if found: break if not found: print(" + Creating record on %s (%r)" % (origin, tmprecord)) tmprecord['domain'] = origin inwx_conn.nameserver.createRecord(tmprecord) # update soa apizonesoa = list(record for record in apizone if record['name'] == origin and record['type'] == 'SOA')[0] split_apizonesoa = apizonesoa['content'].split() zonesoa_rname = dns_name_to_text(list(dataset for dataset in zone['@'].rdatasets if dataset.rdtype == dns.rdatatype.SOA)[0].items[0].rname, dnsorigin) if split_apizonesoa[0] != NS[0] or split_apizonesoa[1] != zonesoa_rname: apizonesoa['content'] = "%s %s %s" % (NS[0], zonesoa_rname, split_apizonesoa[2]) print(" + Updating SOA record on %s (%r)" % (origin, apizonesoa)) inwx_conn.nameserver.updateRecord(apizonesoa)
def dump_xml(self, zone, exclude=None): re_awsalias = re.compile(r'^AWSALIAS') # preprocess; this is annoying but necessary to support our little # TXT record shim: doing it inside dnspython is just painful rr_data = {} for rrname in zone.keys(): rr_name = rrname.derelativize(zone.origin).to_text() rr_data[rr_name] = {} for rdataset in zone[rrname].rdatasets: rr_type = dns.rdatatype.to_text(rdataset.rdtype) rr_data[rr_name][rr_type] = {} rr_data[rr_name][rr_type]['TTL'] = str(rdataset.ttl) rr_data[rr_name][rr_type]['RRS'] = [] for rdtype in rdataset.items: rr_data[rr_name][rr_type]['RRS'].append(rdtype.to_text(origin=zone.origin, relativize=False)) # now deal with the ugliness of aws alias records for rr_name in rr_data: # first, convert any AWSALIAS txt records into A records if 'TXT' in rr_data[rr_name]: rr_vals_to_delete = [] for rr_value in rr_data[rr_name]['TXT']['RRS']: if re_awsalias.search(unquote(rr_value)): (_, hosted_zone_id, dns_name) = unquote(rr_value).split(':') # remove the awsalias from the TXT record set rr_vals_to_delete.append(rr_value) # add as an A record with an alias target if 'A' not in rr_data[rr_name]: rr_data[rr_name]['A'] = {} rr_data[rr_name]['A']['AliasTarget'] = {} rr_data[rr_name]['A']['AliasTarget']['HostedZoneId'] = hosted_zone_id rr_data[rr_name]['A']['AliasTarget']['DNSName'] = dns_name for rr_value in rr_vals_to_delete: del(rr_data[rr_name]['TXT']['RRS'][ rr_data[rr_name]['TXT']['RRS'].index(rr_value)]) # if we've emptied the TXT set, delete it if not rr_data[rr_name]['TXT']['RRS']: del rr_data[rr_name]['TXT'] # now make sure there's no existing A record for that RR if 'A' in rr_data[rr_name]: if 'RRS' in rr_data[rr_name]['A'] and 'AliasTarget' in rr_data[rr_name]['A']: raise ValueError( 'You cannot have both a static A record and an AWSALIAS' ' at the same RR node: %s' % rr_name) # now spit it all back out as XML resource_record_sets = et.Element('ResourceRecordSets', xmlns=boto.route53.Route53Connection.XMLNameSpace) for rr_name in rr_data: for rr_type in rr_data[rr_name]: resource_record_set = et.SubElement(resource_record_sets, 'ResourceRecordSet') text_element(resource_record_set, 'Name', rr_name) text_element(resource_record_set, 'Type', rr_type) if 'AliasTarget' in rr_data[rr_name][rr_type]: alias_target = et.SubElement(resource_record_set, 'AliasTarget') text_element(alias_target, 'HostedZoneId', rr_data[rr_name][rr_type]['AliasTarget']['HostedZoneId']) text_element(alias_target, 'DNSName', rr_data[rr_name][rr_type]['AliasTarget']['DNSName']) else: text_element(resource_record_set, 'TTL', rr_data[rr_name][rr_type]['TTL']) resource_records = et.SubElement(resource_record_set, 'ResourceRecords') for rr_value in rr_data[rr_name][rr_type]['RRS']: resource_record = et.SubElement(resource_records, 'ResourceRecord') text_element(resource_record, 'Value', rr_value) out = StringIO() et.ElementTree(resource_record_sets).write(out) return out.getvalue()