From 3177547e7c647e94f83583341c8cc46273858ffc Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Fri, 31 Jan 2014 03:04:36 +0100 Subject: [PATCH] =?UTF-8?q?[dns]=20Utilisation=20de=20bind2=20(avec=20lc?= =?UTF-8?q?=5Fldap)=20pour=20g=C3=A9n=C3=A9rer=20le=20dns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Et du coup, on met de l'ipv6 aux nom de domaines par defaut --- gestion/config/dns.py | 4 + gestion/gen_confs/bind.py | 2 +- gestion/gen_confs/bind2.py | 280 +++++++++++++++++++++++++++++----- gestion/gen_confs/generate.py | 4 +- 4 files changed, 251 insertions(+), 39 deletions(-) diff --git a/gestion/config/dns.py b/gestion/config/dns.py index 0d227dbe..71b78c09 100644 --- a/gestion/config/dns.py +++ b/gestion/config/dns.py @@ -29,7 +29,11 @@ zone_tv = 'tv.crans.org' #: DNS en connexion de secours secours_relay='10.231.136.14'; +#: Serveurs authoritaires pour les zones crans, le master doit être le premier +DNSs = ['sable.crans.org', 'freebox.crans.org', 'ovh.crans.org'] + #: Résolution DNS directe +zones_ldap = [ 'crans.org', 'crans.ens-cachan.fr', 'wifi.crans.org', 'ferme.crans.org' , 'clubs.ens-cachan.fr', 'adm.crans.org', 'tv.crans.org' ] zones_direct = [ 'crans.org', 'crans.ens-cachan.fr', 'wifi.crans.org', 'ferme.crans.org' , 'clubs.ens-cachan.fr', 'adm.crans.org','crans.eu','wifi.crans.eu', 'tv.crans.org', 'ap.crans.org' ] #: Zones signée par opendnssec sur le serveur master zones_dnssec = [ diff --git a/gestion/gen_confs/bind.py b/gestion/gen_confs/bind.py index d3be53dd..fbee2244 100755 --- a/gestion/gen_confs/bind.py +++ b/gestion/gen_confs/bind.py @@ -117,7 +117,7 @@ la base LDAP } ### Liste DNS # Le premier doit être le maitre - DNSs = ['sable.crans.org', 'freebox.crans.org', 'ovh.crans.org'] + DNSs = config.dns.DNSs DNSs_private = [] ip_master_DNS = config.dns.master diff --git a/gestion/gen_confs/bind2.py b/gestion/gen_confs/bind2.py index e1e6b337..4b67d28f 100644 --- a/gestion/gen_confs/bind2.py +++ b/gestion/gen_confs/bind2.py @@ -2,12 +2,39 @@ # -*- coding: utf-8 -*- import sys +import time import base64 import hashlib import netaddr sys.path.append('/usr/scripts/') +import lc_ldap.shortcuts +from gestion.gen_confs import gen_config +from socket import gethostname from gestion import config +import config.dns + +def short_name(fullhostname): + return fullhostname.split(".")[0] + +def reverse(net, ip): + """Renvoie la zone DNS inverse correspondant au réseau et à + l'adresse donnés, ainsi que le nombre d'éléments de l'ip a + mettre dans le fichier de zone.""" + n = netaddr.IPNetwork(net) + a = netaddr.IPAddress(ip) + rev_dns_a = a.reverse_dns.split('.')[:-1] + assert a in n + if n.version == 4: + if n.prefixlen == 8: + return ('.'.join(rev_dns_a[3:]), 3) + elif n.prefixlen == 16: + return ('.'.join(rev_dns_a[2:]), 2) + else: + return ('.'.join(rev_dns_a[1:]), 1) + elif n.version == 6: + return ('.'.join(rev_dns_a[(128-n.prefixlen)/4:]), (128-n.prefixlen)/4) + class ResourceRecord(object): def __init__(self, type, name, value, ttl=None): @@ -26,10 +53,13 @@ class ResourceRecord(object): class SOA(ResourceRecord): def __init__(self, master, email, serial, refresh, retry, expire, ttl): - super(SOA, self).__init__('SOA', '', '%s. %s. (\n %s ; numero de serie\n %s ; refresh (s)\n %s ; retry (s)\n %s ; expire (s)\n %s ; TTL (s)\n )' % (master, email, serial, refresh, retry, expire, ttl)) + super(SOA, self).__init__('SOA', '@', '%s. %s. (\n %s ; numero de serie\n %s ; refresh (s)\n %s ; retry (s)\n %s ; expire (s)\n %s ; TTL (s)\n )' % (master, email, serial, refresh, retry, expire, ttl)) class A(ResourceRecord): def __init__(self, name, value, ttl=None): super(A, self).__init__('A', name, value, ttl) +class DS(ResourceRecord): + def __init__(self, name, value, ttl=None): + super(DS, self).__init__('DS', name, value, ttl) class PTR(ResourceRecord): def __init__(self, name, value, ttl=None): super(PTR, self).__init__('PTR', name, value, ttl) @@ -55,10 +85,10 @@ class SPF(ResourceRecord): def __init__(self, name, value, ttl=None): super(SPF, self).__init__('SPF', name, value, ttl) class SRV(ResourceRecord): - def __init__(self, service, proto, name, priority, weight, port, target, ttl=None): - super(SRV, self).__init__('SRV', '_%s._%s.%s' % (service, proto, name), '%s\t%s\t%s\t%s' % (priority, weight, port, target), ttl) + def __init__(self, service, proto, priority, weight, port, target, ttl=None): + super(SRV, self).__init__('SRV', '_%s._%s' % (service, proto), '%s\t%s\t%s\t%s' % (priority, weight, port, target), ttl) class NAPTR(ResourceRecord): - def __init__(self, order, preference, flag, service, replace_regexpr, value, ttl=None): + def __init__(self, name, order, preference, flag, service, replace_regexpr, value, ttl=None): super(NAPTR, self).__init__('NAPTR', name, '%s\t%s\t"%s"\t"%s"\t"%s"\t%s' % (order, preference, flag, service, replace_regexpr, value), ttl) class SSHFP(ResourceRecord): def __init__(self, name, hash, algo, key, ttl=None): @@ -69,9 +99,13 @@ class SSHFP(ResourceRecord): super(SSHFP, self).__init__('SSHFP', name, '%s\t%s\t%s' % (config.sshfp_algo[algo][0], config.sshfp_hash[hash], getattr(hashlib, hash)(base64.b64decode(key)).hexdigest()), ttl) class ZoneBase(object): - def __init__(self): + def __init__(self, zone_name): self._rrlist=[] + self.zone_name = zone_name + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, self.zone_name) def __str__(self): ret=""";*********************************************************** ; Ce fichier est genere par les scripts de gen_confs @@ -92,14 +126,23 @@ $TTL %s def add(self, rr): if isinstance(rr, ResourceRecord): self._rrlist.append(rr) + else: + raise ValueError("You can only add ResourceRecords to a Zone") + def extend(self, rr_list): + for rr in rr_list: + self.add(rr) + + def write(self, path): + with open(path, 'w') as f: + f.write("%s" % self) class ZoneClone(ZoneBase): def __init__(self, zone_name, zone_clone, soa): - super(ZoneClone, self).__init__() - self.zone_name = zone_name + super(ZoneClone, self).__init__(zone_name) self.zone_clone = zone_clone + self.ttl = zone_clone.ttl self.add(soa) self.add(DNAME('', "%s." % self.zone_clone.zone_name)) @@ -111,12 +154,12 @@ class ZoneClone(ZoneBase): class Zone(ZoneBase): - def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True): - super(Zone, self).__init__() - self.zone_name = zone_name + def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, bl_zone=[]): + super(Zone, self).__init__(zone_name) self.ttl = ttl - self.ipv4=ipv4 - self.ipv6=ipv6 + self.ipv4 = ipv4 + self.ipv6 = ipv6 + self.bl_zone = bl_zone self.add(soa) for ns in ns_list: @@ -124,14 +167,15 @@ class Zone(ZoneBase): def get_name(self, hostname): - if str(hostname).endswith(self.zone_name): + # le hostname fini bien par la zone courante, et il n'appartient pas à une sous-zone + if str(hostname) == self.zone_name or str(hostname).endswith(".%s" % self.zone_name) and not reduce(lambda x,y: x or y, [str(hostname).endswith(".%s" % z) for z in self.bl_zone if z != self.zone_name and z.endswith(self.zone_name)] + [False]): ret=str(hostname)[0:- len(self.zone_name) -1] if ret == "": return "@" else: return ret else: - print "%s not in zone %s" % (hostname, self.zone_name) + #print "%s not in zone %s" % (hostname, self.zone_name) return None def add_delegation(zone, server): @@ -152,7 +196,8 @@ class Zone(ZoneBase): def add_aaaa_record(self, nom, machine): if self.ipv6: for ip in machine.get('ip6HostNumber', []): - self.add(AAAA(nom, ip)) + if len(machine['dnsIpv6'])<1 or machine['dnsIpv6'][0].value: + self.add(AAAA(nom, ip)) if self.ipv4: if nom == '@': self.add(AAAA("v6", ip)) @@ -185,6 +230,8 @@ class Zone(ZoneBase): if machine['host']: for alias in machine.get('hostAlias', []): + if str(alias) in self.bl_zone and str(alias) != self.zone_name: + continue alias = self.get_name(alias) if alias is None: continue to_nom, to_zone = str(machine['host'][0]).split('.', 1) @@ -201,24 +248,6 @@ class Zone(ZoneBase): self.add(CNAME(alias, "%s." % machine['host'][0])) -def reverse(net, ip): - """Renvoie la zone DNS inverse correspondant au réseau et à - l'adresse donnés, ainsi que le nombre d'éléments de l'ip a - mettre dans le fichier de zone.""" - n = netaddr.IPNetwork(net) - a = netaddr.IPAddress(ip) - rev_dns_a = a.reverse_dns.split('.')[:-1] - assert a in n - if n.version == 4: - if n.prefixlen == 8: - return ('.'.join(rev_dns_a[3:]), 3) - elif n.prefixlen == 16: - return ('.'.join(rev_dns_a[2:]), 2) - else: - return ('.'.join(rev_dns_a[1:]), 1) - elif n.version == 6: - return ('.'.join(rev_dns_a[(128-n.prefixlen)/4:]), (128-n.prefixlen)/4) - class ZoneReverse(Zone): def __init__(self, net, ttl, soa, ns_list): self.net = net @@ -237,9 +266,188 @@ class ZoneReverse(Zone): def add_machine(self, machine): if machine['host']: if self.ipv4: - for ip in machine['ipHostNumber']: + attr = 'ipHostNumber' + elif self.ipv6: + attr = 'ip6HostNumber' + else: + raise ValueError("A reverse zone should be ipv6 or ipv6") + for ip in machine[attr]: + try: zone, length = reverse(self.net, str(ip)) nom = '.'.join(ip.value.reverse_dns.split('.')[:length]) if zone != self.zone_name: - continue - self.add(PTR(nom, '%s.' % machine['host'][0])) + continue + if attr != 'ip6HostNumber' or len(machine['dnsIpv6'])<1 or machine['dnsIpv6'][0].value: # Hack pour envoyer le reverse vers l'adresse .v6 dans le cas où dnsIpv6 = False + self.add(PTR(nom, '%s.' % machine['host'][0])) + else: + rev_nom, rev_zone = str(machine['host'][0]).split('.', 1) + self.add(PTR(nom, '%s.v6.%s.' % (rev_nom, rev_zone))) + except AssertionError: + pass + + +class dns(gen_config) : + ######################################PARTIE DE CONFIGURATION + + ### Fichiers à écrire + # Répertoire d'écriture des fichiers de zone + DNS_DIR = '/etc/bind/generated/' # Avec un / à la fin + DNSSEC_DIR = '/etc/bind/signed/' # Avec un / à la fin + # Fichier de définition des zones pour le maître + DNS_CONF = DNS_DIR + 'zones_crans' + + # Fichier de définition des zones pour les esclaves géré par BCfg2 + DNS_CONF_BCFG2 = "/var/lib/bcfg2/Cfg/etc/bind/generated/zones_crans/zones_crans" + + ### Liste DNS + # Le premier doit être le maitre + + ### Liste des délégations de zone + # Pour les demandes de ces zones, le DNS dira d'aller voir les serveurs listés ici + # Pour les noms des serveurs on met le nom sans point à la fin + # { nom_de_zone : [ ns1, ns2, ...] + DELEG = {} + + ### Serveurs de mail + # format : [ priorité serveur , .... ] + MXs = [ + MX('@',10, 'redisdead.crans.org.'), + MX('@',20, 'ovh.crans.org.'), + MX('@',25, 'freebox.crans.org.'), + ] + SRVs = { + 'crans.org': [ + SRV('jabber', 'tcp', 5, 0, 5269, 'xmpp'), + SRV('xmpp-server', 'tcp', 5, 0, 5269, 'xmpp'), + SRV('xmpp-client', 'tcp', 5, 0, 5222, 'xmpp'), + SRV('sip', 'udp', 5, 0, 5060, 'asterisk'), + SRV('sip', 'tcp', 5, 0, 5060, 'asterisk'), + SRV('sips', 'tcp', 5, 0, 5061, 'asterisk'), + ] + } + NATPRs = { + 'crans.org' : [ + NAPTR('@', 5, 100, "S", "SIPS+D2T", "", '_sips._tcp.crans.org.', ttl=86400), + NAPTR('@', 10, 100, "S", "SIP+D2U", "", '_sip._udp.crans.org.', ttl=86400), + NAPTR('@', 15, 100, "S", "SIP+D2T", "", '_sip._tcp.crans.org.', ttl=86400), + ] + } + + # DS à publier dans zone parentes : { parent : [ zone. TTL IN DS key_id algo_id 1 hash ] } + # ex : { 'crans.eu' : ['wifi.crans.eu. 86400 IN DS 33131 8 1 3B573B0E2712D8A8B1B0C3'] } + # /!\ Il faut faire attention au rollback des keys, il faudrait faire quelque chose d'automatique avec opendnssec + DSs = { + 'crans.eu': [ + DS('wifi', '37582 8 2 51809b508e450d8fec44572a3fa31754c27507465775c7d1c86570abd7a21024'), + DS('v6','12562 8 2 0a8e92398c5213cf2907b79f0fa8bd7db6729e0d2f6ca7443ac5a4b8441e38bd'), + ], + 'v6.crans.eu' : [ + DS('wifi','1799 8 2 52a40a7dfb3e9c88aee032c21c59be756c8d3de29149c408ed8b699d83e30032'), + ], + 'crans.org': [ + DS('v6','23641 8 2 3fff97a2581f0f2f49257b4914d5badf8ccb0a49c5a6f4cbf2f520b97de332d0'), + DS('adm','565 8 2 498f6cd5bcf291aae4129700a7569fa6e9a86821185bd655f0b9efc6a3bf547e'), + DS('ferme','35156 8 2 b63a1443b3d7434429e879e046bc8ba89056cdcb4b9c3566853e64fd521895b8'), + DS('wifi','41320 8 2 024799c1d53f1e827f03d17bc96709b85ee1c05d77eb0ebeadcfbe207ee776a4'), + DS('tv','30910 8 2 3317f684081867ab94402804fbb3cd187e29655cc7f34cb92c938183fe0b71f5'), + ], + 'v6.crans.org' : [ + DS('adm','1711 8 2 f154eeb8eb346d2ca5cffb3f9cc464a17c0c4d69ee425b4fe44eaed7f5dd253b'), + DS('ferme','44434 8 2 fb87cb4216599cb6574add543078a9e48d0e50438483386585a9960557434ab0'), + DS('wifi','59539 8 2 dbe86f2f2e92d6a27bd1436f03ec1588f2948a2aa02124de0383be801cced85e'), + ] + } + + + hostname = short_name(gethostname()) + if hostname == short_name(config.dns.DNSs[0]): + restart_cmd = '/usr/sbin/ods-signer sign --all && /etc/init.d/bind9 reload' + else: + restart_cmd = '/etc/init.d/bind9 reload' + + def gen_zones(self, ttl, ns_list, populate=True): + zones = {} + serial = int(time.time()) + 1000000000 + for zone in config.dns.zones_ldap: + #print "Create zone %s" % zone + zones[zone]=Zone(zone, ttl, SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl), ns_list, bl_zone=config.dns.zones_direct) + for net in config.dns.zones_reverse: + net = netaddr.IPNetwork(net) + if net.prefixlen > 24: + subnets = net.subnet(32) + elif net.prefixlen > 16: + subnets = net.subnet(24) + elif net.prefixlen > 8: + subnets = net.subnet(16) + else: + subnets = net.subnet(8) + for subnet in subnets: + #print "Create zone %s" % subnet + zones[str(subnet)]=ZoneReverse(str(subnet), ttl, SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl), ns_list) + for net in config.dns.zones_reverse_v6: + #print "Create zone %s" % net + zones[net]=ZoneReverse(net, ttl, SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl), ns_list) + + if populate: + conn = lc_ldap.shortcuts.lc_ldap_admin() + machines = conn.search(u"mid=*", sizelimit=10000) + machines.extend(conn.machinesMulticast()) + self.anim.iter=len(zones.values()) + for zone in zones.values(): + #print "Generate zone %s" % zone.zone_name + zone.extend(self.MXs) + for rr_type in [self.SRVs, self.NATPRs, self.DSs]: + if zone.zone_name in rr_type.keys(): + zone.extend(rr_type[zone.zone_name]) + for m in machines: + zone.add_machine(m) + self.anim.cycle() + for zone_clone, zones_alias in config.dns.zone_alias.items(): + for zone in zones_alias: + zones[zone]=ZoneClone(zone, zones[zone_clone], SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl)) + for rr_type in [self.SRVs, self.NATPRs, self.DSs]: + if zones[zone].zone_name in rr_type.keys(): + zones[zone].extend(rr_type[zones[zone].zone_name]) + return zones + + + def gen_master(self): + # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître + zone_template=""" +zone "%(zone_name)s" { + type master; + file "%(zone_path)s"; +}; +""" + zones = self.gen_zones(3600, config.dns.DNSs) + with open(self.DNS_CONF, 'w') as f: + for zone in zones.values(): + zone.write(self.DNS_DIR + 'db.' + zone.zone_name) + if zone.zone_name in config.dns.zones_dnssec: + zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name + else: + zone_path = self.DNS_DIR + 'db.' + zone.zone_name + f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path}) + + def gen_slave(self): + zone_template=""" +zone "%(zone_name)s" { + type slave; + file "%(zone_path)s"; + masters { %(master_ip)s; }; +}; +""" + zones = self.gen_zones(3600, config.dns.DNSs, populate=False) + with open(self.DNS_CONF_BCFG2, 'w') as f: + for zone in zones.values(): + if zone.zone_name in config.dns.zones_dnssec: + zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name + else: + zone_path = self.DNS_DIR + 'db.' + zone.zone_name + f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path, 'master_ip' : config.dns.master}) + + def _gen(self): + self.gen_master() + + def __str__(self): + return "DNS" diff --git a/gestion/gen_confs/generate.py b/gestion/gen_confs/generate.py index 80170d84..cc0be71e 100755 --- a/gestion/gen_confs/generate.py +++ b/gestion/gen_confs/generate.py @@ -289,8 +289,8 @@ class isc(base_reconfigure): class sable(base_reconfigure): def dns(self): - from gen_confs.bind import dns - self._do(dns(), self._machines()) + from gen_confs.bind2 import dns + self._do(dns(), []) class routeur(base_reconfigure): pass