Commentaires et nettoyage dans bind.

This commit is contained in:
Pierre-Elliott Bécue 2015-03-03 13:29:08 +01:00
parent 15e83e1844
commit 4b652b69b6
3 changed files with 415 additions and 156 deletions

View file

@ -177,7 +177,7 @@ sshfp_algo = {
} }
sshfs_ralgo = {} sshfs_ralgo = {}
for key,value in sshfp_algo.items(): for key, value in sshfp_algo.items():
sshfs_ralgo[value[1]] = (value[0], key) sshfs_ralgo[value[1]] = (value[0], key)
sshfp_hash = { sshfp_hash = {

View file

@ -3,8 +3,10 @@
""" Variables de configuration pour la gestion du DNS """ """ Variables de configuration pour la gestion du DNS """
import os
# import des variables génériques # import des variables génériques
import config import gestion.config as config
#: ariane et ariane2 pour la zone parente #: ariane et ariane2 pour la zone parente
parents = [ parents = [
@ -28,38 +30,116 @@ slaves_tv = slaves
zone_tv = 'tv.crans.org' zone_tv = 'tv.crans.org'
#: DNS en connexion de secours #: DNS en connexion de secours
secours_relay='10.231.136.14'; secours_relay = '10.231.136.14';
#: Serveurs autoritaires pour les zones crans, le master doit être le premier #: Serveurs autoritaires pour les zones crans, le master doit être le premier
DNSs = ['sable.crans.org', 'freebox.crans.org', 'soyouz.crans.org'] DNSs = [
'sable.crans.org',
'freebox.crans.org',
'soyouz.crans.org',
]
MXs = {
'redisdead.crans.org': {
'prio': 10,
'spf': 'v=spf1 ptr ~all',
},
'freebox.crans.org': {
'prio': 25,
'spf': 'v=spf1 ptr ~all',
},
'soyouz.crans.org': {
'prio': 15,
'spf': 'v=spf1',
},
}
#: Résolution DNS directe, liste de toutes les zones crans hors reverse #: Résolution DNS directe, liste de toutes les zones crans hors reverse
zones_direct = [ 'crans.org', 'crans.ens-cachan.fr', 'wifi.crans.org', 'clubs.ens-cachan.fr', 'adm.crans.org','crans.eu','wifi.crans.eu', 'tv.crans.org', 'ap.crans.org' ] zones_direct = [
'crans.org',
'crans.ens-cachan.fr',
'wifi.crans.org',
'clubs.ens-cachan.fr',
'adm.crans.org',
'crans.eu',
'wifi.crans.eu',
'tv.crans.org',
'ap.crans.org',
]
#: Les zones apparaissant dans des objets lc_ldap #: Les zones apparaissant dans des objets lc_ldap
zones_ldap = [ 'crans.org', 'crans.ens-cachan.fr', 'wifi.crans.org', 'clubs.ens-cachan.fr', 'adm.crans.org', 'tv.crans.org' ] zones_ldap = [
'crans.org',
'crans.ens-cachan.fr',
'wifi.crans.org',
'clubs.ens-cachan.fr',
'adm.crans.org',
'tv.crans.org',
]
#: Zones signée par opendnssec sur le serveur master #: Zones signée par opendnssec sur le serveur master
zones_dnssec = ['crans.org', 'wifi.crans.org', 'adm.crans.org', 'tv.crans.org', 'crans.eu'] zones_dnssec = [
'crans.org',
'wifi.crans.org',
'adm.crans.org',
'tv.crans.org',
'crans.eu',
]
#: Zones alias : copie les valeur des enregistrement pour la racine de la zone et utilise un enregistemenr DNAME pour les sous domaines #: Zones alias : copie les valeur des enregistrement pour la racine de la zone et utilise un enregistemenr DNAME pour les sous domaines
zone_alias = { zone_alias = {
'crans.org' : ['crans.eu'], 'crans.org' : [
'crans.eu',
],
} }
#: Résolution inverse v4 #: Résolution inverse v4
zones_reverse = config.NETs["all"] + config.NETs["adm"] + config.NETs["personnel-ens"] + config.NETs['multicast'] zones_reverse = config.NETs["all"] + config.NETs["adm"] + config.NETs["personnel-ens"] + config.NETs['multicast']
#: Résolution inverse v6 #: Résolution inverse v6
zones_reverse_v6 = config.prefix['fil'] + config.prefix['wifi'] + config.prefix ['adm'] + config.prefix['personnel-ens'] # à modifier aussi dans bind.py zones_reverse_v6 = config.prefix['fil'] + config.prefix['wifi'] + config.prefix['adm'] + config.prefix['personnel-ens'] # à modifier aussi dans bind.py
#: Serveurs DNS récursifs : #: Serveurs DNS récursifs :
recursiv = { recursiv = {
'fil' : ['138.231.136.98', '138.231.136.152'], 'fil' : [
'wifi' : ['138.231.136.98', '138.231.136.152'], '138.231.136.98',
'evenementiel' : ['138.231.136.98', '138.231.136.152'], '138.231.136.152',
'adm' : ['10.231.136.98', '10.231.136.152'], ],
'gratuit' : ['10.42.0.164'], 'wifi' : [
'accueil' : ['10.51.0.10'], '138.231.136.98',
'isolement' : ['10.52.0.10'], '138.231.136.152',
'personnel-ens' : ['10.2.9.10', '138.231.136.98', '138.231.136.152'], ],
'evenementiel' : [
'138.231.136.98',
'138.231.136.152',
],
'adm' : [
'10.231.136.98',
'10.231.136.152',
],
'gratuit' : [
'10.42.0.164',
],
'accueil' : [
'10.51.0.10',
],
'isolement' : [
'10.52.0.10',
],
'personnel-ens' : [
'10.2.9.10',
'138.231.136.98',
'138.231.136.152',
],
} }
#: Les ip/net des vlans limité vue par les récursifs #: Les ip/net des vlans limité vue par les récursifs
menteur_clients = [ "138.231.136.210", "138.231.136.10" ] + config.prefix['evenementiel'] menteur_clients = [
"138.231.136.210",
"138.231.136.10",
] + config.prefix['evenementiel']
# Chemins de fichiers/dossiers utiles.
DNS_DIR = '/etc/bind/generated/'
DNSSEC_DIR = '/etc/bind/signed/'
# Fichier de définition des zones pour le maître
DNS_CONF = os.path.join(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"

View file

@ -6,6 +6,7 @@ Copyright (C) Valentin Samir
Licence : GPLv3 Licence : GPLv3
""" """
import os
import sys import sys
import ssl import ssl
import time import time
@ -37,24 +38,31 @@ def short_name(fullhostname):
return fullhostname.split(".")[0] return fullhostname.split(".")[0]
class ResourceRecord(object): class ResourceRecord(object):
def __init__(self, type, name, value, ttl=None): """Classe standard définissant une ressource DNS"""
self._type=type
self._name=name def __init__(self, r_type, name, value, ttl=None):
self._value=value """Affecte les valeurs de base de l'enregistrement"""
self._ttl=ttl self.r_type = r_type
self.name = name
self.value = value
self._ttl = ttl
def __str__(self): def __str__(self):
"""Retourne une chaîne printable dans un fichier bind"""
if self._ttl: if self._ttl:
return "%s\t%s\tIN\t%s\t%s" % (self._name, self._ttl, self._type, self._value) return "%s\t%s\tIN\t%s\t%s" % (self.name, self._ttl, self.r_type, self.value)
else: else:
return "%s\tIN\t%s\t%s" % (self._name, self._type, self._value) return "%s\tIN\t%s\t%s" % (self.name, self.r_type, self.value)
def __repr__(self): def __repr__(self):
"""__repr__ == __str__"""
return str(self) return str(self)
class TLSA(ResourceRecord): class TLSA(ResourceRecord):
def __init__(self, name, port, proto, cert, certtype, reftype, selector=0, compat=True, format='pem', ttl=None): """Enregistrement TLSA pour stocker des certifs dans un enregistrement DNS"""
"""
name: nom du domaine du certificat def __init__(self, name, port, proto, cert, certtype, reftype, selector=0, compat=True, r_format='pem', ttl=None):
""" name: nom du domaine du certificat
port: port écoute le service utilisant le certificat port: port écoute le service utilisant le certificat
proto: udp ou tcp proto: udp ou tcp
cert: le certificat au format ``format`` (pem ou der) (selector est donc toujours à 0) cert: le certificat au format ``format`` (pem ou der) (selector est donc toujours à 0)
@ -62,8 +70,9 @@ class TLSA(ResourceRecord):
reftype: 0 = plain cert, 1 = sha256, 2 = sha512 reftype: 0 = plain cert, 1 = sha256, 2 = sha512
compat: on génère un enregistement compris même par les serveurs dns n'implémentant pas TLSA compat: on génère un enregistement compris même par les serveurs dns n'implémentant pas TLSA
""" """
if not format in ['pem', 'der']: if not r_format in ['pem', 'der']:
raise ValueError("format should be pem or der") raise ValueError("format should be pem or der")
if cert is None and proto == 'tcp' and name[-1] == '.': if cert is None and proto == 'tcp' and name[-1] == '.':
try: try:
cert = ssl.get_server_certificate((name[:-1], port), ca_certs='/etc/ssl/certs/ca-certificates.crt') cert = ssl.get_server_certificate((name[:-1], port), ca_certs='/etc/ssl/certs/ca-certificates.crt')
@ -71,12 +80,15 @@ class TLSA(ResourceRecord):
raise ValueError("Unable de retrieve cert dynamically: %s" % e) raise ValueError("Unable de retrieve cert dynamically: %s" % e)
elif cert is None: elif cert is None:
raise ValueError("cert can only be retrive if proto is tcp and name fqdn") raise ValueError("cert can only be retrive if proto is tcp and name fqdn")
if format is not 'der':
if r_format is not 'der':
dercert = ssl.PEM_cert_to_DER_cert(cert) dercert = ssl.PEM_cert_to_DER_cert(cert)
else: else:
dercert = cert dercert = cert
if not dercert: if not dercert:
raise ValueError("Impossible de convertir le certificat au format DER %s %s %s\n%s" % (name, port, proto, cert)) raise ValueError("Impossible de convertir le certificat au format DER %s %s %s\n%s" % (name, port, proto, cert))
certhex = TLSA.hashCert(reftype, str(dercert)) certhex = TLSA.hashCert(reftype, str(dercert))
if compat: if compat:
super(TLSA, self).__init__( super(TLSA, self).__init__(
@ -87,15 +99,15 @@ class TLSA(ResourceRecord):
) )
else: else:
super(TLSA, self).__init__( super(TLSA, self).__init__(
'TLSA', 'TLSA',
'_%s._%s%s' % (port, proto, '.' + name if name else ''), '_%s._%s%s' % (port, proto, '.' + name if name else ''),
"%s %s %s %s"% (certtype, selector, reftype, certhex), "%s %s %s %s"% (certtype, selector, reftype, certhex),
ttl ttl
) )
@staticmethod @staticmethod
def hashCert(reftype, certblob): def hashCert(reftype, certblob):
""" """Retourne un hash d'un certif DER en MAJUSCULES.
certblob: un certificat au format DER certblob: un certificat au format DER
""" """
if reftype == 0: if reftype == 0:
@ -111,98 +123,164 @@ class TLSA(ResourceRecord):
return hashobj.hexdigest().upper() return hashobj.hexdigest().upper()
class SOA(ResourceRecord): class SOA(ResourceRecord):
"""Ressource pour une entrée DNS SOA"""
def __init__(self, master, email, serial, refresh, retry, expire, ttl): 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): class A(ResourceRecord):
"""Entrée DNS pour une IPv4"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(A, self).__init__('A', name, value, ttl) super(A, self).__init__('A', name, value, ttl)
class DS(ResourceRecord): class DS(ResourceRecord):
"""Entrée DNS pour l'empreinte d'une clef DNSSEC"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(DS, self).__init__('DS', name, value, ttl) super(DS, self).__init__('DS', name, value, ttl)
class PTR(ResourceRecord): class PTR(ResourceRecord):
"""Entrée DNS inverse (pour obtenir l'IP à partir du NDD"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(PTR, self).__init__('PTR', name, value, ttl) super(PTR, self).__init__('PTR', name, value, ttl)
class AAAA(ResourceRecord): class AAAA(ResourceRecord):
"""Entrée DNS pour une IPv6"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(AAAA, self).__init__('AAAA', name, value, ttl) super(AAAA, self).__init__('AAAA', name, value, ttl)
class TXT(ResourceRecord): class TXT(ResourceRecord):
"""Entrée DNS pour un champ TXT"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(TXT, self).__init__('TXT', name, value, ttl) super(TXT, self).__init__('TXT', name, value, ttl)
class CNAME(ResourceRecord): class CNAME(ResourceRecord):
"""Entrée DNS pour un alias (toto -> redisdead)"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(CNAME, self).__init__('CNAME', name, value, ttl) super(CNAME, self).__init__('CNAME', name, value, ttl)
class DNAME(ResourceRecord): class DNAME(ResourceRecord):
"""Entrée DNS pour un alias de domaine (crans.eu -> crans.org)"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(DNAME, self).__init__('DNAME', name, value, ttl) super(DNAME, self).__init__('DNAME', name, value, ttl)
class MX(ResourceRecord): class MX(ResourceRecord):
"""Entrée DNS pour un serveur mail. crans.org IN MX 5 redisdead.crans.org veut dire
que redisdead est responsable de recevoir les mails destinés à toto@crans.org avec
une priorité 5 (plus c'est faible, plus c'est prioritaire.
"""
def __init__(self, name, priority, value, ttl=None): def __init__(self, name, priority, value, ttl=None):
super(MX, self).__init__('MX', name, '%s\t%s' % (priority, value), ttl) super(MX, self).__init__('MX', name, '%s\t%s' % (priority, value), ttl)
class NS(ResourceRecord): class NS(ResourceRecord):
"""Entrée DNS pour donner les serveurs autoritaires pour un nom de domaine"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(NS, self).__init__('NS', name, value, ttl) super(NS, self).__init__('NS', name, value, ttl)
class SPF(ResourceRecord): class SPF(ResourceRecord):
"""Entrée DNS pour les champs SPF"""
def __init__(self, name, value, ttl=None): def __init__(self, name, value, ttl=None):
super(SPF, self).__init__('SPF', name, value, ttl) super(SPF, self).__init__('SPF', name, value, ttl)
class SRV(ResourceRecord): class SRV(ResourceRecord):
"""Entrée DNS pour les champs SRV"""
def __init__(self, service, proto, priority, weight, port, target, ttl=None): 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) super(SRV, self).__init__('SRV', '_%s._%s' % (service, proto), '%s\t%s\t%s\t%s' % (priority, weight, port, target), ttl)
class NAPTR(ResourceRecord): class NAPTR(ResourceRecord):
"""Entrée DNS pour les NAPTR"""
def __init__(self, name, 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) 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): class SSHFP(ResourceRecord):
def __init__(self, name, hash, algo, key, ttl=None): """Entrée DNS stockant une fingerprint SSH"""
if not hash in config.sshfp_hash.keys(): def __init__(self, name, r_hash, algo, key, ttl=None):
raise ValueError('Hash %s invalid, valid hash are %s' % (hash, ', '.join(config.sshfp_host.keys()))) """Vérifie que hash/algo sont supportés dans la config"""
if not r_hash in config.sshfp_hash.keys():
raise ValueError('Hash %s invalid, valid hash are %s' % (r_hash, ', '.join(config.sshfp_hash.keys())))
if not algo in config.sshfp_algo.keys(): if not algo in config.sshfp_algo.keys():
raise ValueError('Algo %s unknown, valid values are %s' % (algo, ', '.join(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): super(SSHFP, self).__init__('SSHFP', name, '%s\t%s\t%s' % (config.sshfp_algo[algo][0], config.sshfp_hash[r_hash], getattr(hashlib, r_hash)(base64.b64decode(key)).hexdigest()), ttl)
class ZoneBase(list):
"""Classe abstraite décrivant une zone.
Elle surcharge une liste, car l'ensemble des enregistrements de cette
zone sera contenu en elle-même."""
def __init__(self, zone_name): def __init__(self, zone_name):
self._rrlist=[] """Affecte un nom de zone"""
super(ZoneBase, self).__init__()
self.zone_name = zone_name self.zone_name = zone_name
self.ttl = 3600
def __repr__(self): def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.zone_name) return "<%s %s>" % (self.__class__.__name__, self.zone_name)
def __str__(self): def __str__(self):
ret="%s\n$ORIGIN %s.\n$TTL %s\n" % (disclamer.replace('//', ';'), self.zone_name, self.ttl) """Version enregistrable en fichier d'une zone."""
for rr in self._rrlist: _ret = "%s\n$ORIGIN %s.\n$TTL %s\n" % (disclamer.replace('//', ';'), self.zone_name, self.ttl)
ret+="%s\n" % rr for rr in self:
return ret _ret += "%s\n" % rr
return _ret
def add(self, rr): def add(self, rr):
"""Ajout d'un enregistrement DNS"""
if isinstance(rr, ResourceRecord): if isinstance(rr, ResourceRecord):
self._rrlist.append(rr) self.append(rr)
else: else:
raise ValueError("You can only add ResourceRecords to a Zone") 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): def write(self, path):
"""Pour dumper le tout dans le fichier idoine."""
with open(path, 'w') as f: with open(path, 'w') as f:
f.write("%s" % self) f.write("%s" % self)
class ZoneClone(ZoneBase): class ZoneClone(ZoneBase):
"""Zone clone d'une autre zone."""
def __init__(self, zone_name, zone_clone, soa): def __init__(self, zone_name, zone_clone, soa):
"""La zone clone possède, outre son nom, un pointeur vers
la zone qu'elle duplique.
Le SOA est fourni manuellement, et la première entrée de la zone clonée
est ignorée. (c'est a priori le SOA de celle-ci)
"""
super(ZoneClone, self).__init__(zone_name) super(ZoneClone, self).__init__(zone_name)
self.zone_clone = zone_clone self.zone_clone = zone_clone
self.ttl = zone_clone.ttl self.ttl = zone_clone.ttl
# On met un SOA custom.
self.add(soa) self.add(soa)
# On ajoute un DNAME, qui indique que la zone est un clone.
self.add(DNAME('', "%s." % self.zone_clone.zone_name)) self.add(DNAME('', "%s." % self.zone_clone.zone_name))
for rr in self.zone_clone._rrlist[1:]:
if rr._name in ['', '@']: # Et on extrait les données nécessaires de la zone clônée
# à savoir, celles de l'apex (la base du domaine, qui elle
# n'est pas clônée, seuls les sous-domaines le sont)
for rr in self.zone_clone[1:]:
# Si pas de nom ou si le nom est @, on duplique bêtement l'enregistrement
if rr.name in ['', '@']:
self.add(rr) self.add(rr)
if rr._name in ["%s." % self.zone_clone.zone_name]:
self.add(ResourceRecord(rr._type, "%s." % self.zone_name, rr._value)) # Si le nom de domaine concerné est celui de la zone clonée, pareil, on
# "duplique", en créant un enregistrement idoine.
if rr.name in ["%s." % self.zone_clone.zone_name]:
self.add(ResourceRecord(rr.r_type, "%s." % self.zone_name, rr.value))
class Zone(ZoneBase): class Zone(ZoneBase):
def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, other_zones=[]): """Une zone standard"""
def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, other_zones=None):
"""Héritage, plus quelques propriétés en plus
On définit ici si la zone comporte des ipv4/ipv6,
ainsi que des données utiles pour le comportement de celles-ci.
other_zones contient la liste de sous-zones "indépendantes".
(exemple avec wifi.crans.org qui est une sous-zone de crans.org)"""
if other_zones is None:
other_zones = []
super(Zone, self).__init__(zone_name) super(Zone, self).__init__(zone_name)
self.ttl = ttl self.ttl = ttl
self.ipv4 = ipv4 self.ipv4 = ipv4
@ -215,15 +293,26 @@ class Zone(ZoneBase):
self.add(NS('@', '%s.' % ns)) self.add(NS('@', '%s.' % ns))
def name_in_subzone(self, hostname): def name_in_subzone(self, hostname):
"""Teste si le nom qu'on observe est dans une
sous-zone (toto.wifi.crans.org. est dans wifi.crans.org., et non
dans crans.org..
"""
for zone in self.subzones: for zone in self.subzones:
if str(hostname).endswith(".%s" % zone): if str(hostname).endswith(".%s" % zone):
return True return True
return False return False
def get_name(self, hostname): def get_name(self, hostname):
# le hostname fini bien par la zone courante, et il n'appartient pas à une sous-zone """Retourne la base du nom d'un hôte. Teste si celui-ci appartient bien
à la zone courante et s'il n'est pas lié à une sous-zone.
Si tout est bon, le nom peut valoir "", auquel cas, l'entrée concerne le domaine
courant, donc @.
Dans le cas ce nom ne devrait pas être , on retourne None.
"""
if str(hostname) == self.zone_name or str(hostname).endswith(".%s" % self.zone_name) and not self.name_in_subzone(hostname): 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] ret = str(hostname)[0:-len(self.zone_name)-1]
if ret == "": if ret == "":
return "@" return "@"
else: else:
@ -232,65 +321,90 @@ class Zone(ZoneBase):
return None return None
def get_name_vi(self, nom, i): def get_name_vi(self, nom, i):
"""Kludge foireux pour retourner toto.v4.crans.org à partir
de toto.crans.org (sous-zones v4/v6)."""
if not i in [4, 6]: if not i in [4, 6]:
raise ValueError("i should be 4 or 6") raise ValueError("i should be 4 or 6")
if nom == '@': if nom == '@':
return 'v%s' % i return 'v%s' % i
# On considère que le "vrai" nom est la partie avant le premier .
elif '.' in nom: elif '.' in nom:
nom_1, nom_2 = nom.split('.', 1) nom_1, nom_2 = nom.split('.', 1)
return "%s.v%s.%s" % (nom_1, i, nom_2) return "%s.v%s.%s" % (nom_1, i, nom_2)
else: else:
return "%s.v%s" % (nom, i) return "%s.v%s" % (nom, i)
def add_delegation(zone, server): def add_delegation(self, zone, server):
zone = self.het_name(zone) """Lorsqu'on veut offrir une délégation DNS à une machine
pour un nom de domaine"""
zone = self.get_name(zone)
if zone: if zone:
self.add(NS('@', '%s.' % server)) self.add(NS('@', '%s.' % server))
def add_a_record(self, nom, machine): def add_a_record(self, nom, machine):
"""Ajout d'une entrée A."""
# Fait-on de l'IPv4 dans cette zone ?
if self.ipv4: if self.ipv4:
for ip in machine.get('ipHostNumber', []): for ip in machine.get('ipHostNumber', []):
self.add(A(nom, ip)) self.add(A(nom, ip))
# Fait-on aussi de l'IPv6 ?
if self.ipv6: if self.ipv6:
# Bon bah alors on ajoute nom.v4.crans.org en plus.
self.add(A(self.get_name_vi(nom, 4), ip)) self.add(A(self.get_name_vi(nom, 4), ip))
def add_aaaa_record(self, nom, machine): def add_aaaa_record(self, nom, machine):
"""Ajout d'une entrée AAAA (for the AAAAAAAAwesome)."""
# Fait-on de l'IPv6 dans cette zone ?
if self.ipv6: if self.ipv6:
for ip in machine.get('ip6HostNumber', []): for ip in machine.get('ip6HostNumber', []):
if machine.get('dnsIpv6', [True])[0]: # Si dnsIpv6 est à True dans la base LDAP, on ajoute l'entrée.
# On l'ajoute quand même si la zone ne fait pas d'IPv4, parce que
# ça semble assez dommage d'avoir une machine qui a une IPv6, pas
# d'IPv4, et pas d'entrée DNS pour la contacter, non mais oh.
dnsipv6 = machine.get('dnsIpv6', [True])[0]
if dnsipv6 or not self.ipv4:
self.add(AAAA(nom, ip)) self.add(AAAA(nom, ip))
# Si on fait aussi de l'IPv4...
if self.ipv4: if self.ipv4:
self.add(AAAA(self.get_name_vi(nom, 6), ip)) self.add(AAAA(self.get_name_vi(nom, 6), ip))
def add_sshfp_record(self, nom, machine): def add_sshfp_record(self, nom, machine):
"""Ajoute une fingerprint SSH"""
for sshkey in machine.get('sshFingerprint', []): for sshkey in machine.get('sshFingerprint', []):
try: try:
algo_txt, key = str(sshkey).split()[:2] algo_txt, key = str(sshkey).split()[:2]
algo=config.sshfs_ralgo[algo_txt][1] algo = config.sshfs_ralgo[algo_txt][1]
for hash in config.sshfp_hash.keys(): for r_hash in config.sshfp_hash.keys():
self.add(SSHFP(nom, hash, algo, key)) self.add(SSHFP(nom, r_hash, algo, key))
if self.ipv4 and self.ipv6: if self.ipv4:
self.add(SSHFP(self.get_name_vi(nom, 4), hash, algo, key)) self.add(SSHFP(self.get_name_vi(nom, 4), r_hash, algo, key))
self.add(SSHFP(self.get_name_vi(nom, 6), hash, algo, key)) if self.ipv6:
self.add(SSHFP(self.get_name_vi(nom, 6), r_hash, algo, key))
# KeyError is l'algo dans ldap n'est pas connu # KeyError is l'algo dans ldap n'est pas connu
# TypeError si la clef n'est pas bien en base64 # TypeError si la clef n'est pas bien en base64
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
def add_tlsa_record(self, cert): def add_tlsa_record(self, cert):
"""Ajout d'un certif dans le DNS"""
if 'TLSACert' in cert['objectClass']: if 'TLSACert' in cert['objectClass']:
for host in cert['hostCert']: for host in cert['hostCert']:
nom=self.get_name(host) nom = self.get_name(host)
if nom is None: continue if nom is None: continue
for port in cert['portTCPin']: for port in cert['portTCPin']:
self.add(TLSA(nom, port, 'tcp', cert['certificat'][0], cert['certificatUsage'][0], cert['matchingType'][0], cert['selector'][0], format='der')) self.add(TLSA(nom, port, 'tcp', cert['certificat'][0], cert['certificatUsage'][0], cert['matchingType'][0], cert['selector'][0], r_format='der'))
for port in cert['portUDPin']: for port in cert['portUDPin']:
self.add(TLSA(nom, port, 'udp', cert['certificat'][0], cert['certificatUsage'][0], cert['matchingType'][0], cert['selector'][0], format='der')) self.add(TLSA(nom, port, 'udp', cert['certificat'][0], cert['certificatUsage'][0], cert['matchingType'][0], cert['selector'][0], r_format='der'))
def add_machine(self, machine): def add_machine(self, machine):
"""Ajout d'une machine, à savoir chaînage d'ajout
d'IP, d'IPv6, de fingerprint et de TLSA, pour chaque
entrée "host" dans la base LDAP."""
for host in machine['host']: for host in machine['host']:
nom=self.get_name(host) # Le nom peut être None (machine appartenant à une sous-zone, ou à une autre zone)
if nom is None: continue nom = self.get_name(host)
if nom is None:
continue
self.add_a_record(nom, machine) self.add_a_record(nom, machine)
self.add_aaaa_record(nom, machine) self.add_aaaa_record(nom, machine)
@ -298,14 +412,23 @@ class Zone(ZoneBase):
for cert in machine.certificats(): for cert in machine.certificats():
self.add_tlsa_record(cert) self.add_tlsa_record(cert)
# Si la machine a bien un nom en "host", on lui ajoute aussi
# les aliases, sous forme de CNAME vers le premier nom.
if machine['host']: if machine['host']:
for alias in machine.get('hostAlias', []): for alias in machine.get('hostAlias', []):
# Si l'alias pointe dans une autre zone, on passe. (ça sera fait quand on refera le add_machine
# en toutnant dans la sous-zone
if str(alias) in self.other_zones and str(alias) != self.zone_name: if str(alias) in self.other_zones and str(alias) != self.zone_name:
continue continue
alias = self.get_name(alias) alias = self.get_name(alias)
if alias is None: continue if alias is None:
continue
to_nom = self.get_name(machine['host'][0]) to_nom = self.get_name(machine['host'][0])
# Si l'alias est sur le nom de la zone, il faut ajouter
# des entrées standard.
if alias in ['@', '%s.' % self.zone_name]: if alias in ['@', '%s.' % self.zone_name]:
self.add_a_record(alias, machine) self.add_a_record(alias, machine)
self.add_aaaa_record(alias, machine) self.add_aaaa_record(alias, machine)
@ -315,24 +438,34 @@ class Zone(ZoneBase):
if self.ipv4 and self.ipv6: if self.ipv4 and self.ipv6:
self.add(CNAME(self.get_name_vi(alias, 6), self.get_name_vi(to_nom, 6))) self.add(CNAME(self.get_name_vi(alias, 6), self.get_name_vi(to_nom, 6)))
self.add(CNAME(self.get_name_vi(alias, 4), self.get_name_vi(to_nom, 4))) self.add(CNAME(self.get_name_vi(alias, 4), self.get_name_vi(to_nom, 4)))
# Ne devrait pas arriver.
else: else:
self.add(CNAME(alias, "%s." % machine['host'][0])) self.add(CNAME(alias, "%s." % machine['host'][0]))
class ZoneReverse(Zone): class ZoneReverse(Zone):
"""Zone inverse, listant des PTR (toto.crans.org IN PTR 138.231...)"""
def __init__(self, net, ttl, soa, ns_list): def __init__(self, net, ttl, soa, ns_list):
if len(ZoneReverse.network_to_arpanets(net))!=1: """Initialise une zone reverse.
net est un truc de la forme fe80::/64, ou 138.231.136.0/24
En v4, il faut que net soit un /32, un /24, un /16 ou un /8
En gros, il faut que network_to_arpanets retourne une liste à un élément."""
# Comme dit, liste à un élément.
if len(ZoneReverse.network_to_arpanets(net)) != 1:
raise ValueError("%s n'est pas un réseau valide pour une zone de reverse dns" % net) raise ValueError("%s n'est pas un réseau valide pour une zone de reverse dns" % net)
self.net = net self.net = net
zone_name = ZoneReverse.reverse(net)[0] zone_name = ZoneReverse.reverse(net)[0]
if '.' in net: if '.' in net:
ipv6=False ipv6 = False
ipv4=True ipv4 = True
elif ':' in net: elif ':' in net:
ipv6=True ipv6 = True
ipv4=False ipv4 = False
else: else:
raise ValueError("net should be an ipv4 ou ipv6 network") raise ValueError("net should be an ipv4 ou ipv6 network")
super(ZoneReverse, self).__init__(zone_name, ttl, soa, ns_list, ipv6=ipv6, ipv4=ipv4) super(ZoneReverse, self).__init__(zone_name, ttl, soa, ns_list, ipv6=ipv6, ipv4=ipv4)
@staticmethod @staticmethod
@ -341,29 +474,43 @@ class ZoneReverse(Zone):
l'adresse donnés, ainsi que le nombre d'éléments de l'ip a l'adresse donnés, ainsi que le nombre d'éléments de l'ip a
mettre dans le fichier de zone si elle est fournie, n'importe mettre dans le fichier de zone si elle est fournie, n'importe
quoi sinon.""" quoi sinon."""
n = netaddr.IPNetwork(net) # Initialise la plage d'IP à partir de net
a = netaddr.IPAddress(ip if ip else n.ip) _network = netaddr.IPNetwork(net)
rev_dns_a = a.reverse_dns.split('.')[:-1] # Prend la première adresse ip de la plage, sauf si une est fournie
assert a in n _address = netaddr.IPAddress(ip if ip else _network.ip)
if n.version == 4: # retourne le reverse splitté. (un reverse ressemble à 0.136.231.138.in-addr.arpa.)
if n.prefixlen == 8: rev_dns_a = _address.reverse_dns.split('.')[:-1]
# Si la config est foireuse (donc si on a fourni une IP hors de la plage, ça
# va planter ici.
assert _address in _network
# En v4, le reverse étant de la forme 0.136.231.138.in-addr.arpa., soit
# on a un /8, soit un /16, soit un /24.
if _network.version == 4:
if _network.prefixlen == 8:
return ('.'.join(rev_dns_a[3:]), 3) return ('.'.join(rev_dns_a[3:]), 3)
elif n.prefixlen == 16: elif _network.prefixlen == 16:
return ('.'.join(rev_dns_a[2:]), 2) return ('.'.join(rev_dns_a[2:]), 2)
elif n.prefixlen == 24: elif _network.prefixlen == 24:
return ('.'.join(rev_dns_a[1:]), 1) return ('.'.join(rev_dns_a[1:]), 1)
else: else:
raise ValueError("Bad network %s" % n) raise ValueError("Bad network %s" % _network)
elif n.version == 6: # En v6 c'est plus calme.
return ('.'.join(rev_dns_a[(128-n.prefixlen)/4:]), (128-n.prefixlen)/4) # Le reverse a cette tronche : 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa.
# Du coup c'est aussi fin qu'on le souhaite.
elif _network.version == 6:
return ('.'.join(rev_dns_a[(128 - _network.prefixlen)/4:]), (128 - _network.prefixlen)/4)
@staticmethod @staticmethod
def network_to_arpanets(nets): def network_to_arpanets(nets):
""" """Dans reverse(net, ip), on a constaté qu'en v4, on ne pouvait définir
retourne une liste de reseaux ne contenant que que des plages reverse en /24, /16 ou /8. Cette fonction vise à retourner
des préfixes de taille 32, 24, 16 ou 8 en ipv4 une liste des plages en tenant compte de ce critère (donc de taille
et laisse inchangé les réseaux ipv6. 32/24/16/8)
Ne touche à rien pour l'IPv6.
""" """
if not isinstance(nets, list): if not isinstance(nets, list):
nets = [nets] nets = [nets]
@ -371,6 +518,8 @@ class ZoneReverse(Zone):
for net in nets: for net in nets:
if not isinstance(net, netaddr.IPNetwork): if not isinstance(net, netaddr.IPNetwork):
net = netaddr.IPNetwork(net) net = netaddr.IPNetwork(net)
# Si on est en v4, on fragmente les subnets
# dans les tailles qui vont bien.
if net.version == 4: if net.version == 4:
if net.prefixlen > 24: if net.prefixlen > 24:
subnets.extend(net.subnet(32)) subnets.extend(net.subnet(32))
@ -380,12 +529,13 @@ class ZoneReverse(Zone):
subnets.extend(net.subnet(16)) subnets.extend(net.subnet(16))
else: else:
subnets.extend(net.subnet(8)) subnets.extend(net.subnet(8))
# En v6 c'est tout pété.
elif net.version == 6: elif net.version == 6:
subnets.append(net) subnets.append(net)
return subnets return subnets
def add_machine(self, machine): def add_machine(self, machine):
"""Ajout d'un reverse pour une machine."""
if machine['host']: if machine['host']:
if self.ipv4: if self.ipv4:
attr = 'ipHostNumber' attr = 'ipHostNumber'
@ -393,33 +543,42 @@ class ZoneReverse(Zone):
attr = 'ip6HostNumber' attr = 'ip6HostNumber'
else: else:
raise ValueError("A reverse zone should be ipv6 or ipv6") raise ValueError("A reverse zone should be ipv6 or ipv6")
for ip in machine[attr]: for ip in machine[attr]:
try: try:
zone, length = ZoneReverse.reverse(self.net, str(ip)) zone, length = ZoneReverse.reverse(self.net, str(ip))
nom = '.'.join(ip.value.reverse_dns.split('.')[:length]) nom = '.'.join(ip.value.reverse_dns.split('.')[:length])
# La zone retournée n'est pas le nom de la zone. A priori
# on aurait dû tomber en AssertionError.
if zone != self.zone_name: if zone != self.zone_name:
continue continue
if attr != 'ip6HostNumber' or machine.get('dnsIpv6', [True])[0]: # Hack pour envoyer le reverse vers l'adresse .v6 dans le cas où dnsIpv6 = False
self.add(PTR(nom, '%s.' % machine['host'][0])) if attr != 'ip6HostNumber' or machine.get('dnsIpv6', [True])[0]:
self.add(PTR(nom, '%s.' % machine['host'][0]))
# Gros kludge pour ajouter le reverse vers le .v6 quand on est sur
# une reverse v6 et que dnsIpv6 est faux.
else: else:
rev_nom, rev_zone = str(machine['host'][0]).split('.', 1) rev_nom, rev_zone = str(machine['host'][0]).split('.', 1)
self.add(PTR(nom, '%s.v6.%s.' % (rev_nom, rev_zone))) self.add(PTR(nom, '%s.v6.%s.' % (rev_nom, rev_zone)))
except AssertionError: except AssertionError:
# L'ip n'est pas dans la zone reverse, donc on continue silencieusement.
pass pass
class dns(gen_config) : class dns(gen_config):
"""Classe de configuration du DNS (les services, generate, toussa)"""
######################################PARTIE DE CONFIGURATION ######################################PARTIE DE CONFIGURATION
### Fichiers à écrire ### Fichiers à écrire
# Répertoire d'écriture des fichiers de zone # Répertoire d'écriture des fichiers de zone
DNS_DIR = '/etc/bind/generated/' # Avec un / à la fin DNS_DIR = config.dns.DNS_DIR
DNSSEC_DIR = '/etc/bind/signed/' # Avec un / à la fin DNSSEC_DIR = config.dns.DNSSEC_DIR
# Fichier de définition des zones pour le maître # Fichier de définition des zones pour le maître
DNS_CONF = DNS_DIR + 'zones_crans' DNS_CONF = config.dns.DNS_CONF
# Fichier de définition des zones pour les esclaves géré par BCfg2 # 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" DNS_CONF_BCFG2 = config.dns.DNS_CONF_BCFG2
### Liste DNS ### Liste DNS
# Le premier doit être le maitre # Le premier doit être le maitre
@ -432,13 +591,10 @@ class dns(gen_config) :
### Serveurs de mail ### Serveurs de mail
# format : [ priorité serveur , .... ] # format : [ priorité serveur , .... ]
MXs = [ MXs = [MX('@', config.dns.MXs[_mx].get('prio', 25), _mx) for _mx in config.dns.MXs]
MX('@',10, 'redisdead.crans.org.'),
MX('@',15, 'soyouz.crans.org.'),
MX('@',25, 'freebox.crans.org.'),
]
SRVs = { SRVs = {
'crans.org': [ 'crans.org': [
SRV('jabber', 'tcp', 5, 0, 5269, 'xmpp'), SRV('jabber', 'tcp', 5, 0, 5269, 'xmpp'),
SRV('xmpp-server', 'tcp', 5, 0, 5269, 'xmpp'), SRV('xmpp-server', 'tcp', 5, 0, 5269, 'xmpp'),
SRV('xmpp-client', 'tcp', 5, 0, 5222, 'xmpp'), SRV('xmpp-client', 'tcp', 5, 0, 5222, 'xmpp'),
@ -446,14 +602,14 @@ class dns(gen_config) :
SRV('sip', 'tcp', 5, 0, 5060, 'asterisk'), SRV('sip', 'tcp', 5, 0, 5060, 'asterisk'),
SRV('sips', 'tcp', 5, 0, 5061, 'asterisk'), SRV('sips', 'tcp', 5, 0, 5061, 'asterisk'),
SRV('stun', 'udp', 5, 0, 3478, 'asterisk'), SRV('stun', 'udp', 5, 0, 3478, 'asterisk'),
] ],
} }
NATPRs = { NATPRs = {
'crans.org' : [ 'crans.org' : [
NAPTR('@', 5, 100, "S", "SIPS+D2T", "", '_sips._tcp.crans.org.', ttl=86400), 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('@', 10, 100, "S", "SIP+D2U", "", '_sip._udp.crans.org.', ttl=86400),
NAPTR('@', 15, 100, "S", "SIP+D2T", "", '_sip._tcp.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 ] } # DS à publier dans zone parentes : { parent : [ zone. TTL IN DS key_id algo_id 1 hash ] }
@ -461,10 +617,10 @@ class dns(gen_config) :
# /!\ Il faut faire attention au rollback des keys, il faudrait faire quelque chose d'automatique avec opendnssec # /!\ Il faut faire attention au rollback des keys, il faudrait faire quelque chose d'automatique avec opendnssec
DSs = { DSs = {
'crans.org': [ 'crans.org': [
DS('adm', '64649 8 2 9c45f0fef063672d96c983d5a3813a08a649c72d357f41ddece73ae8872d60cf'), DS('adm', '64649 8 2 9c45f0fef063672d96c983d5a3813a08a649c72d357f41ddece73ae8872d60cf'),
DS('wifi', '5531 8 2 daf30a647566234edc1617546fd74abbbaf965b17389248f72fc66a33d6f5063'), DS('wifi', '5531 8 2 daf30a647566234edc1617546fd74abbbaf965b17389248f72fc66a33d6f5063'),
DS('tv', '18199 8 2 d3cc2f5f81b830cbb8894ffd32c236e968edd3b0c0305112b6eb970aa763418e'), DS('tv', '18199 8 2 d3cc2f5f81b830cbb8894ffd32c236e968edd3b0c0305112b6eb970aa763418e'),
], ],
} }
@ -472,23 +628,28 @@ class dns(gen_config) :
serial = int(time.time()) + 1000000000 serial = int(time.time()) + 1000000000
TTL = 3600 TTL = 3600
if hostname == short_name(config.dns.DNSs[0]): if hostname == short_name(config.dns.DNSs[0]):
restart_cmd = '/usr/sbin/ods-signer sign --all && /etc/init.d/bind9 reload' restart_cmd = '/usr/sbin/ods-signer sign --all && /etc/init.d/bind9 reload'
else: else:
restart_cmd = '/etc/init.d/bind9 reload' restart_cmd = '/etc/init.d/bind9 reload'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Surcharge pour affecter EXTRAS"""
self.EXTRAS = {} self.EXTRAS = {}
self.anim = None
super(dns, self).__init__(*args, **kwargs) super(dns, self).__init__(*args, **kwargs)
def gen_soa(self, ns_list, serial, ttl): def gen_soa(self, ns_list, serial, ttl):
"""Génère l'enregistrement SOA pour le domaine"""
return SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl) return SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl)
def populate_zones(self, zones, machines): def populate_zones(self, zones, machines):
self.anim.iter=len(zones.values()) """On peuple les fichiers de zones"""
self.anim.iter = len(zones.values())
for zone in zones.values(): for zone in zones.values():
# On met les mêmes MX pour toutes les zones.
zone.extend(self.MXs) zone.extend(self.MXs)
# Les RR définis ici sont ajoutés aux zones idoines, de façon à se simplifier la vie.
for rr_type in [self.SRVs, self.NATPRs, self.DSs, self.EXTRAS]: for rr_type in [self.SRVs, self.NATPRs, self.DSs, self.EXTRAS]:
if zone.zone_name in rr_type.keys(): if zone.zone_name in rr_type.keys():
zone.extend(rr_type[zone.zone_name]) zone.extend(rr_type[zone.zone_name])
@ -498,31 +659,43 @@ class dns(gen_config) :
return zones return zones
def gen_zones_ldap(self, ttl, ns_list, serial, zones={}, zones_ldap=config.dns.zones_ldap): def gen_zones_ldap(self, ttl, ns_list, serial, zones={}, zones_ldap=config.dns.zones_ldap):
"""On génère la liste des zones ldap, à partir de config.dns. C'est un peu ici que tout commence.
Le dico zones passé en argument est modifié en place."""
for zone in 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) # On crée la zone et on l'ajoute au dico.
zones[zone] = Zone(zone, ttl, self.gen_soa(ns_list, serial, ttl), ns_list, other_zones=config.dns.zones_direct)
return zones return zones
def gen_zones_reverse(self, ttl, ns_list, serial, 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): zones_reverse_v4=config.dns.zones_reverse,
zones_reverse_v6=config.dns.zones_reverse_v6):
"""Deuxième gros morceau, les reverses, pareil, on peuple depuis config.dns, et on crée toutes les zones
idoines. Pareil, ici, le dico zones est modifié en place"""
for net in ZoneReverse.network_to_arpanets(zones_reverse_v4 + zones_reverse_v6): for net in ZoneReverse.network_to_arpanets(zones_reverse_v4 + zones_reverse_v6):
zones[str(net)]=ZoneReverse(str(net), ttl, self.gen_soa(ns_list, serial, ttl), ns_list) # On crée la zone et on l'ajoute au dico.
zones[str(net)] = ZoneReverse(str(net), ttl, self.gen_soa(ns_list, serial, ttl), ns_list)
return zones return zones
def gen_zones_clone(self, ttl, ns_list, serial, zones={}): def gen_zones_clone(self, ttl, ns_list, serial, zones={}):
for zone_clone, zones_alias in config.dns.zone_alias.items(): """Les clônes, à savoir crans.eu et cie, dico zones modifié en place."""
for zone_clone, zones_alias in config.dns.zone_alias.iteritems():
for zone in zones_alias: for zone in zones_alias:
zones[zone]=ZoneClone(zone, zones[zone_clone], self.gen_soa(ns_list, serial, ttl)) # On crée la zone et on l'ajoute au dico.
zones[zone] = ZoneClone(zone, zones[zone_clone], self.gen_soa(ns_list, serial, ttl))
# Et on ajoute les enregistrements concernant la zone clône (pas la clônée, ça
# a déjà été fait à l'init) à la main.
for rr_type in [self.SRVs, self.NATPRs, self.DSs]: for rr_type in [self.SRVs, self.NATPRs, self.DSs]:
if zones[zone].zone_name in rr_type.keys(): if zones[zone].zone_name in rr_type.keys():
zones[zone].extend(rr_type[zones[zone].zone_name]) zones[zone].extend(rr_type[zones[zone].zone_name])
return zones return zones
def gen_zones(self, ttl, serial, ns_list, populate=True): def gen_zones(self, ttl, serial, ns_list, populate=True):
"""On chaîne les différents gen_zones_*"""
zones = {} zones = {}
self.gen_zones_ldap(ttl, ns_list, serial, zones) self.gen_zones_ldap(ttl, ns_list, serial, zones)
self.gen_zones_reverse(ttl, ns_list, serial, zones) self.gen_zones_reverse(ttl, ns_list, serial, zones)
# Si populate, on remplit les zones avec les enregistrements \o/
if populate: if populate:
conn = lc_ldap.shortcuts.lc_ldap_admin() conn = lc_ldap.shortcuts.lc_ldap_admin()
machines = conn.search(u"mid=*", sizelimit=10000) machines = conn.search(u"mid=*", sizelimit=10000)
@ -534,30 +707,34 @@ class dns(gen_config) :
self.gen_zones_clone(ttl, ns_list, serial, zones) self.gen_zones_clone(ttl, ns_list, serial, zones)
return zones return zones
def gen_tv(self, populate=True): def gen_tv(self, populate=True):
"""Génération de la TV, un peu à part."""
self.anim = affich_tools.anim('\tgénération de la zone tv') self.anim = affich_tools.anim('\tgénération de la zone tv')
zones = {} zones = {}
serial = self.serial 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_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]) self.gen_zones_ldap(self.TTL, config.dns.DNSs, serial, zones, zones_ldap=[config.dns.zone_tv])
# Pareil, si on doit peupler on ajoute ce qu'il faut niveau machines.
if populate: if populate:
conn = lc_ldap.shortcuts.lc_ldap_admin() conn = lc_ldap.shortcuts.lc_ldap_admin()
machines=conn.machinesMulticast() machines = conn.machinesMulticast()
machines.extend(conn.search(u'(|(host=%s)(host=*.%s)(hostAlias=%s)(hostAlias=*.%s))' % ((config.dns.zone_tv,)*4))) machines.extend(conn.search(u'(|(host=%s)(host=*.%s)(hostAlias=%s)(hostAlias=*.%s))' % ((config.dns.zone_tv,)*4)))
self.populate_zones(zones, machines) self.populate_zones(zones, machines)
for zone in zones.values(): for zone in zones.values():
zone.write(self.DNS_DIR + 'db.' + zone.zone_name) zone.write(os.path.join(self.DNS_DIR, 'db.%s' % (zone.zone_name,)))
self.anim.reinit() self.anim.reinit()
print affich_tools.OK print affich_tools.OK
return zones return zones
def gen_master(self): def gen_master(self):
"""Pour le serveur maître.
Appelle gen_zones puis écrit les fichiers."""
# Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître
zone_template=""" zone_template = """
zone "%(zone_name)s" { zone "%(zone_name)s" {
type master; type master;
file "%(zone_path)s"; file "%(zone_path)s";
@ -567,15 +744,17 @@ zone "%(zone_name)s" {
with open(self.DNS_CONF, 'w') as f: with open(self.DNS_CONF, 'w') as f:
f.write(disclamer) f.write(disclamer)
for zone in zones.values(): for zone in zones.values():
zone.write(self.DNS_DIR + 'db.' + zone.zone_name) zone.write(os.path.join(self.DNS_DIR, 'db.%s' % (zone.zone_name,)))
if zone.zone_name in config.dns.zones_dnssec: if zone.zone_name in config.dns.zones_dnssec:
zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name zone_path = os.path.join(self.DNSSEC_DIR, 'db.%s' % (zone.zone_name,))
else: else:
zone_path = self.DNS_DIR + 'db.' + zone.zone_name zone_path = os.path.join(self.DNS_DIR, 'db.%s' % (zone.zone_name,))
f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path}) f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path})
def gen_slave(self): def gen_slave(self):
zone_template=""" """Pour les slaves, fait l'écriture de la conf dans bcfg2, mais on ne peuple rien !
On ne fait qu'écrire le fichier zone_crans."""
zone_template = """
zone "%(zone_name)s" { zone "%(zone_name)s" {
type slave; type slave;
file "%(zone_path)s"; file "%(zone_path)s";
@ -587,9 +766,9 @@ zone "%(zone_name)s" {
f.write(disclamer) f.write(disclamer)
for zone in zones.values(): for zone in zones.values():
if zone.zone_name in config.dns.zones_dnssec: if zone.zone_name in config.dns.zones_dnssec:
zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name zone_path = os.path.join(self.DNSSEC_DIR, 'db.%s' % (zone.zone_name,))
else: else:
zone_path = self.DNS_DIR + 'db.' + zone.zone_name zone_path = os.path.join(self.DNS_DIR, 'db.%s' % (zone.zone_name,))
f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path, 'master_ip' : config.dns.master}) f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path, 'master_ip' : config.dns.master})
def _gen(self): def _gen(self):
@ -599,28 +778,28 @@ zone "%(zone_name)s" {
return "DNS" return "DNS"
if __name__ == '__main__' : if __name__ == '__main__':
hostname = short_name(gethostname()) HOSTNAME = short_name(gethostname())
if hostname == short_name(config.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)." print "Reconfiguration du fichier de BCfg2 pour configurer le bind d'un serveur en esclave (pensez à lancer bcfg2 sur les esclaves)."
c = dns() CONFIG = dns()
c.gen_slave() CONFIG.gen_slave()
elif hostname == short_name(config.dns.DNSs[0]): elif HOSTNAME == short_name(config.dns.DNSs[0]):
print "Serveur maître :" print "Serveur maître :"
c = dns() CONFIG = dns()
zones = c.gen_tv() ZONES = CONFIG.gen_tv()
import subprocess import subprocess
for zone in zones.values(): for ZONE in ZONES.values():
if zone.zone_name in config.dns.zones_dnssec: if ZONE.zone_name in config.dns.zones_dnssec:
args=("/usr/sbin/ods-signer sign %s" % zone.zone_name).split() ARGS = ("/usr/sbin/ods-signer sign %s" % ZONE.zone_name).split()
p=subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE) PROCESS = subprocess.Popen(ARGS, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
ret=p.communicate() RET = PROCESS.communicate()
print ret[0].strip() print RET[0].strip()
if ret[1].strip(): if RET[1].strip():
print 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." 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:]): elif HOSTNAME in [short_name(FULLHOSTNAME) for FULLHOSTNAME in config.dns.DNSs[1:]]:
print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % bcfg2_main print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % (config.bcfg2_main,)
else: else:
print "Ce serveur ne correspond à rien pour la configuration DNS." print "Ce serveur ne correspond à rien pour la configuration DNS."