#!/usr/bin/env python3 from configparser import ConfigParser import socket import datetime import netaddr from re2oapi import Re2oAPIClient config = ConfigParser() config.read('config.ini') api_hostname = config.get('Re2o', 'hostname') api_password = config.get('Re2o', 'password') api_username = config.get('Re2o', 'username') template_soa = ("{zone} IN SOA ns.{zone}. {mail} (\n" " {serial} ; serial\n" " {refresh} ; refresh\n" " {retry} ; retry\n" " {expire} ; expire\n" " {ttl} ; ttl\n" ")") template_originv4 = "@ IN A {ipv4}" template_originv6 = "@ IN AAAA {ipv6}" template_ns = "@ IN NS {target}" template_mx = "@ IN MX {priority} {target}" template_txt = "{field1} IN TXT {field2}" template_srv = "_{service}._{protocol}.{zone} {ttl} IN SRV {priority} {weight} {port} {target}" template_a = "{hostname} IN A {ipv4}" template_aaaa = "{hostname} IN AAAA {ipv6}" template_cname = "{hostname} IN CNAME {alias}" template_ptr = "{target} IN PTR {hostname}" template_zone = ("$TTL 2D\n" "{soa}\n" "\n" "{originv4}\n" "{originv6}\n" "\n" "{ns_records}\n" "\n" "{mx_records}\n" "\n" "{txt_records}\n" "\n" "{srv_records}\n" "\n" "{a_records}\n" "\n" "{aaaa_records}\n" "\n" "{cname_records}") template_reverse = ("$TTL 2D\n" "{soa}\n" "\n" "{ns_records}\n" "\n" "{mx_records}\n" "\n" "{ptr_records}\n") def write_dns_files(api_client): for zone in api_client.list("dns/zones"): zone_name = zone['name'][1:] now = datetime.datetime.now(datetime.timezone.utc) serial = now.strftime("%Y%m%d") + str(int(100*(now.hour*3600 + now.minute*60 + now.second)/86400)) soa_mail_fields = zone['soa']['mail'].split('@') soa_mail = "{}.{}.".format(soa_mail_fields[0].replace('.', '\\.'), soa_mail_fields[1]) soa = template_soa.format(zone=zone_name, mail=soa_mail, serial=serial, refresh=zone['soa']['refresh'], retry=zone['soa']['retry'], expire=zone['soa']['expire'], ttl=zone['soa']['ttl']) if zone['originv4'] is not None: originv4 = template_originv4.format(ipv4=zone['originv4']['ipv4']) else: originv4 = "" if zone['originv6'] is not None: originv6 = template_originv6.format(ipv6=zone['originv6']) else: originv6 = "" ns_records = "\n".join( template_ns.format(target=x['target']) for x in zone['ns_records'] ) mx_records = "\n".join( template_mx.format(priority=x['priority'], target=x['target']) for x in zone['mx_records'] ) txt_records = "\n".join( template_txt.format(field1=x['field1'], field2=x['field2']) for x in zone['txt_records'] ) srv_records = "\n".join( template_srv.format(service=x['service'], protocol=x['protocol'], zone=zone_name, ttl=x['ttl'], priority=x['priority'], weight=x['weight'], port=x['port'], target=x['target']) for x in zone['srv_records'] ) a_records = "\n".join( template_a.format(hostname=x['hostname'], ipv4=x['ipv4']) for x in zone['a_records'] ) aaaa_records = "\n".join( template_aaaa.format(hostname=x['hostname'], ipv6=ip['ipv6']) for x in zone['aaaa_records'] for ip in x['ipv6'] if x['ipv6'] is not None ) cname_records = "\n".join( template_cname.format(hostname=x['hostname'], alias=x['alias'],extension=x['extension']) for x in zone['cname_records'] ) zone_file_content = template_zone.format(soa=soa, originv4=originv4, originv6=originv6, ns_records=ns_records, mx_records=mx_records, txt_records=txt_records, srv_records=srv_records, a_records=a_records, aaaa_records=aaaa_records, cname_records=cname_records) filename = 'dns.{zone}.zone'.format(zone=zone_name) with open(filename, 'w+') as f: f.write(zone_file_content) def network_to_arpanets(nets): """En ipv4, on ne pouvait définir que des plages reverse en /24, /16 ou /8. Cette fonction vise à retourner une liste des plages en tenant compte de ce critère (donc de taille 32/24/16/8) Ne touche à rien pour l'IPv6. """ subnets = [] for net in nets: if not isinstance(net, netaddr.IPNetwork): net = netaddr.IPNetwork(net) # on fragmente les subnets # dans les tailles qui vont bien. if net.prefixlen > 24: subnets.extend(net.subnet(32)) elif net.prefixlen > 16: subnets.extend(net.subnet(24)) elif net.prefixlen > 8: subnets.extend(net.subnet(16)) else: subnets.extend(net.subnet(8)) return subnets def get_arpa(cidr): """ Renvoie la zone DNS inverse correspondant au réseau donné. """ net = netaddr.IPNetwork(cidr) byte_size = 8 if net.version == 4 else 4 addr_bytes = 4 if net.version == 4 else 32 net_class = max(((net.prefixlen - 1) // byte_size) + 1, 1) return ".".join( netaddr.IPAddress(net.first).reverse_dns.split('.')[addr_bytes - net_class:] ) def get_ip_reverse(ip, prefix_length): ip = netaddr.IPAddress(ip) return '.'.join(ip.reverse_dns.split('.')[:prefix_length]) def write_dns_reverse_file(api_client): for zone in api_client.list("dns/reverse-zones"): now = datetime.datetime.now(datetime.timezone.utc) serial = now.strftime("%Y%m%d") + str(int(100*(now.hour*3600 + now.minute*60 + now.second)/86400)) extension = zone['extension'] soa_mail_fields = zone['soa']['mail'].split('@') soa_mail = "{}.{}.".format(soa_mail_fields[0].replace('.', '\\.'), soa_mail_fields[1]) ns_records = "\n".join( template_ns.format(target=x['target']) for x in zone['ns_records'] ) mx_records = "\n".join( template_mx.format(priority=x['priority'], target=x['target']) for x in zone['mx_records'] ) ### Start with ipv4 reverse net = network_to_arpanets(zone['cidrs'])[0] # Prend la première adresse ip de la plage, sauf si une est fournie _address = netaddr.IPAddress(net.first) # retourne le reverse splitté. (un reverse ressemble à 0.136.231.138.in-addr.arpa.) rev_dns_a = _address.reverse_dns.split('.')[:-1] # En v4, le reverse étant de la forme 0.136.231.138.in-addr.arpa., soit # on a un /8, soit un /16, soit un /24. if net.prefixlen == 8: zone_name,prefix_length = ('.'.join(rev_dns_a[3:]), 3) elif net.prefixlen == 16: zone_name,prefix_length = ('.'.join(rev_dns_a[2:]), 2) elif net.prefixlen == 24: zone_name,prefix_length = ('.'.join(rev_dns_a[1:]), 1) soa = template_soa.format(zone=zone_name, mail=soa_mail, serial=serial, refresh=zone['soa']['refresh'], retry=zone['soa']['retry'], expire=zone['soa']['expire'], ttl=zone['soa']['ttl']) ptr_records = "\n".join( template_ptr.format(hostname=host['hostname']+extension, target=get_ip_reverse(host['ipv4'],prefix_length)) for host in zone['ptr_records'] if host['ipv4'] ) zone_file_content = template_reverse.format(soa=soa, ns_records=ns_records, mx_records=mx_records, ptr_records = ptr_records) filename = 'dns.{zone}zone'.format(zone=zone_name) with open(filename, 'w+') as f: f.write(zone_file_content) ### Continue with the ipv6 reverse # hack because we do not have the /64 info zone6_name = get_arpa(zone['prefix_v6']+"/64") soa = template_soa.format(zone=zone6_name, mail=soa_mail, serial=serial, refresh=zone['soa']['refresh'], retry=zone['soa']['retry'], expire=zone['soa']['expire'], ttl=zone['soa']['ttl']) net = netaddr.IPNetwork(zone['prefix_v6']+"/64") prefix_length = int((128 - net.prefixlen)/4) ptr_records = "\n".join( template_ptr.format(hostname=host['hostname']+extension, target=get_ip_reverse(ip['ipv6'],prefix_length)) for host in zone['ptr_v6_records'] for ip in host['ipv6'] ) zone_file_content = template_reverse.format(soa=soa, ns_records=ns_records, mx_records=mx_records, ptr_records = ptr_records) filename = 'dns.{zone}zone'.format(zone=zone6_name) with open(filename, 'w+') as f: f.write(zone_file_content) api_client = Re2oAPIClient(api_hostname, api_username, api_password) client_hostname = socket.gethostname().split('.', 1)[0] for service in api_client.list("services/regen/"): # if service['hostname'] == client_hostname and \ # service['service_name'] == 'dns' and \ # service['need_regen']: #write_dns_files(api_client) write_dns_reverse_file(api_client) # api_client.patch(service['api_url'], data={'need_regen': False})