655 lines
27 KiB
Python
Executable file
655 lines
27 KiB
Python
Executable file
#! /usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
""" Génération de la configuration pour bind9
|
|
|
|
Copyright (C) Valentin Samir
|
|
Licence : GPLv3
|
|
"""
|
|
|
|
import sys
|
|
import ssl
|
|
import time
|
|
import base64
|
|
import hashlib
|
|
import binascii
|
|
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]
|
|
|
|
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 TLSA(ResourceRecord):
|
|
def __init__(self, name, port, proto, cert, certtype, reftype, selector=0, compat=True, format='pem', ttl=None):
|
|
"""
|
|
name: nom du domaine du certificat
|
|
port: port où écoute le service utilisant le certificat
|
|
proto: udp ou tcp
|
|
cert: le certificat au format ``format`` (pem ou der) (selector est donc toujours à 0)
|
|
certtype: type d'enregistrement 0 = CA pinning, 1 = cert pinning, 2 = self trusted CA, 3 = self trusted cert
|
|
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
|
|
"""
|
|
if not format in ['pem', 'der']:
|
|
raise ValueError("format should be pem or der")
|
|
if cert is None and proto == 'tcp' and name[-1] == '.':
|
|
try:
|
|
cert = ssl.get_server_certificate((name[:-1], port), ca_certs='/etc/ssl/certs/ca-certificates.crt')
|
|
except Exception as e:
|
|
raise ValueError("Unable de retrieve cert dynamically: %s" % e)
|
|
elif cert is None:
|
|
raise ValueError("cert can only be retrive if proto is tcp and name fqdn")
|
|
if format is not 'der':
|
|
dercert = ssl.PEM_cert_to_DER_cert(cert)
|
|
else:
|
|
dercert = cert
|
|
if not dercert:
|
|
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))
|
|
if compat:
|
|
super(TLSA, self).__init__(
|
|
'TYPE52',
|
|
'_%s._%s%s' % (port, proto, '.' + name if name else ''),
|
|
"\# %s 0%s0%s0%s%s" % (len(certhex)/2 +3, certtype, selector, reftype, certhex),
|
|
ttl
|
|
)
|
|
else:
|
|
super(TLSA, self).__init__(
|
|
'TLSA',
|
|
'_%s._%s%s' % (port, proto, '.' + name if name else ''),
|
|
"%s %s %s %s"% (certtype, selector, reftype, certhex),
|
|
ttl
|
|
)
|
|
|
|
@staticmethod
|
|
def hashCert(reftype, certblob):
|
|
"""
|
|
certblob: un certificat au format DER
|
|
"""
|
|
if reftype == 0:
|
|
return binascii.b2a_hex(certblob).upper()
|
|
elif reftype == 1:
|
|
hashobj = hashlib.sha256()
|
|
hashobj.update(certblob)
|
|
elif reftype == 2:
|
|
hashobj = hashlib.sha512()
|
|
hashobj.update(certblob)
|
|
else:
|
|
raise ValueError("reftype sould be 0 1 or 2, not %s" % reftype)
|
|
return hashobj.hexdigest().upper()
|
|
|
|
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 get_name_vi(self, nom, i):
|
|
if not i in [4, 6]:
|
|
raise ValueError("i should be 4 or 6")
|
|
if nom == '@':
|
|
return 'v%s' % i
|
|
elif '.' in nom:
|
|
nom_1, nom_2 = nom.split('.', 1)
|
|
return "%s.v%s.%s" % (nom_1, i, nom_2)
|
|
else:
|
|
return "%s.v%s" % (nom, i)
|
|
|
|
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:
|
|
self.add(A(self.get_name_vi(nom, 4), ip))
|
|
|
|
def add_aaaa_record(self, nom, machine):
|
|
if self.ipv6:
|
|
for ip in machine.get('ip6HostNumber', []):
|
|
if machine.get('dnsIpv6', [True])[0]:
|
|
self.add(AAAA(nom, ip))
|
|
if self.ipv4:
|
|
self.add(AAAA(self.get_name_vi(nom, 6), ip))
|
|
|
|
def add_sshfp_record(self, nom, machine):
|
|
for sshkey in machine.get('sshFingerprint', []):
|
|
try:
|
|
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:
|
|
self.add(SSHFP(self.get_name_vi(nom, 4), hash, algo, key))
|
|
self.add(SSHFP(self.get_name_vi(nom, 6), hash, algo, key))
|
|
# KeyError is l'algo dans ldap n'est pas connu
|
|
# TypeError si la clef n'est pas bien en base64
|
|
except (KeyError, TypeError):
|
|
pass
|
|
|
|
def add_tlsa_record(self, cert):
|
|
if 'TLSACert' in cert['objectClass']:
|
|
for host in cert['hostCert']:
|
|
nom=self.get_name(host)
|
|
if nom is None: continue
|
|
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'))
|
|
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'))
|
|
|
|
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)
|
|
for cert in machine.certificats():
|
|
self.add_tlsa_record(cert)
|
|
|
|
|
|
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 = self.get_name(machine['host'][0])
|
|
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_nom:
|
|
self.add(CNAME(alias, "%s" % to_nom))
|
|
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, 4), self.get_name_vi(to_nom, 4)))
|
|
else:
|
|
self.add(CNAME(alias, "%s." % machine['host'][0]))
|
|
|
|
|
|
class ZoneReverse(Zone):
|
|
def __init__(self, net, ttl, soa, ns_list):
|
|
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)
|
|
self.net = net
|
|
zone_name = ZoneReverse.reverse(net)[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)
|
|
|
|
@staticmethod
|
|
def reverse(net, ip=None):
|
|
"""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 si elle est fournie, n'importe
|
|
quoi sinon."""
|
|
n = netaddr.IPNetwork(net)
|
|
a = netaddr.IPAddress(ip if ip else n.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)
|
|
elif n.prefixlen == 24:
|
|
return ('.'.join(rev_dns_a[1:]), 1)
|
|
else:
|
|
raise ValueError("Bad network %s" % n)
|
|
elif n.version == 6:
|
|
return ('.'.join(rev_dns_a[(128-n.prefixlen)/4:]), (128-n.prefixlen)/4)
|
|
|
|
|
|
@staticmethod
|
|
def network_to_arpanets(nets):
|
|
"""
|
|
retourne une liste de reseaux ne contenant que
|
|
des préfixes de taille 32, 24, 16 ou 8 en ipv4
|
|
et laisse inchangé les réseaux ipv6.
|
|
"""
|
|
if not isinstance(nets, list):
|
|
nets = [nets]
|
|
subnets = []
|
|
for net in nets:
|
|
if not isinstance(net, netaddr.IPNetwork):
|
|
net = netaddr.IPNetwork(net)
|
|
if net.version == 4:
|
|
if net.prefixlen > 24:
|
|
subnets.extend(net.subnet(32))
|
|
elif net.prefixlen > 16:
|
|
subnets.extend(net.subnet(24))
|
|
elif net.prefixlen > 8:
|
|
subnets.extend(net.subnet(16))
|
|
else:
|
|
subnets.extend(net.subnet(8))
|
|
elif net.version == 6:
|
|
subnets.append(net)
|
|
return subnets
|
|
|
|
|
|
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 = ZoneReverse.reverse(self.net, str(ip))
|
|
nom = '.'.join(ip.value.reverse_dns.split('.')[:length])
|
|
if zone != self.zone_name:
|
|
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]))
|
|
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('@',15, 'soyouz.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.org': [
|
|
DS('adm', '64649 8 2 9c45f0fef063672d96c983d5a3813a08a649c72d357f41ddece73ae8872d60cf'),
|
|
DS('ferme', '57424 8 2 2c21ec2a80a9ceb93fe085409ebdbab8d39145c18dc8ea8b23e9a38b5c414eb4'),
|
|
DS('wifi', '5531 8 2 daf30a647566234edc1617546fd74abbbaf965b17389248f72fc66a33d6f5063'),
|
|
DS('tv', '18199 8 2 d3cc2f5f81b830cbb8894ffd32c236e968edd3b0c0305112b6eb970aa763418e'),
|
|
],
|
|
}
|
|
|
|
|
|
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 __init__(self, *args, **kwargs):
|
|
xmpp_cert = ssl.get_server_certificate(('xmpp.crans.org', 443), ca_certs='/etc/ssl/certs/ca-certificates.crt')
|
|
self.EXTRAS = {
|
|
'crans.org' : [
|
|
TLSA('crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('www.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('cas.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('wiki.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('perso.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('intranet.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('intranet2.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('webmail.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('horde.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('roundcube.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('sogo.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('git.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('nagios.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('pad.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('news.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('lists.crans.org.', 443, 'tcp', None, 3, 2),
|
|
TLSA('asterisk.crans.org.', 5061, 'tcp', None, 3, 2),
|
|
TLSA('smtp.crans.org.', 465, 'tcp', None, 3, 2),
|
|
TLSA('imap.crans.org.', 993, 'tcp', None, 3, 2),
|
|
TLSA('xmpp', 5222, 'tcp', xmpp_cert, 3, 2),
|
|
TLSA('xmpp', 5269, 'tcp', xmpp_cert, 3, 2),
|
|
TLSA('xmpp', 443, 'tcp', xmpp_cert, 3, 2),
|
|
TLSA('jabber', 443, 'tcp', xmpp_cert, 3, 2),
|
|
],
|
|
'wifi.crans.org' : [
|
|
TLSA('wifi.crans.org.', 443, 'tcp', None, 3, 2),
|
|
],
|
|
}
|
|
super(dns, self).__init__(*args, **kwargs)
|
|
|
|
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, self.EXTRAS]:
|
|
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):
|
|
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)
|
|
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()
|
|
machines.extend(conn.search(u'(|(host=%s)(host=*.%s)(hostAlias=%s)(hostAlias=*.%s))' % ((config.dns.zone_tv,)*4)))
|
|
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."
|
|
|