scripts/gestion/gen_confs/bind2.py
Valentin Samir 3177547e7c [dns] Utilisation de bind2 (avec lc_ldap) pour générer le dns
Et du coup, on met de l'ipv6 aux nom de domaines par defaut
2014-01-31 03:04:36 +01:00

453 lines
18 KiB
Python

#! /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
from gestion.gen_confs import gen_config
from socket import gethostname
from gestion import config
import config.dns
def short_name(fullhostname):
return fullhostname.split(".")[0]
def reverse(net, ip):
"""Renvoie la zone DNS inverse correspondant au réseau et à
l'adresse donnés, ainsi que le nombre d'éléments de l'ip a
mettre dans le fichier de zone."""
n = netaddr.IPNetwork(net)
a = netaddr.IPAddress(ip)
rev_dns_a = a.reverse_dns.split('.')[:-1]
assert a in n
if n.version == 4:
if n.prefixlen == 8:
return ('.'.join(rev_dns_a[3:]), 3)
elif n.prefixlen == 16:
return ('.'.join(rev_dns_a[2:]), 2)
else:
return ('.'.join(rev_dns_a[1:]), 1)
elif n.version == 6:
return ('.'.join(rev_dns_a[(128-n.prefixlen)/4:]), (128-n.prefixlen)/4)
class ResourceRecord(object):
def __init__(self, type, name, value, ttl=None):
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=""";***********************************************************
; 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.
;
; NE PAS EDITER
;
;***********************************************************
$ORIGIN %s.
$TTL %s
""" % (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." % "%s.", rr._value))
class Zone(ZoneBase):
def __init__(self, zone_name, ttl, soa, ns_list, ipv6=True, ipv4=True, bl_zone=[]):
super(Zone, self).__init__(zone_name)
self.ttl = ttl
self.ipv4 = ipv4
self.ipv6 = ipv6
self.bl_zone = bl_zone
self.add(soa)
for ns in ns_list:
self.add(NS('@', '%s.' % ns))
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 reduce(lambda x,y: x or y, [str(hostname).endswith(".%s" % z) for z in self.bl_zone if z != self.zone_name and z.endswith(self.zone_name)] + [False]):
ret=str(hostname)[0:- len(self.zone_name) -1]
if ret == "":
return "@"
else:
return ret
else:
#print "%s not in zone %s" % (hostname, self.zone_name)
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.bl_zone and str(alias) != self.zone_name:
continue
alias = self.get_name(alias)
if alias is None: continue
to_nom, to_zone = str(machine['host'][0]).split('.', 1)
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())
if hostname == short_name(config.dns.DNSs[0]):
restart_cmd = '/usr/sbin/ods-signer sign --all && /etc/init.d/bind9 reload'
else:
restart_cmd = '/etc/init.d/bind9 reload'
def gen_zones(self, ttl, ns_list, populate=True):
zones = {}
serial = int(time.time()) + 1000000000
for zone in config.dns.zones_ldap:
#print "Create zone %s" % zone
zones[zone]=Zone(zone, ttl, SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl), ns_list, bl_zone=config.dns.zones_direct)
for net in config.dns.zones_reverse:
net = netaddr.IPNetwork(net)
if net.prefixlen > 24:
subnets = net.subnet(32)
elif net.prefixlen > 16:
subnets = net.subnet(24)
elif net.prefixlen > 8:
subnets = net.subnet(16)
else:
subnets = net.subnet(8)
for subnet in subnets:
#print "Create zone %s" % subnet
zones[str(subnet)]=ZoneReverse(str(subnet), ttl, SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl), ns_list)
for net in config.dns.zones_reverse_v6:
#print "Create zone %s" % net
zones[net]=ZoneReverse(net, ttl, SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl), ns_list)
if populate:
conn = lc_ldap.shortcuts.lc_ldap_admin()
machines = conn.search(u"mid=*", sizelimit=10000)
machines.extend(conn.machinesMulticast())
self.anim.iter=len(zones.values())
for zone in zones.values():
#print "Generate zone %s" % zone.zone_name
zone.extend(self.MXs)
for rr_type in [self.SRVs, self.NATPRs, self.DSs]:
if zone.zone_name in rr_type.keys():
zone.extend(rr_type[zone.zone_name])
for m in machines:
zone.add_machine(m)
self.anim.cycle()
for zone_clone, zones_alias in config.dns.zone_alias.items():
for zone in zones_alias:
zones[zone]=ZoneClone(zone, zones[zone_clone], SOA(ns_list[0], 'root.crans.org', serial, 21600, 3600, 1209600, ttl))
for rr_type in [self.SRVs, self.NATPRs, self.DSs]:
if zones[zone].zone_name in rr_type.keys():
zones[zone].extend(rr_type[zones[zone].zone_name])
return zones
def gen_master(self):
# Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître
zone_template="""
zone "%(zone_name)s" {
type master;
file "%(zone_path)s";
};
"""
zones = self.gen_zones(3600, config.dns.DNSs)
with open(self.DNS_CONF, 'w') as f:
for zone in zones.values():
zone.write(self.DNS_DIR + 'db.' + zone.zone_name)
if zone.zone_name in config.dns.zones_dnssec:
zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name
else:
zone_path = self.DNS_DIR + 'db.' + zone.zone_name
f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path})
def gen_slave(self):
zone_template="""
zone "%(zone_name)s" {
type slave;
file "%(zone_path)s";
masters { %(master_ip)s; };
};
"""
zones = self.gen_zones(3600, config.dns.DNSs, populate=False)
with open(self.DNS_CONF_BCFG2, 'w') as f:
for zone in zones.values():
if zone.zone_name in config.dns.zones_dnssec:
zone_path = self.DNSSEC_DIR + 'db.' + zone.zone_name
else:
zone_path = self.DNS_DIR + 'db.' + zone.zone_name
f.write(zone_template % {'zone_name' : zone.zone_name, 'zone_path' : zone_path, 'master_ip' : config.dns.master})
def _gen(self):
self.gen_master()
def __str__(self):
return "DNS"