def update_dns(config, record, sa_version): "Update the DNS record" try: domain = config.get('domain_name', 'sa.baruwa.com.') dns_key = config.get('domain_key') dns_ip = config.get('domain_ip', '127.0.0.1') keyring = tsigkeyring.from_text({domain: dns_key}) transaction = update.Update(domain, keyring=keyring, keyalgorithm=tsig.HMAC_SHA512) txtrecord = '%s.%s' % (sa_version, domain) transaction.replace(txtrecord, 120, 'txt', record) query.tcp(transaction, dns_ip) return True except DNSException, msg: raise SaChannelUpdateDNSError(msg)
def return_response_tcp(name, server): name_message, server_addr = query_data(name, server, "A") response = query.tcp(name_message, server_addr, 2.0) return response
def add_record(self, new_ip, ttl=300): """ Adds an A record for the current instance. """ o_update = update.Update(DOMAIN_NAME, keyring=KEYRING, keyalgorithm=KEYALGORITHM) o_update.add(self.hostname, ttl, 'A', new_ip) try: query.tcp(o_update, NAMESERVER) except Exception as e: logger.error("Attempt to add A record {} to {} failed.".format( new_ip, self.hostname)) logger.debug(e) raise logger.info("A record {} added to {}.".format(new_ip, self.hostname))
def update_dns(config, record, sa_version): "Update the DNS record" try: domain = config.get('domain_name', 'sa.baruwa.com.') dns_key = config.get('domain_key') dns_ip = config.get('domain_ip', '127.0.0.1') keyring = tsigkeyring.from_text({domain: dns_key}) transaction = update.Update( domain, keyring=keyring, keyalgorithm=tsig.HMAC_SHA512) txtrecord = '%s.%s' % (sa_version, domain) transaction.replace(txtrecord, 120, 'txt', record) query.tcp(transaction, dns_ip) return True except DNSException, msg: raise SaChannelUpdateDNSError(msg)
def create_challenge_responses_in_dns(zones, fqdn_challenges): """ Create the expected challenge response in dns @param zones: dict of zones, where each zone has a list of fqdns as values @type zones: dict() @param fqdn_challenges: dict of zones, containing challenge response (key) of zone @type fqdn_challenges: dict() @rtype: None @exceptions Can''t parse ddns key or DNS update failed for zone {} with rcode: {} """ if Misc.LE_ZONE_UPDATE_METHOD == 'zone_file': for zone in zones.keys(): dest = str(Pathes.zone_file_root / zone / Pathes.zone_file_include_name) lines = [] for fqdn in zones[zone]: sld('fqdn: {}'.format(fqdn)) lines.append( str('_acme-challenge.{}. IN TXT \"{}\"\n'.format( fqdn, fqdn_challenges[fqdn].key))) sli('Writing RRs: {}'.format(lines)) with open(dest, 'w') as file: file.writelines(lines) ##os.chmod(file.fileno(), Pathes.zone_tlsa_inc_mode) ##os.chown(file.fileno(), pathes.zone_tlsa_inc_uid, pathes.zone_tlsa_inc_gid) updateZoneCache(zone) updateSOAofUpdatedZones() elif Misc.LE_ZONE_UPDATE_METHOD == 'ddns': txt_datatape = rdatatype.from_text('TXT') for zone in zones.keys(): the_update = ddns_update(zone) for fqdn in zones[zone]: the_update.delete('_acme-challenge.{}.'.format(fqdn), txt_datatape) the_update.add('_acme-challenge.{}.'.format(fqdn), 60, txt_datatape, fqdn_challenges[fqdn].key) sld('DNS update of RR: {}'.format( '_acme-challenge.{}. 60 TXT \"{}\"'.format( fqdn, fqdn_challenges[fqdn].key))) response = dns_query.tcp(the_update, '127.0.0.1', timeout=10) sld('DNS update delete/add returned response: {}'.format(response)) rc = response.rcode() if rc != 0: sle('DNS delete failed for zone {} with rcode: {}:\n{}'.format( zone, rc.to_text(rc), rc)) raise Exception( 'DNS update failed for zone {} with rcode: {}'.format( zone, rc.to_text(rc)))
def _query_ns(self): domain = dns.name.from_text(self.domain) if not domain.is_absolute(): domain = domain.concatenate(dns.name.root) request = dns.message.make_query(domain, dns.rdatatype.ANY) res = query.tcp( request, where='127.0.0.1', port=self.conf.port) print [str(a) for a in res.answer]
def replace_records(self, new_ip, ttl=300): """ Replaces all existing A records for the current instance with a single new one. """ o_update = update.Update(DOMAIN_NAME, keyring=KEYRING, keyalgorithm=KEYALGORITHM) o_update.replace(self.hostname, ttl, 'A', new_ip) try: query.tcp(o_update, NAMESERVER) except Exception as e: logger.error("Attempt to replace A records for {} failed.".format( self.hostname)) logger.debug(e) raise SystemExit logger.info("All A records for {} replaced with {}.".format( self.hostname, new_ip))
def query(self, message, verify=True): try: if self.type == "tcp": response = query.tcp(message, self.host, port=self.port) elif self.type == "udp": response = query.udp(message, self.host, port=self.port) if self.verify(message): return response except: return None
def resolve_name(name): _log.debug("DNS resolve for %s", name) if libnet.is_ipaddr(name): name = reversename.from_address(name) msg = message.make_query(name, "ANY") resp = query.tcp(msg, _config["nameserver"]) dnslines = [] for rr in resp.answer: rrtype = rdatatype.to_text(rr.rdtype) if rrtype in _config["dns_rr_types"]: if rrtype != "TXT": dnslines += ["{} {} {}".format(name, rrtype, str(r) if rrtype != "TXT" else str(r).tolower()) for r in rr] dnslines.sort() result = "\n".join(dnslines) return result
def delete_TLSA(cert_meta: Certificate) -> None: """ Delete all TLSA RRs per fqdn of all altnames either in flatfile (make include file empty) or in dyn dns :param cert_meta: :return: """ if Pathes.tlsa_dns_master == '': # DNS master on local host if Misc.LE_ZONE_UPDATE_METHOD == 'zone_file': for (zone, fqdn) in cert_meta.zone_and_FQDN_from_altnames(): filename = fqdn + '.tlsa' dest = str(Pathes.zone_file_root / zone / filename) #just open for write without writing, which makes file empty with open(dest, 'w') as fd: sli('Truncating {}'.format(dest)) updateZoneCache(zone) elif Misc.LE_ZONE_UPDATE_METHOD == 'ddns': zones = {} for (zone, fqdn) in cert_meta.zone_and_FQDN_from_altnames(): if zone in zones: if fqdn not in zones[zone]: zones[zone].append(fqdn) else: zones[zone] = [fqdn] for zone in zones: the_update = ddns_update(zone) for fqdn in zones[zone]: for prefix in cert_meta.tlsaprefixes.keys(): tag = str(prefix.format(fqdn)).split(maxsplit=1)[0] sld('Deleting TLSA with tag {} an fqdn {} in zone {}'. format(tag, fqdn, zone)) the_update.delete(tag) response = dns_query.tcp(the_update, '127.0.0.1', timeout=10) rc = response.rcode() if rc != 0: sle('DNS update failed for zone {} with rcode: {}:\n{}'. format(zone, response.rcode.to_text(rc), response.rcode)) raise Exception( 'DNS update failed for zone {} with rcode: {}'.format( zone, response.rcode.to_text(rc)))
def delete_challenge_responses_in_dns(zones): """ Delete the challenge response in dns, created by create_challenge_responses_in_dns() @param zones: dict of zones, where each zone has a list of fqdns as values @type zones: dict() @rtype: None @exceptions Can''t parse ddns key or DNS update failed for zone {} with rcode: {} """ if Misc.LE_ZONE_UPDATE_METHOD == 'zone_file': for zone in zones.keys(): dest = str(Pathes.zone_file_root / zone / Pathes.zone_file_include_name) with open(dest, 'w') as file: file.writelines(('', )) updateZoneCache(zone) updateSOAofUpdatedZones() elif Misc.LE_ZONE_UPDATE_METHOD == 'ddns': txt_datatape = rdatatype.from_text('TXT') for zone in zones.keys(): the_update = ddns_update(zone) for fqdn in zones[zone]: the_update.delete('_acme-challenge.{}.'.format(fqdn), txt_datatape) response = dns_query.tcp(the_update, '127.0.0.1', timeout=10) sld('DNS update delete/add returned response: {}'.format(response)) rc = response.rcode() if rc != 0: sle('DNS update failed for zone {} with rcode: {}:\n{}'.format( zone, rc.to_text(rc), rc)) raise Exception( 'DNS update failed for zone {} with rcode: {}'.format( zone, rc.to_text(rc)))
def check_dns(domain, ns): ns_s = filter(len, map(lambda s: s.strip(), ns.replace('"', '').split(';'))) if len(ns_s) < 1: return {} mess = dns.message.make_query(dns_name.from_text(domain), dns.rdatatype.SOA) result = {} for ns in ns_s: try: name_s = dns_name.from_text(ns.split()[0]).to_text() answer = query.tcp(mess, name_s, timeout=2) if len(answer.authority): result[ns] = True else: rr = answer.answer[0][0] if rr.rdtype == dns.rdatatype.SOA: result[ns] = True else: result[ns] = False except: result[ns] = False return result
def dnsck_query( dns_server: str, dns_query: str, record_type: str, iterations: int, tcp: bool = False, nosleep: bool = False, ) -> int: """Perform a DNS query for a set number of iterations. Args: dns_server (str): IP address of server. dns_query (str): Query to lookup. record_type (str): Record type. iterations (int): Number of iterations. tcp (bool): Use TCP for query. nosleep (bool): Disable sleep. Returns: int: Number of errors. """ result_code_dict: DefaultDict[str, int] = defaultdict(int) query_times = [] # type: List[float] record_number = 0 # type: int response_errors = 0 # type: int iteration_count = 0 # type: int try: make_dns_query = message.make_query(dns_query, record_type.upper(), use_edns=True) except rdatatype.UnknownRdatatype: print("Unknown record type, try again.") sys.exit(1) print( f"Performing {iterations} queries to server {dns_server} for domain {dns_query}", f"with record type {record_type.upper()}.\n", ) try: for iteration in range(iterations): print(f"[Query {iteration + 1} of {iterations}]") try: if tcp: dns_response = query.tcp(make_dns_query, dns_server, timeout=10) else: dns_response = query.udp(make_dns_query, dns_server, timeout=10) if dns_response.answer: for answer in dns_response.answer: print(answer) record_number = len(answer) else: print("No records returned.") elapsed_time = dns_response.time * 1000 # type: float if elapsed_time < 500: result_code = rcode.to_text( dns_response.rcode()) # type: str result_code_dict[result_code] += 1 iteration_count += 1 else: result_code = "Degraded" result_code_dict[result_code] += 1 iteration_count += 1 response_errors += 1 except exception.Timeout: print("Query timeout.") result_code = "Timeout" result_code_dict[result_code] += 1 elapsed_time = 10000 iteration_count += 1 response_errors += 1 if not nosleep: time.sleep(1) query_times.append(elapsed_time) print(f"Records returned: {record_number}") print(f"Response time: {elapsed_time:.2f} ms") print(f"Response status: {result_code}\n") except KeyboardInterrupt: print("Program terminating...") print("Response status breakdown:") for query_rcode, count in result_code_dict.items(): print(f"{count} {query_rcode}") print( f"\nSummary: Performed {iteration_count} queries to server {dns_server}", f"for domain {dns_query} with record type {record_type.upper()}.", f"\nResponse errors: {response_errors / iteration_count * 100:.2f}%", ) print( f"Average response time: {sum(query_times) / len(query_times):.2f} ms\n" ) return response_errors
def distribute_tlsa_rrs(cert_meta: Certificate, hashes: Union[Tuple[str], List[str]]) -> None: """ Distribute TLSA RR. Puts TLSA RR fqdn into DNS zone, by dynamic dns or editing zone file and updating zone cache. If cert has altnames, one set of TLSA RRs is inserted per altname and per TLSA prefix. :param cert_meta: :param hashes: list of hashes, may include active and prepublishes hashes for all algos :return: """ if len(cert_meta.tlsaprefixes) == 0: return sli('Distributing TLSA RRs for DANE.') if Pathes.tlsa_dns_master == '': # DNS master on local host if Misc.LE_ZONE_UPDATE_METHOD == 'zone_file': for (zone, fqdn) in zone_and_FQDN_from_altnames(cert_meta): filename = fqdn + '.tlsa' dest = str(Pathes.zone_file_root / zone / filename) sli('{} => {}'.format(filename, dest)) tlsa_lines = [] for prefix in cert_meta.tlsaprefixes.keys(): for hash in hashes: tlsa_lines.append( str(prefix.format(fqdn) + ' ' + hash + '\n')) with open(dest, 'w') as fd: fd.writelines(tlsa_lines) updateZoneCache(zone) elif Misc.LE_ZONE_UPDATE_METHOD == 'ddns': tlsa_datatype = rdatatype.from_text('TLSA') zones = {} for (zone, fqdn) in cert_meta.zone_and_FQDN_from_altnames(): if zone in zones: if fqdn not in zones[zone]: zones[zone].append(fqdn) else: zones[zone] = [fqdn] for zone in zones: the_update = ddns_update(zone) for fqdn in zones[zone]: for prefix in cert_meta.tlsaprefixes.keys(): pf_with_fqdn = str(prefix.format(fqdn)) fields = pf_with_fqdn.split(maxsplit=4) sld('Deleting possible old TLSAs: {}'.format( fields[0])) the_update.delete(fields[0], tlsa_datatype) for hash in hashes: sld('Adding TLSA: {} {} {} {}'.format( fields[0], int(fields[1]), fields[3], fields[4] + ' ' + hash)) the_update.add(fields[0], int(fields[1]), fields[3], fields[4] + ' ' + hash) response = dns_query.tcp(the_update, '127.0.0.1', timeout=10) rc = response.rcode() if rc != 0: sle('DNS update failed for zone {} with rcode: {}:\n{}'. format(zone, response.rcode.to_text(rc), response.rcode)) raise Exception( 'DNS update add failed for zone {} with rcode: {}'. format(zone, response.rcode.to_text(rc))) else: # remote DNS master ( **INCOMPLETE**) sle('Remote DNS master server is currently not supported. Must be on same host as this script.' ) exit(1) with ssh_connection(Pathes.tlsa_dns_master) as client: with client.open_sftp() as sftp: chdir(str(Pathes.work_tlsa)) p = Path('.') sftp.chdir(str(Pathes.zone_file_root)) for child_dir in p.iterdir(): for child in child_dir.iterdir(): sli('{} => {}:{}'.format(child, Pathes.tlsa_dns_master, child)) fat = sftp.put(str(child), str(child), confirm=True) sld('size={}, uid={}, gid={}, mtime={}'.format( fat.st_size, fat.st_uid, fat.st_gid, fat.st_mtime))