
Ignore-this: 2af02b214edaf26f2d4c62be532b2066 darcs-hash:20130103151331-3a55a-38c141614ea1a4e9e948ee0b197234b459078326.gz
389 lines
16 KiB
Python
389 lines
16 KiB
Python
#! /usr/bin/env python
|
|
# -*- coding: iso-8859-15 -*-
|
|
|
|
""" Génération de la configuration pour bind9
|
|
|
|
Copyright (C) Frédéric Pauget
|
|
Licence : GPLv2
|
|
"""
|
|
import time, sys
|
|
sys.path.append('/usr/scripts/gestion')
|
|
from socket import gethostname
|
|
from gen_confs import gen_config
|
|
|
|
import config
|
|
from iptools import AddrInNet, AddrInNets
|
|
import ip6tools
|
|
|
|
import netaddr
|
|
|
|
def short_name(fullhostname):
|
|
return fullhostname.split(".")[0]
|
|
|
|
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
|
|
# 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 = [ 'crans.org', 'crans.ens-cachan.fr', 'wifi.crans.org', 'ferme.crans.org' , 'clubs.ens-cachan.fr', 'adm.crans.org' ]
|
|
zones_v4_to_v6 = {
|
|
'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',
|
|
}
|
|
# Résolution inverse
|
|
zones_reverse = config.NETs["all"] + config.NETs["adm"] + config.NETs["personnel-ens"]
|
|
zones_v6_to_net = {
|
|
'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 = ['sable.crans.org', 'charybde.crans.org', 'freebox.crans.org', 'ovh.crans.org']
|
|
DNSs_private = ['vert.adm.crans.org']
|
|
ip_master_DNS = "10.231.136.9"
|
|
|
|
### 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 = { 'tv.crans.org' : ['charybde.crans.org.' , 'freebox.crans.org.', 'sable.crans.org.' , 'mdr.crans.org.'] }
|
|
|
|
### Serveurs de mail
|
|
# format : [ priorité serveur , .... ]
|
|
MXs = ['10 redisdead.crans.org', '20 ovh.crans.org', '20 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.',
|
|
]
|
|
|
|
### Entète des fichiers de zone
|
|
zone_entete="""
|
|
$ORIGIN %(zone)s.
|
|
$TTL 86400
|
|
@\tIN\tSOA sable.crans.org. root.crans.org. (
|
|
%(serial)i ; numero de serie
|
|
21600 ; refresh (s)
|
|
3600 ; retry (s)
|
|
1209600 ; expire (s)
|
|
86400 ; 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
|
|
|
|
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_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())
|
|
|
|
# Ajout des zones reverse
|
|
for net in self.zones_reverse:
|
|
# IPv4 reverse
|
|
addr, prefixlen = net.split('/')
|
|
if prefixlen == '16':
|
|
zones.append("%s.in-addr.arpa" % '.'.join(reversed(addr.split('.')[0:2])))
|
|
continue
|
|
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
|
|
|
|
for net in set(self.zones_v6_to_net.values()):
|
|
# IPv6 reverse
|
|
n = netaddr.IPNetwork(net)
|
|
network_reverse = netaddr.IPAddress(n.first).reverse_dns
|
|
zone = network_reverse.split('.')[(128-n.prefixlen)/4:-1]
|
|
zones.append('.'.join(zone))
|
|
|
|
# Ecriture
|
|
fd = self._open_conf(self.DNS_CONF_BCFG2,'//')
|
|
for zone in zones :
|
|
fd.write(self.zone_template_slave % { 'NOM_zone' : zone,
|
|
'FICHIER_zone' : self.DNS_DIR + 'db.' + zone,
|
|
'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 += '%(zone)s.\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"
|
|
|
|
### Ajout des parametres SPF
|
|
direct['crans.org'] +='; Parametres SPF\n'
|
|
direct['crans.org'] +='crans.org.\tIN\tTXT\t"v=spf1 a mx ?all"\n'
|
|
for m in self.MXs:
|
|
direct['crans.org'] +='%s.\tIN\tTXT\t"v=spf1 a ?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\tTXT\t"v=spf1 a:crans.org mx ?all"\n\n'
|
|
|
|
### Ajout d'eventuels champs SRV
|
|
direct['crans.org'] +='; Champs SRV\n'
|
|
for s in self.SRVs:
|
|
direct['crans.org'] += s + '\n'
|
|
direct['crans.org'] += '\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('iso-8859-1')
|
|
except :
|
|
warnings += u'Machine ignorée (mid=%s) : format nom incorrect (%s)\n' % ( machine.id().encode('iso-8859-1'), machine.nom().encode('iso-8859-1') )
|
|
continue
|
|
|
|
# Le direct
|
|
if zone in self.zones_direct :
|
|
ligne = "%s\tIN\tA\t%s\n" % ( nom, machine.ip() )
|
|
direct[zone] = direct.get(zone, "") + 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('iso-8859-1'), zone.encode('iso-8859-1') )
|
|
|
|
# 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('iso-8859-1')
|
|
# Cas particulier : nom de l'alias = nom de la zone
|
|
if alias in self.zones_direct :
|
|
ligne = "@\tIN\tA\t%s\n" % machine.ip()
|
|
ligne = ligne.encode('iso-8859-1')
|
|
direct[alias] = direct.get(alias, "") + ligne
|
|
if machine.dnsIpv6():
|
|
ligne = "@\tIN\tAAAA\t%s\n" % machine.ipv6()
|
|
ligne = ligne.encode('iso-8859-1')
|
|
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('iso-8859-1')
|
|
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('iso-8859-1'), alias.encode('iso-8859-1') )
|
|
continue
|
|
zone = zone.encode('iso-8859-1')
|
|
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()
|
|
net = AddrInNets(ip, self.zones_reverse)
|
|
if net:
|
|
base_ip = ip.split('.')
|
|
base_ip.reverse()
|
|
zone, length = self.reverse(net, ip)
|
|
zone = zone.encode('iso-8859-1')
|
|
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('iso-8859-1'), machine.ip().encode('iso-8859-1') )
|
|
|
|
### Ajouts pour les fichiers de résolution directs
|
|
for zone in direct.keys() :
|
|
# MXs
|
|
direct[zone] = MX % { 'zone' : zone } + 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, "")
|
|
|
|
### 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 )
|
|
|
|
### Ecriture des fichiers de zone et préparation du fichier de définition
|
|
f = ''
|
|
for zone, lignes in direct.items() + reverse.items() :
|
|
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()
|
|
if short_name(gethostname()) in map(short_name, dns.DNSs[1:]):
|
|
f += self.zone_template_slave % {'NOM_zone': zone, 'FICHIER_zone': file,
|
|
'ip_master_DNS': self.ip_master_DNS}
|
|
else:
|
|
f += self.zone_template % { 'NOM_zone' : zone, 'FICHIER_zone' : file }
|
|
|
|
### 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.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."
|