diff --git a/archive/gestion/gen_confs/bind.py b/archive/gestion/gen_confs/bind.py new file mode 100755 index 00000000..fbee2244 --- /dev/null +++ b/archive/gestion/gen_confs/bind.py @@ -0,0 +1,592 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +""" Génération de la configuration pour bind9 + +Copyright (C) Frédéric Pauget +Licence : GPLv2 +""" +import time, sys, re, hashlib, base64, os +sys.path.append('/usr/scripts/gestion') +from socket import gethostname +from gen_confs import gen_config + +import config +import config.dns +from iptools import AddrInNet, AddrInNets +import ip6tools + +import netaddr +import ldap_crans + +def short_name(fullhostname): + return fullhostname.split(".")[0] + + + +def netv4_to_arpa(net): + addr, prefixlen = net.split('/') + if prefixlen == '8': + return ["%s.in-addr.arpa" % addr.split('.')[0]] + if prefixlen == '16': + return ["%s.in-addr.arpa" % '.'.join(reversed(addr.split('.')[0:2]))] + zones=[] + n = map(int,net.split('/')[0].split('.')[:3]) + while 1 : + try: + innet = AddrInNet("%d.%d.%d.1" % tuple(n),net) + except ValueError: + break + else: + if not innet: + break + else : + n.reverse() + zones.append("%d.%d.%d.in-addr.arpa" % tuple(n)) + n.reverse() + n[2] += 1 + return zones + +def netv6_to_arpa(net): + n = netaddr.IPNetwork(net) + network_reverse = netaddr.IPAddress(n.first).reverse_dns + zone = network_reverse.split('.')[(128-n.prefixlen)/4:-1] + return '.'.join(zone) + + +class dns(gen_config) : + """ + Génération des fichiers de configuration de bind9 : + * fichier DNS_CONF qui contient les définitions de zone conformément +à zone_template. Ce fichier doit être inclus à partir de la config statique +de bind + * les fichiers de zones, ce sont eux qui contiennent les données du +dns, ils ont appellés par le fichier DNS_CONF et sont générés dans DNS_DIR +Leur entète est générée à partir de zone_entete. + + Les fichiers générés placent bind comme autoritaire sur les noms de +zones_direct et les adresses de zones_reverse. Les données proviennent de +la base LDAP + """ + ######################################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" + + ### Sur quelles zones on a autorité ? + ## En cas de modification de ces zones penser à regéner le fichier de + ## zone des esclaves (sur le serveur principal de bcfg2 : python /usr/scripts/gestion/gen_confs/bind.py puis lancer bcfg2 sur les miroirs) + # Résolution directe + zones_direct = config.dns.zones_direct + # Zones signée par opendnssec sur le serveur maitre + zones_dnssec = config.dns.zones_dnssec + # Zones alias pour les enregistrement A AAAA CNAME TXT et SSHFP + zone_alias = config.dns.zone_alias + zones_v4_to_v6 = { + 'wifi.crans.eu': 'wifi.v6.crans.eu', + 'crans.eu': 'v6.crans.eu', + 'crans.org': 'v6.crans.org', + 'wifi.crans.org': 'wifi.v6.crans.org', + 'adm.crans.org': 'adm.v6.crans.org', + 'ferme.crans.org': 'ferme.v6.crans.org', + } + zone_alias.update({ + 'wifi.v6.crans.eu': ['v6.wifi.crans.eu'], + 'wifi.v6.crans.org': ['v6.wifi.crans.org'], + 'adm.v6.crans.org': ['v6.adm.crans.org'], + 'ferme.v6.crans.org': ['v6.ferme.crans.org'], + }) + + # Résolution inverse + zones_reverse = config.dns.zones_reverse + zones_v6_to_net = { + 'crans.eu': config.prefix["fil"][0], + 'crans.org': config.prefix["fil"][0], + 'wifi.crans.org': config.prefix["wifi"][0], + 'adm.crans.org': config.prefix["adm"][0], + 'ferme.crans.org': config.prefix["fil"][0], + # Hack pour générer un fichier de zone vide + '##HACK##': config.prefix["subnet"][0], + } + ### Liste DNS + # Le premier doit être le maitre + DNSs = config.dns.DNSs + DNSs_private = [] + ip_master_DNS = config.dns.master + + zone_multicast = config.dns.zone_tv + + ### 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 l'IP sans point ou le nom avec un point + DELEG = { + zone_multicast : [ 'sable.crans.org.' , 'mdr.crans.org.', 'freebox.crans.org.', 'ovh.crans.org.'] , + } + + ### Serveurs de mail + # format : [ priorité serveur , .... ] + MXs = ['10 redisdead.crans.org', '20 ovh.crans.org', '25 freebox.crans.org'] + SRVs = [ + '_jabber._tcp.crans.org. 86400 IN SRV 5 0 5269 xmpp.crans.org.', + '_xmpp-server._tcp.crans.org. 86400 IN SRV 5 0 5269 xmpp.crans.org.', + '_xmpp-client._tcp.crans.org. 86400 IN SRV 5 0 5222 xmpp.crans.org.', + '_sip._udp.crans.org. 86400 IN SRV 5 0 5060 asterisk.crans.org.', + '_sip._tcp.crans.org. 86400 IN SRV 5 0 5060 asterisk.crans.org.', + '_sips._tcp.crans.org. 86400 IN SRV 5 0 5061 asterisk.crans.org.', + ] + + NATPRs = [ + '@ 86400 IN NAPTR 5 100 "S" "SIPS+D2T" "" _sips._tcp.crans.org.', + '@ 86400 IN NAPTR 10 100 "S" "SIP+D2U" "" _sip._udp.crans.org.', + '@ 86400 IN NAPTR 15 100 "S" "SIP+D2T" "" _sip._tcp.crans.org.', + ] + + # 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 + DS = { + 'crans.eu': [ + 'wifi.crans.eu. 3600 IN DS 37582 8 2 51809b508e450d8fec44572a3fa31754c27507465775c7d1c86570abd7a21024', + 'v6.crans.eu. 3600 IN DS 12562 8 2 0a8e92398c5213cf2907b79f0fa8bd7db6729e0d2f6ca7443ac5a4b8441e38bd', + ], + 'v6.crans.eu' : [ + 'wifi.v6.crans.eu. 3600 IN DS 1799 8 2 52a40a7dfb3e9c88aee032c21c59be756c8d3de29149c408ed8b699d83e30032', + ], + 'crans.org': [ + 'v6.crans.org. 3600 IN DS 23641 8 2 3fff97a2581f0f2f49257b4914d5badf8ccb0a49c5a6f4cbf2f520b97de332d0', + 'adm.crans.org. 3600 IN DS 565 8 2 498f6cd5bcf291aae4129700a7569fa6e9a86821185bd655f0b9efc6a3bf547e', + 'ferme.crans.org. 3600 IN DS 35156 8 2 b63a1443b3d7434429e879e046bc8ba89056cdcb4b9c3566853e64fd521895b8', + 'wifi.crans.org. 3600 IN DS 41320 8 2 024799c1d53f1e827f03d17bc96709b85ee1c05d77eb0ebeadcfbe207ee776a4', + 'tv.crans.org. 3600 IN DS 30910 8 2 3317f684081867ab94402804fbb3cd187e29655cc7f34cb92c938183fe0b71f5', + ], + 'v6.crans.org' : [ + 'adm.v6.crans.org. 3600 IN DS 1711 8 2 f154eeb8eb346d2ca5cffb3f9cc464a17c0c4d69ee425b4fe44eaed7f5dd253b', + 'ferme.v6.crans.org. 3600 IN DS 44434 8 2 fb87cb4216599cb6574add543078a9e48d0e50438483386585a9960557434ab0', + 'wifi.v6.crans.org. 3600 IN DS 59539 8 2 dbe86f2f2e92d6a27bd1436f03ec1588f2948a2aa02124de0383be801cced85e', + ] + } + + + ### Entète des fichiers de zone + zone_entete=""" +$ORIGIN %(zone)s. +$TTL 3600 +@\tIN\tSOA %(serveur_autoritaire)s. root.crans.org. ( + %(serial)i ; numero de serie + 21600 ; refresh (s) + 3600 ; retry (s) + 1209600 ; expire (s) + 3600 ; TTL (s) + ) +""" + + # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître + zone_template=""" +zone "%(NOM_zone)s" { + type master; + file "%(FICHIER_zone)s"; +}; +""" + # Syntaxe utilisée dans le fichier DNS_CONF_BCFG2 pour définir une zone sur un esclave + zone_template_slave=""" +zone "%(NOM_zone)s" { + type slave; + file "%(FICHIER_zone)s"; + masters { %(ip_master_DNS)s; }; +}; +""" + + ### Verbosité + # Si =2, ralera (chaine warnings) si machines hors zone trouvée + # Si =1, comme ci-dessus, mais ne ralera pas pour freebox + # Si =0, ralera seulement contre les machines ne pouvant être classées + verbose = 1 + + hostname = short_name(gethostname()) + if hostname == short_name(DNSs[0]): + restart_cmd = '/usr/sbin/ods-signer sign --all && /etc/init.d/bind9 reload' + else: + restart_cmd = '/etc/init.d/bind9 reload' + + ######################################FIN PARTIE DE CONFIGURATION + + def __str__(self) : + return "DNS" + + def reverse(self, 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 == 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) + + + def is_ascii(self, s): + return all(ord(c) < 128 for c in s) + + def gen_tv(self): + sys.path.append('/usr/scripts') + import tv.dns + serial = time.time() + 1000000000 + zone_reverse=netv4_to_arpa(config.NETs['multicast'][0])[0] + sap=open('/usr/scripts/var/tv/sap.txt').readlines() + + DNS='; DNS de la zone par ordre de priorité\n' + for d in self.DELEG[self.zone_multicast] : + DNS += '@\tIN\tNS %s\n' % d + DNS += '\n' + + lignes_d = '\n' + lignes_r = '\n' + lignes_d +='@\tIN\tA\t%s\n' % '138.231.136.88' + for line in sap: + [nom,ip]=line.split(':') + nom=unicode(nom, 'utf-8') + nom_ascii=tv.dns.ascii(nom) + nom_punycode=tv.dns.punycode(nom) + try: + [ip1,ip2,ip3,ip4]=ip.strip().split('.') + lignes_r += '%s.%s.%s\tIN\tPTR\t%s.%s.\n' % (ip4,ip3,ip2,nom_ascii,self.zone_multicast) + lignes_d +='%s\tIN\tA\t%s' % (nom_ascii,ip) + if nom_punycode: + lignes_d +='%s\tIN\tA\t%s' % (nom_punycode, ip) + except: + raise + # Écriture de la zone directe + file = self.DNS_DIR + 'db.' + self.zone_multicast + fd = self._open_conf(file,';') + fd.write(self.zone_entete % \ + { 'zone' : self.zone_multicast, 'serveur_autoritaire' : self.DELEG[self.zone_multicast][0][0:-1] , 'serial' : serial } ) + fd.write('\n') + fd.write(DNS) + fd.write(lignes_d) + fd.close() + + # Écriture du reverse + file = self.DNS_DIR + 'db.' + zone_reverse + fd = self._open_conf(file,';') + fd.write(self.zone_entete % \ + { 'zone' : zone_reverse, 'serveur_autoritaire' : self.DELEG[self.zone_multicast][0][0:-1] , 'serial' : serial } ) + fd.write('\n') + fd.write(DNS) + fd.write(lignes_r) + fd.close() + + + def gen_slave(self) : + """ Génération du fichier de config de zone pour les esclaves """ + zones = self.zones_direct + zones.extend(self.zones_v4_to_v6.values()) + zones.extend([z for l in self.zone_alias.values() for z in l]) + zones = list(set(zones)) + zones.sort() + + # Ajout des zones reverse + for net in self.zones_reverse: + # IPv4 reverse + zones.extend(netv4_to_arpa(net)) + + for net in set(self.zones_v6_to_net.values()): + # IPv6 reverse + zones.append(netv6_to_arpa(net)) + + # Ecriture + fd = self._open_conf(self.DNS_CONF_BCFG2,'//') + for zone in zones : + if zone in self.zones_dnssec: + path=self.DNSSEC_DIR + 'db.' + zone + else: + path=self.DNS_DIR + 'db.' + zone + fd.write(self.zone_template_slave % { 'NOM_zone' : zone, + 'FICHIER_zone' : path, + 'ip_master_DNS': self.ip_master_DNS}) + + fd.close() + + def _gen(self) : + ### Génération du numéro de série + # Le + 1000.... s'explique pas l'idée précédente et peu pratique d'avoir + # le numéro de série du type AAAAMMJJNN (année, mois, jour, incrément par jour) + serial = time.time() + 1000000000 + + ### DNS + DNS='; DNS de la zone par ordre de priorité\n' + for d in self.DNSs : + DNS += '@\tIN\tNS %s.\n' % d + DNS += '\n' + + ### Serveurs de mail + MX='; Serveurs de mails\n' + for m in self.MXs : + MX += '@\t' # Sera remplacé par le nom de zone plus tard + MX += 'IN\tMX\t%s.\n' % m + MX += '\n' + + direct = {} # format : { zone : [ lignes correspondantes] } + reverse = {} + warnings = '' + + direct['crans.org'] = "" + + # P'tit lien vers irc.rezosup.org + #direct["crans.org"] = "\n; irc.crans.org -> irc.rezosup.org\n" + #direct["crans.org"] += "irc\tIN\tCNAME\tirc.rezosup.org.\n\n" + + ### Tri des machines + self.anim.iter=len(self.machines) + for machine in self.machines : + self.anim.cycle() + # Calculs préliminaires + try : + nom , zone = machine.nom().split('.',1) + zone = zone.encode('utf-8') + except : + warnings += u'Machine ignorée (mid=%s) : format nom incorrect (%s)\n' % ( machine.id().encode('utf-8'), machine.nom().encode('utf-8') ) + continue + + # Le direct + if zone in self.zones_direct : + ligne='' + if machine.ip() != '': + ligne += "%s\tIN\tA\t%s\n" % ( nom, machine.ip() ) + # Si la machine est une borne wifi, on ajoute la position + if isinstance(machine,ldap_crans.BorneWifi) and machine.position(): + ligne +="%s\tIN\tTXT\t\"LOC %s,%s \"\n" % (nom,machine.position()[0],machine.position()[1]) + # Si la machine à des clefs ssh, on ajoute les champs SSFP correspondant + for sshkey in machine.sshFingerprint(): + try: + [algo_txt,key]=sshkey.split()[:2] + algo=None + for value in config.sshfp_algo.values(): + if algo_txt == value[1]: + algo=value[0] + break + if not algo: + raise ValueError("Invalid Algorithms %s" % algo_txt) + key1=hashlib.sha1(base64.b64decode(key)).hexdigest() + key2=hashlib.sha256(base64.b64decode(key)).hexdigest() + ligne +="%s\tIN\tSSHFP\t%s\t1\t%s\n" % (nom,algo,key1) + ligne +="%s\tIN\tSSHFP\t%s\t2\t%s\n" % (nom,algo,key2) + except(ValueError,TypeError): pass + direct[zone] = direct.get(zone, "") + ligne + if isinstance(machine,ldap_crans.BorneWifi): + direct['ap.crans.org'] = direct.get('ap.crans.org', "") + ligne + elif self.verbose and machine.nom() != "ftp.federez.net": + warnings += u'Résolution directe ignorée (mid=%s) : zone non autoritaire (%s)\n' % ( machine.id().encode('utf-8'), zone.encode('utf-8') ) + + # IPv6 + if zone in self.zones_v4_to_v6: + # Direct + zone_v6 = self.zones_v4_to_v6[zone] + ipv6 = machine.ipv6() + net_v6 = machine.netv6() + ligne = "%s\tIN\tAAAA\t%s\n" % (nom, ipv6) + direct[zone_v6] = direct.get(zone_v6, "") + ligne + if machine.dnsIpv6(): + direct[zone] = direct.get(zone, "") + ligne + # Reverse + zone_rev, length = self.reverse(net_v6, ipv6) + rev = '.'.join(ipv6.reverse_dns.split('.')[:length]) + ligne = "%s\tIN\tPTR\t%s.\n" % (rev, machine.nom6()) + reverse[zone_rev] = reverse.get(zone_rev, "") + ligne + + + # Le direct avec alias + for alias in machine.alias() : + alias = alias.encode('utf-8') + # Cas particulier : nom de l'alias = nom de la zone + if alias in self.zones_direct : + if machine.ip() != '': + ligne = "@\tIN\tA\t%s\n" % machine.ip() + ligne = ligne.encode('utf-8') + direct[alias] = direct.get(alias, "") + ligne + if machine.dnsIpv6(): + ligne = "@\tIN\tAAAA\t%s\n" % machine.ipv6() + ligne = ligne.encode('utf-8') + direct[alias]= direct.get(alias, "") + ligne + if alias in self.zones_v4_to_v6: + ligne = "@\tIN\tAAAA\t%s\n" % machine.ipv6() + ligne = ligne.encode('utf-8') + zone6 = self.zones_v4_to_v6[alias] + direct[zone6] = direct.get(zone6, '') + ligne + continue + + # Bon format ? + alias_l = alias.split('.') + ok = 0 + for i in range(len(alias_l)) : + zone_essai = '.'.join(alias_l[i:]) + if zone_essai in self.zones_direct : + # On est autoritaire sur cette zone + # On place donc l'alias dans le fichier de cette zone + zone = zone_essai + nom = '.'.join(alias_l[:i]) + ok = 1 + break + if not ok: + warnings += u'Alias ignoré (mid=%s) : %s\n' % ( machine.id().encode('utf-8'), alias.encode('utf-8') ) + continue + zone = zone.encode('utf-8') + ligne = "%s\tIN\tCNAME\t%s.\n" % ( nom, machine.nom() ) + direct[zone] = direct.get(zone, '') + ligne + if zone in self.zones_v4_to_v6: + zone6 = self.zones_v4_to_v6[zone] + ligne = "%s\tIN\tCNAME\t%s.\n" % ( nom, machine.nom6() ) + direct[zone6] = direct.get(zone6, '') + ligne + + # Le reverse + ip = machine.ip() + if ip == '': + net=False + continue + else: + net = AddrInNets(ip, self.zones_reverse) + if net: + base_ip = ip.split('.') + base_ip.reverse() + zone, length = self.reverse(net, ip) + zone = zone.encode('utf-8') + ligne = '%s\tIN\tPTR\t%s.\n' % ('.'.join(base_ip[:length]), machine.nom()) + try : reverse[zone] += ligne + except : reverse[zone] = ligne + elif self.verbose >= 2 or machine.nom() not in ('freebox.crans.org', 'ovh.crans.org', 'kokarde.crans.org'): + warnings += u'Résolution inverse ignorée (mid=%s) : ip sur zone non autoritaire (%s)\n' % ( machine.id().encode('utf-8'), machine.ip().encode('utf-8') ) + + ### Ajouts pour les fichiers de résolution directs + for zone in direct.keys() : + # MXs + direct[zone] = MX + direct[zone] + + ### XXX: création de la zone inverse pour le /48 IPv6 complet du Cr@ns + full_net_v6 = self.zones_v6_to_net["##HACK##"] + zone_rev, length = self.reverse(full_net_v6, netaddr.IPNetwork(full_net_v6).first) + reverse[zone_rev] = reverse.get(zone_rev, "") + + ### Alias de zone + zone_todo = [zone for zone in self.zone_alias] + while zone_todo: + for zone in zone_todo: + for alias in self.zone_alias[zone]: + try: + direct[alias] = direct[zone] + zone_todo.remove(zone) + except KeyError: + pass + if alias in self.zones_v4_to_v6: + alias_v6=self.zones_v4_to_v6[alias] + zone_v6 = self.zones_v4_to_v6[zone] + direct[alias_v6] = direct[zone_v6] + + ### Ajout des parametres SPF + direct['crans.org'] +='; Parametres SPF\n' + direct['crans.org'] +='crans.org.\tIN\tSPF\t"v=spf1 a mx ip6:2a01:240:fe3d:4::/64 ?all"\n' + for m in self.MXs: + direct['crans.org'] +='%s.\tIN\tSPF\t"v=spf1 a ip6:2a01:240:fe3d:4::/64 ?all"\n' % m.split()[-1] + direct['crans.org'] += '\n' + + direct['crans.ens-cachan.fr'] += '; Parametres SPF\n' + direct['crans.ens-cachan.fr'] += 'crans.ens-cachan.fr.\tIN\tSPF\t"v=spf1 a:crans.org mx ip6:2a01:240:fe3d:4::/64 ?all"\n\n' + + ### Ajout d'eventuels champs SRV + direct['crans.org'] +='; Champs SRV\n' + for s in self.SRVs: + direct['crans.org'] += s + '\n' + for s in self.NATPRs: + direct['crans.org'] += s + '\n' + direct['crans.org'] += '\n' + + ### Ajout des délégations de zones + for deleg in self.DELEG.keys(): + nom, zone = deleg.split('.',1) + if not zone in direct.keys(): + warnings += u'Délégation ignorée %s : on ne génère pas la zone parent\n' % deleg + continue + for serv in self.DELEG[deleg]: + direct[zone] = direct[zone] + "%s\tIN\tNS\t%s\n" % ( nom, serv ) + + for zone in direct.keys(): + child, parent = zone.split('.',1) + if not zone in self.DELEG.keys() and parent in self.zones_direct + [z for l in self.zone_alias.values() for z in l] + self.zones_v4_to_v6.values(): + for d in self.DNSs: + direct[parent] = direct.get(parent, "") + '%s\tIN\tNS %s.\n' % (child, d) + + + ### Ajout d'eventuel champs DS pour les délégation dnssec + for zone,ds in self.DS.items(): + for s in ds: + direct[zone] += s + '\n' + direct[zone] += '\n' + + ### Ecriture des fichiers de zone et préparation du fichier de définition + f = '' + for zone, lignes in direct.items() + reverse.items() : + if zone in self.zones_dnssec: + path = self.DNSSEC_DIR + 'db.' + zone + else: + path = self.DNS_DIR + 'db.' + zone + file = self.DNS_DIR + 'db.' + zone + fd = self._open_conf(file,';') + fd.write(self.zone_entete % \ + { 'zone' : zone, 'serveur_autoritaire' : self.DNSs[0] , 'serial' : serial } ) + fd.write('\n') + fd.write(DNS) + fd.write(lignes) + fd.close() + os.chmod(file,0664) + if short_name(gethostname()) in map(short_name, dns.DNSs[1:]): + f += self.zone_template_slave % {'NOM_zone': zone, 'FICHIER_zone': path, + 'ip_master_DNS': self.ip_master_DNS} + else: + f += self.zone_template % { 'NOM_zone' : zone, 'FICHIER_zone' : path } + + ### Ecriture fichier de définition + fd = self._open_conf(self.DNS_CONF,'//') + fd.write(f) + fd.close() + + return warnings + + +if __name__ == '__main__' : + from config import bcfg2_main + hostname = short_name(gethostname()) + if hostname == short_name(bcfg2_main): + print "Reconfiguration du fichier de BCfg2 pour configurer le bind d'un serveur en esclave (pensez à lancer bcfg2 sur les esclaves)." + c = dns() + c.gen_slave() + if hostname == short_name(dns.DNSs[0]): + print "Ce serveur est également serveur maitre, mais la reconfiguration du DNS maitre se fait par generate." + elif hostname == short_name(dns.DELEG[config.dns.zone_tv][0][0:-1]): + print "Serveur maître pour tv.crans.org, génération de la zone" + c = dns() + c.gen_tv() + import subprocess + args="/usr/sbin/ods-signer sign tv.crans.org".split() + p=subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + ret=p.communicate() + print ret[0].strip() + print ret[1].strip() + if hostname == short_name(dns.DNSs[0]): + print "Ce serveur est également serveur maitre, mais la reconfiguration du DNS maitre se fait par generate." + elif hostname == short_name(dns.DNSs[0]): + print "Ce serveur est maître ! Utilisez generate." + elif hostname in map(lambda fullhostname : short_name(fullhostname),dns.DNSs[1:]+dns.DNSs_private): + print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % bcfg2_main + else: + print "Ce serveur ne correspond à rien pour la configuration DNS." diff --git a/gestion/gen_confs/bind.py b/gestion/gen_confs/bind.py index fbee2244..82b54537 100755 --- a/gestion/gen_confs/bind.py +++ b/gestion/gen_confs/bind.py @@ -1,73 +1,302 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- - """ Génération de la configuration pour bind9 -Copyright (C) Frédéric Pauget -Licence : GPLv2 +Copyright (C) Valentin Samir +Licence : GPLv3 """ -import time, sys, re, hashlib, base64, os -sys.path.append('/usr/scripts/gestion') -from socket import gethostname -from gen_confs import gen_config - -import config -import config.dns -from iptools import AddrInNet, AddrInNets -import ip6tools +import sys +import time +import base64 +import hashlib import netaddr -import ldap_crans +sys.path.append('/usr/scripts/') + +import lc_ldap.shortcuts +import affich_tools +from gestion.gen_confs import gen_config +from socket import gethostname +from gestion import config +import config.dns + +disclamer = """//**************************************************************// +// Ce fichier est genere par les scripts de gen_confs // +// Les donnees proviennent de la base LDAP et de la conf // +// presente au debut du script. Il peut être propagé via bcfg2. // +// // +// NE PAS EDITER // +// // +//**************************************************************// +""" def short_name(fullhostname): return fullhostname.split(".")[0] - - -def netv4_to_arpa(net): - addr, prefixlen = net.split('/') - if prefixlen == '8': - return ["%s.in-addr.arpa" % addr.split('.')[0]] - if prefixlen == '16': - return ["%s.in-addr.arpa" % '.'.join(reversed(addr.split('.')[0:2]))] - zones=[] - n = map(int,net.split('/')[0].split('.')[:3]) - while 1 : - try: - innet = AddrInNet("%d.%d.%d.1" % tuple(n),net) - except ValueError: - break - else: - if not innet: - break - else : - n.reverse() - zones.append("%d.%d.%d.in-addr.arpa" % tuple(n)) - n.reverse() - n[2] += 1 - return zones - -def netv6_to_arpa(net): +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) - network_reverse = netaddr.IPAddress(n.first).reverse_dns - zone = network_reverse.split('.')[(128-n.prefixlen)/4:-1] - return '.'.join(zone) + 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): + self._type=type + self._name=name + self._value=value + self._ttl=ttl + + def __str__(self): + if self._ttl: + return "%s\t%s\tIN\t%s\t%s" % (self._name, self._ttl, self._type, self._value) + else: + return "%s\tIN\t%s\t%s" % (self._name, self._type, self._value) + def __repr__(self): + return str(self) + +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)) +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) +class AAAA(ResourceRecord): + def __init__(self, name, value, ttl=None): + super(AAAA, self).__init__('AAAA', name, value, ttl) +class TXT(ResourceRecord): + def __init__(self, name, value, ttl=None): + super(TXT, self).__init__('TXT', name, value, ttl) +class CNAME(ResourceRecord): + def __init__(self, name, value, ttl=None): + super(CNAME, self).__init__('CNAME', name, value, ttl) +class DNAME(ResourceRecord): + def __init__(self, name, value, ttl=None): + super(DNAME, self).__init__('DNAME', name, value, ttl) +class MX(ResourceRecord): + def __init__(self, name, priority, value, ttl=None): + super(MX, self).__init__('MX', name, '%s\t%s' % (priority, value), ttl) +class NS(ResourceRecord): + def __init__(self, name, value, ttl=None): + super(NS, self).__init__('NS', name, value, ttl) +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, 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, 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): + if not hash in config.sshfp_hash.keys(): + raise ValueError('Hash %s invalid, valid hash are %s' % (hash, ', '.join(config.sshfp_host.keys()))) + if not algo in config.sshfp_algo.keys(): + raise ValueError('Algo %s unknown, valid values are %s' % (algo, ', '.join(config.sshfp_algo.keys()))) + 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, 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="%s\n$ORIGIN %s.\n$TTL %s\n" % (disclamer.replace('//', ';'), self.zone_name, self.ttl) + for rr in self._rrlist: + ret+="%s\n" % rr + return ret + + 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__(zone_name) + self.zone_clone = zone_clone + self.ttl = zone_clone.ttl + + self.add(soa) + self.add(DNAME('', "%s." % self.zone_clone.zone_name)) + for rr in self.zone_clone._rrlist[1:]: + if rr._name in ['', '@']: + self.add(rr) + if rr._name in ["%s." % self.zone_clone.zone_name]: + self.add(ResourceRecord(rr._type, "%s." % self.zone_name, rr._value)) + + +class Zone(ZoneBase): + def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, other_zones=[]): + super(Zone, self).__init__(zone_name) + self.ttl = ttl + self.ipv4 = ipv4 + self.ipv6 = ipv6 + self.other_zones = other_zones + self.subzones = [z for z in self.other_zones if z != self.zone_name and z.endswith(self.zone_name)] + + self.add(soa) + for ns in ns_list: + self.add(NS('@', '%s.' % ns)) + + def name_in_subzone(self, hostname): + for zone in self.subzones: + if str(hostname).endswith(".%s" % zone): + return True + return False + + def get_name(self, hostname): + # 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 self.name_in_subzone(hostname): + ret=str(hostname)[0:- len(self.zone_name) -1] + if ret == "": + return "@" + else: + return ret + else: + return None + + def add_delegation(zone, server): + zone = self.het_name(zone) + if zone: + self.add(NS('@', '%s.' % server)) + + def add_a_record(self, nom, machine): + if self.ipv4: + for ip in machine.get('ipHostNumber', []): + self.add(A(nom, ip)) + if self.ipv6: + if nom == '@': + self.add(A("v4", ip)) + else: + self.add(A("%s.v4" % nom, ip)) + + def add_aaaa_record(self, nom, machine): + if self.ipv6: + for ip in machine.get('ip6HostNumber', []): + 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)) + else: + self.add(AAAA("%s.v6" % nom, ip)) + + def add_sshfp_record(self, nom, machine): + for sshkey in machine.get('sshFingerprint', []): + algo_txt, key = str(sshkey).split()[:2] + algo=config.sshfs_ralgo[algo_txt][1] + for hash in config.sshfp_hash.keys(): + self.add(SSHFP(nom, hash, algo, key)) + if self.ipv4 and self.ipv6: + if nom == '@': + self.add(SSHFP("v4", hash, algo, key)) + self.add(SSHFP("v6", hash, algo, key)) + else: + self.add(SSHFP("%s.v4" % nom, hash, algo, key)) + self.add(SSHFP("%s.v6" % nom, hash, algo, key)) + + def add_machine(self, machine): + for host in machine['host']: + nom=self.get_name(host) + if nom is None: continue + + self.add_a_record(nom, machine) + self.add_aaaa_record(nom, machine) + self.add_sshfp_record(nom, machine) + + + if machine['host']: + for alias in machine.get('hostAlias', []): + if str(alias) in self.other_zones 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) + if alias in ['@', '%s.' % self.zone_name]: + self.add_a_record(alias, machine) + self.add_aaaa_record(alias, machine) + self.add_sshfp_record(alias, machine) + elif to_zone == self.zone_name: + self.add(CNAME(alias, "%s" % to_nom)) + if self.ipv4 and self.ipv6: + self.add(CNAME("%s.v4" % alias, "%s.v4" % to_nom)) + self.add(CNAME("%s.v6" % alias, "%s.v6" % to_nom)) + else: + self.add(CNAME(alias, "%s." % machine['host'][0])) + + +class ZoneReverse(Zone): + def __init__(self, net, ttl, soa, ns_list): + self.net = net + zone_name = reverse(net, net.split('/')[0])[0] + if '.' in net: + ipv6=False + ipv4=True + elif ':' in net: + ipv6=True + ipv4=False + else: + raise ValueError("net should be an ipv4 ou ipv6 network") + super(ZoneReverse, self).__init__(zone_name, ttl, soa, ns_list, ipv6=ipv6, ipv4=ipv4) + + + def add_machine(self, machine): + if machine['host']: + if self.ipv4: + 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 + 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) : - """ - Génération des fichiers de configuration de bind9 : - * fichier DNS_CONF qui contient les définitions de zone conformément -à zone_template. Ce fichier doit être inclus à partir de la config statique -de bind - * les fichiers de zones, ce sont eux qui contiennent les données du -dns, ils ont appellés par le fichier DNS_CONF et sont générés dans DNS_DIR -Leur entète est générée à partir de zone_entete. - - Les fichiers générés placent bind comme autoritaire sur les noms de -zones_direct et les adresses de zones_reverse. Les données proviennent de -la base LDAP - """ ######################################PARTIE DE CONFIGURATION ### Fichiers à écrire @@ -80,513 +309,229 @@ la base LDAP # 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" - ### Sur quelles zones on a autorité ? - ## En cas de modification de ces zones penser à regéner le fichier de - ## zone des esclaves (sur le serveur principal de bcfg2 : python /usr/scripts/gestion/gen_confs/bind.py puis lancer bcfg2 sur les miroirs) - # Résolution directe - zones_direct = config.dns.zones_direct - # Zones signée par opendnssec sur le serveur maitre - zones_dnssec = config.dns.zones_dnssec - # Zones alias pour les enregistrement A AAAA CNAME TXT et SSHFP - zone_alias = config.dns.zone_alias - zones_v4_to_v6 = { - 'wifi.crans.eu': 'wifi.v6.crans.eu', - 'crans.eu': 'v6.crans.eu', - 'crans.org': 'v6.crans.org', - 'wifi.crans.org': 'wifi.v6.crans.org', - 'adm.crans.org': 'adm.v6.crans.org', - 'ferme.crans.org': 'ferme.v6.crans.org', - } - zone_alias.update({ - 'wifi.v6.crans.eu': ['v6.wifi.crans.eu'], - 'wifi.v6.crans.org': ['v6.wifi.crans.org'], - 'adm.v6.crans.org': ['v6.adm.crans.org'], - 'ferme.v6.crans.org': ['v6.ferme.crans.org'], - }) - - # Résolution inverse - zones_reverse = config.dns.zones_reverse - zones_v6_to_net = { - 'crans.eu': config.prefix["fil"][0], - 'crans.org': config.prefix["fil"][0], - 'wifi.crans.org': config.prefix["wifi"][0], - 'adm.crans.org': config.prefix["adm"][0], - 'ferme.crans.org': config.prefix["fil"][0], - # Hack pour générer un fichier de zone vide - '##HACK##': config.prefix["subnet"][0], - } ### Liste DNS # Le premier doit être le maitre - DNSs = config.dns.DNSs - DNSs_private = [] - ip_master_DNS = config.dns.master - - zone_multicast = config.dns.zone_tv ### 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 l'IP sans point ou le nom avec un point - DELEG = { - zone_multicast : [ 'sable.crans.org.' , 'mdr.crans.org.', 'freebox.crans.org.', 'ovh.crans.org.'] , - } + # 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 = ['10 redisdead.crans.org', '20 ovh.crans.org', '25 freebox.crans.org'] - SRVs = [ - '_jabber._tcp.crans.org. 86400 IN SRV 5 0 5269 xmpp.crans.org.', - '_xmpp-server._tcp.crans.org. 86400 IN SRV 5 0 5269 xmpp.crans.org.', - '_xmpp-client._tcp.crans.org. 86400 IN SRV 5 0 5222 xmpp.crans.org.', - '_sip._udp.crans.org. 86400 IN SRV 5 0 5060 asterisk.crans.org.', - '_sip._tcp.crans.org. 86400 IN SRV 5 0 5060 asterisk.crans.org.', - '_sips._tcp.crans.org. 86400 IN SRV 5 0 5061 asterisk.crans.org.', - ] - - NATPRs = [ - '@ 86400 IN NAPTR 5 100 "S" "SIPS+D2T" "" _sips._tcp.crans.org.', - '@ 86400 IN NAPTR 10 100 "S" "SIP+D2U" "" _sip._udp.crans.org.', - '@ 86400 IN NAPTR 15 100 "S" "SIP+D2T" "" _sip._tcp.crans.org.', - ] - - # 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 - DS = { - 'crans.eu': [ - 'wifi.crans.eu. 3600 IN DS 37582 8 2 51809b508e450d8fec44572a3fa31754c27507465775c7d1c86570abd7a21024', - 'v6.crans.eu. 3600 IN DS 12562 8 2 0a8e92398c5213cf2907b79f0fa8bd7db6729e0d2f6ca7443ac5a4b8441e38bd', - ], - 'v6.crans.eu' : [ - 'wifi.v6.crans.eu. 3600 IN DS 1799 8 2 52a40a7dfb3e9c88aee032c21c59be756c8d3de29149c408ed8b699d83e30032', - ], - 'crans.org': [ - 'v6.crans.org. 3600 IN DS 23641 8 2 3fff97a2581f0f2f49257b4914d5badf8ccb0a49c5a6f4cbf2f520b97de332d0', - 'adm.crans.org. 3600 IN DS 565 8 2 498f6cd5bcf291aae4129700a7569fa6e9a86821185bd655f0b9efc6a3bf547e', - 'ferme.crans.org. 3600 IN DS 35156 8 2 b63a1443b3d7434429e879e046bc8ba89056cdcb4b9c3566853e64fd521895b8', - 'wifi.crans.org. 3600 IN DS 41320 8 2 024799c1d53f1e827f03d17bc96709b85ee1c05d77eb0ebeadcfbe207ee776a4', - 'tv.crans.org. 3600 IN DS 30910 8 2 3317f684081867ab94402804fbb3cd187e29655cc7f34cb92c938183fe0b71f5', - ], - 'v6.crans.org' : [ - 'adm.v6.crans.org. 3600 IN DS 1711 8 2 f154eeb8eb346d2ca5cffb3f9cc464a17c0c4d69ee425b4fe44eaed7f5dd253b', - 'ferme.v6.crans.org. 3600 IN DS 44434 8 2 fb87cb4216599cb6574add543078a9e48d0e50438483386585a9960557434ab0', - 'wifi.v6.crans.org. 3600 IN DS 59539 8 2 dbe86f2f2e92d6a27bd1436f03ec1588f2948a2aa02124de0383be801cced85e', + 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), + ] + } - ### Entète des fichiers de zone - zone_entete=""" -$ORIGIN %(zone)s. -$TTL 3600 -@\tIN\tSOA %(serveur_autoritaire)s. root.crans.org. ( - %(serial)i ; numero de serie - 21600 ; refresh (s) - 3600 ; retry (s) - 1209600 ; expire (s) - 3600 ; TTL (s) - ) -""" + # 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'), + ] + } - # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître - zone_template=""" -zone "%(NOM_zone)s" { - type master; - file "%(FICHIER_zone)s"; -}; -""" - # Syntaxe utilisée dans le fichier DNS_CONF_BCFG2 pour définir une zone sur un esclave - zone_template_slave=""" -zone "%(NOM_zone)s" { - type slave; - file "%(FICHIER_zone)s"; - masters { %(ip_master_DNS)s; }; -}; -""" - - ### Verbosité - # Si =2, ralera (chaine warnings) si machines hors zone trouvée - # Si =1, comme ci-dessus, mais ne ralera pas pour freebox - # Si =0, ralera seulement contre les machines ne pouvant être classées - verbose = 1 hostname = short_name(gethostname()) - if hostname == short_name(DNSs[0]): + serial = int(time.time()) + 1000000000 + TTL = 3600 + + 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' - ######################################FIN PARTIE DE CONFIGURATION - def __str__(self) : - return "DNS" - - def reverse(self, 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 == 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) + def gen_soa(self, ns_list, serial, ttl): + return SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl) - def is_ascii(self, s): - return all(ord(c) < 128 for c in s) - - def gen_tv(self): - sys.path.append('/usr/scripts') - import tv.dns - serial = time.time() + 1000000000 - zone_reverse=netv4_to_arpa(config.NETs['multicast'][0])[0] - sap=open('/usr/scripts/var/tv/sap.txt').readlines() - - DNS='; DNS de la zone par ordre de priorité\n' - for d in self.DELEG[self.zone_multicast] : - DNS += '@\tIN\tNS %s\n' % d - DNS += '\n' - - lignes_d = '\n' - lignes_r = '\n' - lignes_d +='@\tIN\tA\t%s\n' % '138.231.136.88' - for line in sap: - [nom,ip]=line.split(':') - nom=unicode(nom, 'utf-8') - nom_ascii=tv.dns.ascii(nom) - nom_punycode=tv.dns.punycode(nom) - try: - [ip1,ip2,ip3,ip4]=ip.strip().split('.') - lignes_r += '%s.%s.%s\tIN\tPTR\t%s.%s.\n' % (ip4,ip3,ip2,nom_ascii,self.zone_multicast) - lignes_d +='%s\tIN\tA\t%s' % (nom_ascii,ip) - if nom_punycode: - lignes_d +='%s\tIN\tA\t%s' % (nom_punycode, ip) - except: - raise - # Écriture de la zone directe - file = self.DNS_DIR + 'db.' + self.zone_multicast - fd = self._open_conf(file,';') - fd.write(self.zone_entete % \ - { 'zone' : self.zone_multicast, 'serveur_autoritaire' : self.DELEG[self.zone_multicast][0][0:-1] , 'serial' : serial } ) - fd.write('\n') - fd.write(DNS) - fd.write(lignes_d) - fd.close() - - # Écriture du reverse - file = self.DNS_DIR + 'db.' + zone_reverse - fd = self._open_conf(file,';') - fd.write(self.zone_entete % \ - { 'zone' : zone_reverse, 'serveur_autoritaire' : self.DELEG[self.zone_multicast][0][0:-1] , 'serial' : serial } ) - fd.write('\n') - fd.write(DNS) - fd.write(lignes_r) - fd.close() - - - def gen_slave(self) : - """ Génération du fichier de config de zone pour les esclaves """ - zones = self.zones_direct - zones.extend(self.zones_v4_to_v6.values()) - zones.extend([z for l in self.zone_alias.values() for z in l]) - zones = list(set(zones)) - zones.sort() - - # Ajout des zones reverse - for net in self.zones_reverse: - # IPv4 reverse - zones.extend(netv4_to_arpa(net)) - - for net in set(self.zones_v6_to_net.values()): - # IPv6 reverse - zones.append(netv6_to_arpa(net)) - - # Ecriture - fd = self._open_conf(self.DNS_CONF_BCFG2,'//') - for zone in zones : - if zone in self.zones_dnssec: - path=self.DNSSEC_DIR + 'db.' + zone - else: - path=self.DNS_DIR + 'db.' + zone - fd.write(self.zone_template_slave % { 'NOM_zone' : zone, - 'FICHIER_zone' : path, - 'ip_master_DNS': self.ip_master_DNS}) - - fd.close() - - def _gen(self) : - ### Génération du numéro de série - # Le + 1000.... s'explique pas l'idée précédente et peu pratique d'avoir - # le numéro de série du type AAAAMMJJNN (année, mois, jour, incrément par jour) - serial = time.time() + 1000000000 - - ### DNS - DNS='; DNS de la zone par ordre de priorité\n' - for d in self.DNSs : - DNS += '@\tIN\tNS %s.\n' % d - DNS += '\n' - - ### Serveurs de mail - MX='; Serveurs de mails\n' - for m in self.MXs : - MX += '@\t' # Sera remplacé par le nom de zone plus tard - MX += 'IN\tMX\t%s.\n' % m - MX += '\n' - - direct = {} # format : { zone : [ lignes correspondantes] } - reverse = {} - warnings = '' - - direct['crans.org'] = "" - - # P'tit lien vers irc.rezosup.org - #direct["crans.org"] = "\n; irc.crans.org -> irc.rezosup.org\n" - #direct["crans.org"] += "irc\tIN\tCNAME\tirc.rezosup.org.\n\n" - - ### Tri des machines - self.anim.iter=len(self.machines) - for machine in self.machines : + def populate_zones(self, zones, machines): + self.anim.iter=len(zones.values()) + for zone in zones.values(): + 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() - # Calculs préliminaires - try : - nom , zone = machine.nom().split('.',1) - zone = zone.encode('utf-8') - except : - warnings += u'Machine ignorée (mid=%s) : format nom incorrect (%s)\n' % ( machine.id().encode('utf-8'), machine.nom().encode('utf-8') ) - continue + return zones - # Le direct - if zone in self.zones_direct : - ligne='' - if machine.ip() != '': - ligne += "%s\tIN\tA\t%s\n" % ( nom, machine.ip() ) - # Si la machine est une borne wifi, on ajoute la position - if isinstance(machine,ldap_crans.BorneWifi) and machine.position(): - ligne +="%s\tIN\tTXT\t\"LOC %s,%s \"\n" % (nom,machine.position()[0],machine.position()[1]) - # Si la machine à des clefs ssh, on ajoute les champs SSFP correspondant - for sshkey in machine.sshFingerprint(): - try: - [algo_txt,key]=sshkey.split()[:2] - algo=None - for value in config.sshfp_algo.values(): - if algo_txt == value[1]: - algo=value[0] - break - if not algo: - raise ValueError("Invalid Algorithms %s" % algo_txt) - key1=hashlib.sha1(base64.b64decode(key)).hexdigest() - key2=hashlib.sha256(base64.b64decode(key)).hexdigest() - ligne +="%s\tIN\tSSHFP\t%s\t1\t%s\n" % (nom,algo,key1) - ligne +="%s\tIN\tSSHFP\t%s\t2\t%s\n" % (nom,algo,key2) - except(ValueError,TypeError): pass - direct[zone] = direct.get(zone, "") + ligne - if isinstance(machine,ldap_crans.BorneWifi): - direct['ap.crans.org'] = direct.get('ap.crans.org', "") + ligne - elif self.verbose and machine.nom() != "ftp.federez.net": - warnings += u'Résolution directe ignorée (mid=%s) : zone non autoritaire (%s)\n' % ( machine.id().encode('utf-8'), zone.encode('utf-8') ) + def gen_zones_ldap(self, ttl, ns_list, serial, zones={}, zones_ldap=config.dns.zones_ldap): + for zone in zones_ldap: + zones[zone]=Zone(zone, ttl, self.gen_soa(ns_list, serial, ttl), ns_list, other_zones=config.dns.zones_direct) + return zones - # IPv6 - if zone in self.zones_v4_to_v6: - # Direct - zone_v6 = self.zones_v4_to_v6[zone] - ipv6 = machine.ipv6() - net_v6 = machine.netv6() - ligne = "%s\tIN\tAAAA\t%s\n" % (nom, ipv6) - direct[zone_v6] = direct.get(zone_v6, "") + ligne - if machine.dnsIpv6(): - direct[zone] = direct.get(zone, "") + ligne - # Reverse - zone_rev, length = self.reverse(net_v6, ipv6) - rev = '.'.join(ipv6.reverse_dns.split('.')[:length]) - ligne = "%s\tIN\tPTR\t%s.\n" % (rev, machine.nom6()) - reverse[zone_rev] = reverse.get(zone_rev, "") + ligne - - - # Le direct avec alias - for alias in machine.alias() : - alias = alias.encode('utf-8') - # Cas particulier : nom de l'alias = nom de la zone - if alias in self.zones_direct : - if machine.ip() != '': - ligne = "@\tIN\tA\t%s\n" % machine.ip() - ligne = ligne.encode('utf-8') - direct[alias] = direct.get(alias, "") + ligne - if machine.dnsIpv6(): - ligne = "@\tIN\tAAAA\t%s\n" % machine.ipv6() - ligne = ligne.encode('utf-8') - direct[alias]= direct.get(alias, "") + ligne - if alias in self.zones_v4_to_v6: - ligne = "@\tIN\tAAAA\t%s\n" % machine.ipv6() - ligne = ligne.encode('utf-8') - zone6 = self.zones_v4_to_v6[alias] - direct[zone6] = direct.get(zone6, '') + ligne - continue - - # Bon format ? - alias_l = alias.split('.') - ok = 0 - for i in range(len(alias_l)) : - zone_essai = '.'.join(alias_l[i:]) - if zone_essai in self.zones_direct : - # On est autoritaire sur cette zone - # On place donc l'alias dans le fichier de cette zone - zone = zone_essai - nom = '.'.join(alias_l[:i]) - ok = 1 - break - if not ok: - warnings += u'Alias ignoré (mid=%s) : %s\n' % ( machine.id().encode('utf-8'), alias.encode('utf-8') ) - continue - zone = zone.encode('utf-8') - ligne = "%s\tIN\tCNAME\t%s.\n" % ( nom, machine.nom() ) - direct[zone] = direct.get(zone, '') + ligne - if zone in self.zones_v4_to_v6: - zone6 = self.zones_v4_to_v6[zone] - ligne = "%s\tIN\tCNAME\t%s.\n" % ( nom, machine.nom6() ) - direct[zone6] = direct.get(zone6, '') + ligne - - # Le reverse - ip = machine.ip() - if ip == '': - net=False - continue + def gen_zones_reverse(self, ttl, ns_list, serial, zones={}, + zones_reverse_v4=config.dns.zones_reverse, zones_reverse_v6=config.dns.zones_reverse_v6): + # reverse ipv4 + for net in zones_reverse_v4: + 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: - net = AddrInNets(ip, self.zones_reverse) - if net: - base_ip = ip.split('.') - base_ip.reverse() - zone, length = self.reverse(net, ip) - zone = zone.encode('utf-8') - ligne = '%s\tIN\tPTR\t%s.\n' % ('.'.join(base_ip[:length]), machine.nom()) - try : reverse[zone] += ligne - except : reverse[zone] = ligne - elif self.verbose >= 2 or machine.nom() not in ('freebox.crans.org', 'ovh.crans.org', 'kokarde.crans.org'): - warnings += u'Résolution inverse ignorée (mid=%s) : ip sur zone non autoritaire (%s)\n' % ( machine.id().encode('utf-8'), machine.ip().encode('utf-8') ) + subnets = net.subnet(8) + for subnet in subnets: + zones[str(subnet)]=ZoneReverse(str(subnet), ttl, self.gen_soa(ns_list, serial, ttl), ns_list) + # reverse ipv6 + for net in zones_reverse_v6: + zones[net]=ZoneReverse(net, ttl, self.gen_soa(ns_list, serial, ttl), ns_list) + return zones - ### Ajouts pour les fichiers de résolution directs - for zone in direct.keys() : - # MXs - direct[zone] = MX + direct[zone] - - ### XXX: création de la zone inverse pour le /48 IPv6 complet du Cr@ns - full_net_v6 = self.zones_v6_to_net["##HACK##"] - zone_rev, length = self.reverse(full_net_v6, netaddr.IPNetwork(full_net_v6).first) - reverse[zone_rev] = reverse.get(zone_rev, "") - - ### Alias de zone - zone_todo = [zone for zone in self.zone_alias] - while zone_todo: - for zone in zone_todo: - for alias in self.zone_alias[zone]: - try: - direct[alias] = direct[zone] - zone_todo.remove(zone) - except KeyError: - pass - if alias in self.zones_v4_to_v6: - alias_v6=self.zones_v4_to_v6[alias] - zone_v6 = self.zones_v4_to_v6[zone] - direct[alias_v6] = direct[zone_v6] - - ### Ajout des parametres SPF - direct['crans.org'] +='; Parametres SPF\n' - direct['crans.org'] +='crans.org.\tIN\tSPF\t"v=spf1 a mx ip6:2a01:240:fe3d:4::/64 ?all"\n' - for m in self.MXs: - direct['crans.org'] +='%s.\tIN\tSPF\t"v=spf1 a ip6:2a01:240:fe3d:4::/64 ?all"\n' % m.split()[-1] - direct['crans.org'] += '\n' - - direct['crans.ens-cachan.fr'] += '; Parametres SPF\n' - direct['crans.ens-cachan.fr'] += 'crans.ens-cachan.fr.\tIN\tSPF\t"v=spf1 a:crans.org mx ip6:2a01:240:fe3d:4::/64 ?all"\n\n' - - ### Ajout d'eventuels champs SRV - direct['crans.org'] +='; Champs SRV\n' - for s in self.SRVs: - direct['crans.org'] += s + '\n' - for s in self.NATPRs: - direct['crans.org'] += s + '\n' - direct['crans.org'] += '\n' - - ### Ajout des délégations de zones - for deleg in self.DELEG.keys(): - nom, zone = deleg.split('.',1) - if not zone in direct.keys(): - warnings += u'Délégation ignorée %s : on ne génère pas la zone parent\n' % deleg - continue - for serv in self.DELEG[deleg]: - direct[zone] = direct[zone] + "%s\tIN\tNS\t%s\n" % ( nom, serv ) - - for zone in direct.keys(): - child, parent = zone.split('.',1) - if not zone in self.DELEG.keys() and parent in self.zones_direct + [z for l in self.zone_alias.values() for z in l] + self.zones_v4_to_v6.values(): - for d in self.DNSs: - direct[parent] = direct.get(parent, "") + '%s\tIN\tNS %s.\n' % (child, d) + def gen_zones_clone(self, ttl, ns_list, serial, zones={}): + for zone_clone, zones_alias in config.dns.zone_alias.items(): + for zone in zones_alias: + zones[zone]=ZoneClone(zone, zones[zone_clone], self.gen_soa(ns_list, serial, 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 - ### Ajout d'eventuel champs DS pour les délégation dnssec - for zone,ds in self.DS.items(): - for s in ds: - direct[zone] += s + '\n' - direct[zone] += '\n' + def gen_zones(self, ttl, serial, ns_list, populate=True): + zones = {} + self.gen_zones_ldap(ttl, ns_list, serial, zones) + self.gen_zones_reverse(ttl, ns_list, serial, zones) - ### Ecriture des fichiers de zone et préparation du fichier de définition - f = '' - for zone, lignes in direct.items() + reverse.items() : - if zone in self.zones_dnssec: - path = self.DNSSEC_DIR + 'db.' + zone - else: - path = self.DNS_DIR + 'db.' + zone - file = self.DNS_DIR + 'db.' + zone - fd = self._open_conf(file,';') - fd.write(self.zone_entete % \ - { 'zone' : zone, 'serveur_autoritaire' : self.DNSs[0] , 'serial' : serial } ) - fd.write('\n') - fd.write(DNS) - fd.write(lignes) - fd.close() - os.chmod(file,0664) - if short_name(gethostname()) in map(short_name, dns.DNSs[1:]): - f += self.zone_template_slave % {'NOM_zone': zone, 'FICHIER_zone': path, - 'ip_master_DNS': self.ip_master_DNS} - else: - f += self.zone_template % { 'NOM_zone' : zone, 'FICHIER_zone' : path } + if populate: + conn = lc_ldap.shortcuts.lc_ldap_admin() + machines = conn.search(u"mid=*", sizelimit=10000) + machines.extend(conn.machinesMulticast()) + self.populate_zones(zones, machines) - ### Ecriture fichier de définition - fd = self._open_conf(self.DNS_CONF,'//') - fd.write(f) - fd.close() + # Doit être fait après populate_zones lorsque l'on a l'intention d'écrire les fichiers de zone + # En effet, la génération de la zone clone dépend du contenue de la zone originale + self.gen_zones_clone(ttl, ns_list, serial, zones) + return zones - return warnings + + def gen_tv(self, populate=True): + self.anim = affich_tools.anim('\tgénération de la zone tv') + zones = {} + serial = self.serial + self.gen_zones_reverse(self.TTL, config.dns.DNSs, serial, zones, zones_reverse_v4=config.NETs['multicast'], zones_reverse_v6=[]) + self.gen_zones_ldap(self.TTL, config.dns.DNSs, serial, zones, zones_ldap=[config.dns.zone_tv]) + + if populate: + conn = lc_ldap.shortcuts.lc_ldap_admin() + machines=conn.machinesMulticast() + self.populate_zones(zones, machines) + + for zone in zones.values(): + zone.write(self.DNS_DIR + 'db.' + zone.zone_name) + + self.anim.reinit() + print affich_tools.OK + 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(self.TTL, self.serial, config.dns.DNSs) + with open(self.DNS_CONF, 'w') as f: + f.write(disclamer) + 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(self.TTL, self.serial, config.dns.DNSs, populate=False) + with open(self.DNS_CONF_BCFG2, 'w') as f: + f.write(disclamer) + 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" if __name__ == '__main__' : - from config import bcfg2_main hostname = short_name(gethostname()) - if hostname == short_name(bcfg2_main): + if hostname == short_name(config.bcfg2_main): print "Reconfiguration du fichier de BCfg2 pour configurer le bind d'un serveur en esclave (pensez à lancer bcfg2 sur les esclaves)." c = dns() c.gen_slave() - if hostname == short_name(dns.DNSs[0]): - print "Ce serveur est également serveur maitre, mais la reconfiguration du DNS maitre se fait par generate." - elif hostname == short_name(dns.DELEG[config.dns.zone_tv][0][0:-1]): - print "Serveur maître pour tv.crans.org, génération de la zone" + elif hostname == short_name(config.dns.DNSs[0]): + print "Serveur maître :" c = dns() - c.gen_tv() + zones = c.gen_tv() import subprocess - args="/usr/sbin/ods-signer sign tv.crans.org".split() - p=subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - ret=p.communicate() - print ret[0].strip() - print ret[1].strip() - if hostname == short_name(dns.DNSs[0]): - print "Ce serveur est également serveur maitre, mais la reconfiguration du DNS maitre se fait par generate." - elif hostname == short_name(dns.DNSs[0]): - print "Ce serveur est maître ! Utilisez generate." - elif hostname in map(lambda fullhostname : short_name(fullhostname),dns.DNSs[1:]+dns.DNSs_private): + for zone in zones.values(): + if zone.zone_name in config.dns.zones_dnssec: + args=("/usr/sbin/ods-signer sign %s" % zone.zone_name).split() + p=subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + ret=p.communicate() + print ret[0].strip() + if ret[1].strip(): + print ret[1].strip() + print "Ce serveur est également serveur maitre pour les autres zones dns, mais leur reconfiguration se fait par generate." + elif hostname in map(lambda fullhostname : short_name(fullhostname),config.dns.DNSs[1:]): print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % bcfg2_main else: print "Ce serveur ne correspond à rien pour la configuration DNS." + diff --git a/gestion/gen_confs/bind2.py b/gestion/gen_confs/bind2.py deleted file mode 100755 index b85b20aa..00000000 --- a/gestion/gen_confs/bind2.py +++ /dev/null @@ -1,532 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -import sys -import time -import base64 -import hashlib -import netaddr -sys.path.append('/usr/scripts/') - -import lc_ldap.shortcuts -import affich_tools -from gestion.gen_confs import gen_config -from socket import gethostname -from gestion import config -import config.dns - -disclamer = """//**************************************************************// -// Ce fichier est genere par les scripts de gen_confs // -// Les donnees proviennent de la base LDAP et de la conf // -// presente au debut du script. Il peut être propagé via bcfg2. // -// // -// NE PAS EDITER // -// // -//**************************************************************// -""" - -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): - self._type=type - self._name=name - self._value=value - self._ttl=ttl - - def __str__(self): - if self._ttl: - return "%s\t%s\tIN\t%s\t%s" % (self._name, self._ttl, self._type, self._value) - else: - return "%s\tIN\t%s\t%s" % (self._name, self._type, self._value) - def __repr__(self): - return str(self) - -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)) -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) -class AAAA(ResourceRecord): - def __init__(self, name, value, ttl=None): - super(AAAA, self).__init__('AAAA', name, value, ttl) -class TXT(ResourceRecord): - def __init__(self, name, value, ttl=None): - super(TXT, self).__init__('TXT', name, value, ttl) -class CNAME(ResourceRecord): - def __init__(self, name, value, ttl=None): - super(CNAME, self).__init__('CNAME', name, value, ttl) -class DNAME(ResourceRecord): - def __init__(self, name, value, ttl=None): - super(DNAME, self).__init__('DNAME', name, value, ttl) -class MX(ResourceRecord): - def __init__(self, name, priority, value, ttl=None): - super(MX, self).__init__('MX', name, '%s\t%s' % (priority, value), ttl) -class NS(ResourceRecord): - def __init__(self, name, value, ttl=None): - super(NS, self).__init__('NS', name, value, ttl) -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, 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, 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): - if not hash in config.sshfp_hash.keys(): - raise ValueError('Hash %s invalid, valid hash are %s' % (hash, ', '.join(config.sshfp_host.keys()))) - if not algo in config.sshfp_algo.keys(): - raise ValueError('Algo %s unknown, valid values are %s' % (algo, ', '.join(config.sshfp_algo.keys()))) - 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, 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="%s\n$ORIGIN %s.\n$TTL %s\n" % (disclamer.replace('//', ';'), self.zone_name, self.ttl) - for rr in self._rrlist: - ret+="%s\n" % rr - return ret - - 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__(zone_name) - self.zone_clone = zone_clone - self.ttl = zone_clone.ttl - - self.add(soa) - self.add(DNAME('', "%s." % self.zone_clone.zone_name)) - for rr in self.zone_clone._rrlist[1:]: - if rr._name in ['', '@']: - self.add(rr) - if rr._name in ["%s." % self.zone_clone.zone_name]: - self.add(ResourceRecord(rr._type, "%s." % self.zone_name, rr._value)) - - -class Zone(ZoneBase): - def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, other_zones=[]): - super(Zone, self).__init__(zone_name) - self.ttl = ttl - self.ipv4 = ipv4 - self.ipv6 = ipv6 - self.other_zones = other_zones - self.subzones = [z for z in self.other_zones if z != self.zone_name and z.endswith(self.zone_name)] - - self.add(soa) - for ns in ns_list: - self.add(NS('@', '%s.' % ns)) - - def name_in_subzone(self, hostname): - for zone in self.subzones: - if str(hostname).endswith(".%s" % zone): - return True - return False - - def get_name(self, hostname): - # 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 self.name_in_subzone(hostname): - ret=str(hostname)[0:- len(self.zone_name) -1] - if ret == "": - return "@" - else: - return ret - else: - return None - - def add_delegation(zone, server): - zone = self.het_name(zone) - if zone: - self.add(NS('@', '%s.' % server)) - - def add_a_record(self, nom, machine): - if self.ipv4: - for ip in machine.get('ipHostNumber', []): - self.add(A(nom, ip)) - if self.ipv6: - if nom == '@': - self.add(A("v4", ip)) - else: - self.add(A("%s.v4" % nom, ip)) - - def add_aaaa_record(self, nom, machine): - if self.ipv6: - for ip in machine.get('ip6HostNumber', []): - 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)) - else: - self.add(AAAA("%s.v6" % nom, ip)) - - def add_sshfp_record(self, nom, machine): - for sshkey in machine.get('sshFingerprint', []): - algo_txt, key = str(sshkey).split()[:2] - algo=config.sshfs_ralgo[algo_txt][1] - for hash in config.sshfp_hash.keys(): - self.add(SSHFP(nom, hash, algo, key)) - if self.ipv4 and self.ipv6: - if nom == '@': - self.add(SSHFP("v4", hash, algo, key)) - self.add(SSHFP("v6", hash, algo, key)) - else: - self.add(SSHFP("%s.v4" % nom, hash, algo, key)) - self.add(SSHFP("%s.v6" % nom, hash, algo, key)) - - def add_machine(self, machine): - for host in machine['host']: - nom=self.get_name(host) - if nom is None: continue - - self.add_a_record(nom, machine) - self.add_aaaa_record(nom, machine) - self.add_sshfp_record(nom, machine) - - - if machine['host']: - for alias in machine.get('hostAlias', []): - if str(alias) in self.other_zones 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) - if alias in ['@', '%s.' % self.zone_name]: - self.add_a_record(alias, machine) - self.add_aaaa_record(alias, machine) - self.add_sshfp_record(alias, machine) - elif to_zone == self.zone_name: - self.add(CNAME(alias, "%s" % to_nom)) - if self.ipv4 and self.ipv6: - self.add(CNAME("%s.v4" % alias, "%s.v4" % to_nom)) - self.add(CNAME("%s.v6" % alias, "%s.v6" % to_nom)) - else: - self.add(CNAME(alias, "%s." % machine['host'][0])) - - -class ZoneReverse(Zone): - def __init__(self, net, ttl, soa, ns_list): - self.net = net - zone_name = reverse(net, net.split('/')[0])[0] - if '.' in net: - ipv6=False - ipv4=True - elif ':' in net: - ipv6=True - ipv4=False - else: - raise ValueError("net should be an ipv4 ou ipv6 network") - super(ZoneReverse, self).__init__(zone_name, ttl, soa, ns_list, ipv6=ipv6, ipv4=ipv4) - - - def add_machine(self, machine): - if machine['host']: - if self.ipv4: - 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 - 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()) - serial = int(time.time()) + 1000000000 - TTL = 3600 - - 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_soa(self, ns_list, serial, ttl): - return SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl) - - - def populate_zones(self, zones, machines): - self.anim.iter=len(zones.values()) - for zone in zones.values(): - 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() - return zones - - def gen_zones_ldap(self, ttl, ns_list, serial, zones={}, zones_ldap=config.dns.zones_ldap): - for zone in zones_ldap: - zones[zone]=Zone(zone, ttl, self.gen_soa(ns_list, serial, ttl), ns_list, other_zones=config.dns.zones_direct) - return zones - - def gen_zones_reverse(self, ttl, ns_list, serial, zones={}, - zones_reverse_v4=config.dns.zones_reverse, zones_reverse_v6=config.dns.zones_reverse_v6): - # reverse ipv4 - for net in zones_reverse_v4: - 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: - zones[str(subnet)]=ZoneReverse(str(subnet), ttl, self.gen_soa(ns_list, serial, ttl), ns_list) - # reverse ipv6 - for net in zones_reverse_v6: - zones[net]=ZoneReverse(net, ttl, self.gen_soa(ns_list, serial, ttl), ns_list) - return zones - - def gen_zones_clone(self, ttl, ns_list, serial, zones={}): - for zone_clone, zones_alias in config.dns.zone_alias.items(): - for zone in zones_alias: - zones[zone]=ZoneClone(zone, zones[zone_clone], self.gen_soa(ns_list, serial, 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_zones(self, ttl, serial, ns_list, populate=True): - zones = {} - self.gen_zones_ldap(ttl, ns_list, serial, zones) - self.gen_zones_reverse(ttl, ns_list, serial, zones) - - if populate: - conn = lc_ldap.shortcuts.lc_ldap_admin() - machines = conn.search(u"mid=*", sizelimit=10000) - machines.extend(conn.machinesMulticast()) - self.populate_zones(zones, machines) - - # Doit être fait après populate_zones lorsque l'on a l'intention d'écrire les fichiers de zone - # En effet, la génération de la zone clone dépend du contenue de la zone originale - self.gen_zones_clone(ttl, ns_list, serial, zones) - return zones - - - def gen_tv(self, populate=True): - self.anim = affich_tools.anim('\tgénération de la zone tv') - zones = {} - serial = self.serial - self.gen_zones_reverse(self.TTL, config.dns.DNSs, serial, zones, zones_reverse_v4=config.NETs['multicast'], zones_reverse_v6=[]) - self.gen_zones_ldap(self.TTL, config.dns.DNSs, serial, zones, zones_ldap=[config.dns.zone_tv]) - - if populate: - conn = lc_ldap.shortcuts.lc_ldap_admin() - machines=conn.machinesMulticast() - self.populate_zones(zones, machines) - - for zone in zones.values(): - zone.write(self.DNS_DIR + 'db.' + zone.zone_name) - - self.anim.reinit() - print affich_tools.OK - 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(self.TTL, self.serial, config.dns.DNSs) - with open(self.DNS_CONF, 'w') as f: - f.write(disclamer) - 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(self.TTL, self.serial, config.dns.DNSs, populate=False) - with open(self.DNS_CONF_BCFG2, 'w') as f: - f.write(disclamer) - 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" - - -if __name__ == '__main__' : - hostname = short_name(gethostname()) - if hostname == short_name(config.bcfg2_main): - print "Reconfiguration du fichier de BCfg2 pour configurer le bind d'un serveur en esclave (pensez à lancer bcfg2 sur les esclaves)." - c = dns() - c.gen_slave() - elif hostname == short_name(config.dns.DNSs[0]): - print "Serveur maître :" - c = dns() - zones = c.gen_tv() - import subprocess - for zone in zones.values(): - if zone.zone_name in config.dns.zones_dnssec: - args=("/usr/sbin/ods-signer sign %s" % zone.zone_name).split() - p=subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE) - ret=p.communicate() - print ret[0].strip() - if ret[1].strip(): - print ret[1].strip() - print "Ce serveur est également serveur maitre pour les autres zones dns, mais leur reconfiguration se fait par generate." - elif hostname in map(lambda fullhostname : short_name(fullhostname),config.dns.DNSs[1:]): - print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % bcfg2_main - else: - print "Ce serveur ne correspond à rien pour la configuration DNS." - diff --git a/gestion/gen_confs/generate.py b/gestion/gen_confs/generate.py index cc0be71e..033614bd 100755 --- a/gestion/gen_confs/generate.py +++ b/gestion/gen_confs/generate.py @@ -289,7 +289,7 @@ class isc(base_reconfigure): class sable(base_reconfigure): def dns(self): - from gen_confs.bind2 import dns + from gen_confs.bind import dns self._do(dns(), []) class routeur(base_reconfigure):