From e97e3041455c8f1a847da6a48cbc49c7f2fdf893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 17 Mar 2018 18:54:17 +0000 Subject: [PATCH] API: Add support for DNS --- api/serializers.py | 265 +++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 10 ++ api/views.py | 185 +++++++++++++++++++++++++++++++ 3 files changed, 460 insertions(+) diff --git a/api/serializers.py b/api/serializers.py index 5cbf9438..1b02c577 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -26,7 +26,19 @@ from rest_framework import serializers from users.models import Club, Adherent from machines.models import ( Interface, + IpType, + Extension, + IpList, + MachineType, + Domain, + Txt, + Mx, + Srv, Service_link, + Ns, + OuverturePortList, + OuverturePort, + Ipv6List ) @@ -52,6 +64,28 @@ class MailingMemberSerializer(serializers.ModelSerializer): fields = ('email', 'name', 'surname', 'pseudo',) +class IpTypeField(serializers.RelatedField): + """Serialisation d'une iptype, renvoie son evaluation str""" + def to_representation(self, value): + return value.type + + +class IpListSerializer(serializers.ModelSerializer): + """Serialisation d'une iplist, ip_type etant une foreign_key, + on evalue sa methode str""" + ip_type = IpTypeField(read_only=True) + + class Meta: + model = IpList + fields = ('ipv4', 'ip_type') + + +class Ipv6ListSerializer(serializers.ModelSerializer): + class Meta: + model = Ipv6List + fields = ('ipv6', 'slaac_ip') + + class InterfaceSerializer(serializers.ModelSerializer): """Serialisation d'une interface, ipv4, domain et extension sont des foreign_key, on les override et on les evalue avec des fonctions @@ -75,6 +109,209 @@ class InterfaceSerializer(serializers.ModelSerializer): return str(obj.mac_address) +class FullInterfaceSerializer(serializers.ModelSerializer): + """Serialisation complete d'une interface avec les ipv6 en plus""" + ipv4 = IpListSerializer(read_only=True) + ipv6 = Ipv6ListSerializer(read_only=True, many=True) + mac_address = serializers.SerializerMethodField('get_macaddress') + domain = serializers.SerializerMethodField('get_dns') + extension = serializers.SerializerMethodField('get_interface_extension') + + class Meta: + model = Interface + fields = ('ipv4', 'ipv6', 'mac_address', 'domain', 'extension') + + def get_dns(self, obj): + return obj.domain.name + + def get_interface_extension(self, obj): + return obj.domain.extension.name + + def get_macaddress(self, obj): + return str(obj.mac_address) + + +class ExtensionNameField(serializers.RelatedField): + """Evaluation str d'un objet extension (.example.org)""" + def to_representation(self, value): + return value.name + + +class TypeSerializer(serializers.ModelSerializer): + """Serialisation d'un iptype : extension et la liste des + ouvertures de port son evalués en get_... etant des + foreign_key ou des relations manytomany""" + extension = ExtensionNameField(read_only=True) + ouverture_ports_tcp_in = serializers\ + .SerializerMethodField('get_port_policy_input_tcp') + ouverture_ports_tcp_out = serializers\ + .SerializerMethodField('get_port_policy_output_tcp') + ouverture_ports_udp_in = serializers\ + .SerializerMethodField('get_port_policy_input_udp') + ouverture_ports_udp_out = serializers\ + .SerializerMethodField('get_port_policy_output_udp') + + class Meta: + model = IpType + fields = ('type', 'extension', 'domaine_ip_start', 'domaine_ip_stop', + 'prefix_v6', + 'ouverture_ports_tcp_in', 'ouverture_ports_tcp_out', + 'ouverture_ports_udp_in', 'ouverture_ports_udp_out',) + + def get_port_policy(self, obj, protocole, io): + if obj.ouverture_ports is None: + return [] + return map( + str, + obj.ouverture_ports.ouvertureport_set.filter( + protocole=protocole + ).filter(io=io) + ) + + def get_port_policy_input_tcp(self, obj): + """Renvoie la liste des ports ouverts en entrée tcp""" + return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.IN) + + def get_port_policy_output_tcp(self, obj): + """Renvoie la liste des ports ouverts en sortie tcp""" + return self.get_port_policy(obj, OuverturePort.TCP, OuverturePort.OUT) + + def get_port_policy_input_udp(self, obj): + """Renvoie la liste des ports ouverts en entrée udp""" + return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.IN) + + def get_port_policy_output_udp(self, obj): + """Renvoie la liste des ports ouverts en sortie udp""" + return self.get_port_policy(obj, OuverturePort.UDP, OuverturePort.OUT) + + +class ExtensionSerializer(serializers.ModelSerializer): + """Serialisation d'une extension : origin_ip et la zone sont + des foreign_key donc evalués en get_...""" + origin = serializers.SerializerMethodField('get_origin_ip') + zone_entry = serializers.SerializerMethodField('get_zone_name') + soa = serializers.SerializerMethodField('get_soa_data') + + class Meta: + model = Extension + fields = ('name', 'origin', 'origin_v6', 'zone_entry', 'soa') + + def get_origin_ip(self, obj): + return obj.origin.ipv4 + + def get_zone_name(self, obj): + return str(obj.dns_entry) + + def get_soa_data(self, obj): + return { 'mail': obj.soa.dns_soa_mail, 'param': obj.soa.dns_soa_param } + + +class MxSerializer(serializers.ModelSerializer): + """Serialisation d'un MX, evaluation du nom, de la zone + et du serveur cible, etant des foreign_key""" + name = serializers.SerializerMethodField('get_entry_name') + zone = serializers.SerializerMethodField('get_zone_name') + mx_entry = serializers.SerializerMethodField('get_mx_name') + + class Meta: + model = Mx + fields = ('zone', 'priority', 'name', 'mx_entry') + + def get_entry_name(self, obj): + return str(obj.name) + + def get_zone_name(self, obj): + return obj.zone.name + + def get_mx_name(self, obj): + return str(obj.dns_entry) + + +class TxtSerializer(serializers.ModelSerializer): + """Serialisation d'un txt : zone cible et l'entrée txt + sont evaluées à part""" + zone = serializers.SerializerMethodField('get_zone_name') + txt_entry = serializers.SerializerMethodField('get_txt_name') + + class Meta: + model = Txt + fields = ('zone', 'txt_entry', 'field1', 'field2') + + def get_zone_name(self, obj): + return str(obj.zone.name) + + def get_txt_name(self, obj): + return str(obj.dns_entry) + + +class SrvSerializer(serializers.ModelSerializer): + """Serialisation d'un srv : zone cible et l'entrée txt""" + extension = serializers.SerializerMethodField('get_extension_name') + srv_entry = serializers.SerializerMethodField('get_srv_name') + + class Meta: + model = Srv + fields = ( + 'service', + 'protocole', + 'extension', + 'ttl', + 'priority', + 'weight', + 'port', + 'target', + 'srv_entry' + ) + + def get_extension_name(self, obj): + return str(obj.extension.name) + + def get_srv_name(self, obj): + return str(obj.dns_entry) + + +class NsSerializer(serializers.ModelSerializer): + """Serialisation d'un NS : la zone, l'entrée ns complète et le serveur + ns sont évalués à part""" + zone = serializers.SerializerMethodField('get_zone_name') + ns = serializers.SerializerMethodField('get_domain_name') + ns_entry = serializers.SerializerMethodField('get_text_name') + + class Meta: + model = Ns + fields = ('zone', 'ns', 'ns_entry') + + def get_zone_name(self, obj): + return obj.zone.name + + def get_domain_name(self, obj): + return str(obj.ns) + + def get_text_name(self, obj): + return str(obj.dns_entry) + + +class DomainSerializer(serializers.ModelSerializer): + """Serialisation d'un domain, extension, cname sont des foreign_key, + et l'entrée complète, sont évalués à part""" + extension = serializers.SerializerMethodField('get_zone_name') + cname = serializers.SerializerMethodField('get_alias_name') + cname_entry = serializers.SerializerMethodField('get_cname_name') + + class Meta: + model = Domain + fields = ('name', 'extension', 'cname', 'cname_entry') + + def get_zone_name(self, obj): + return obj.extension.name + + def get_alias_name(self, obj): + return str(obj.cname) + + def get_cname_name(self, obj): + return str(obj.dns_entry) + + class ServicesSerializer(serializers.ModelSerializer): """Evaluation d'un Service, et serialisation""" server = serializers.SerializerMethodField('get_server_name') @@ -93,3 +330,31 @@ class ServicesSerializer(serializers.ModelSerializer): def get_regen_status(self, obj): return obj.need_regen() + + +class OuverturePortsSerializer(serializers.Serializer): + """Serialisation de l'ouverture des ports""" + ipv4 = serializers.SerializerMethodField() + ipv6 = serializers.SerializerMethodField() + + def get_ipv4(): + return {i.ipv4.ipv4: + { + "tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()], + "tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()], + "udp_in":[j.udp_ports_in() for j in i.port_lists.all()], + "udp_out":[j.udp_ports_out() for j in i.port_lists.all()], + } + for i in Interface.objects.all() if i.ipv4 + } + + def get_ipv6(): + return {i.ipv6: + { + "tcp_in":[j.tcp_ports_in() for j in i.port_lists.all()], + "tcp_out":[j.tcp_ports_out()for j in i.port_lists.all()], + "udp_in":[j.udp_ports_in() for j in i.port_lists.all()], + "udp_out":[j.udp_ports_out() for j in i.port_lists.all()], + } + for i in Interface.objects.all() if i.ipv6 + } diff --git a/api/urls.py b/api/urls.py index 9b9eaedd..584dd600 100644 --- a/api/urls.py +++ b/api/urls.py @@ -35,6 +35,16 @@ urlpatterns = [ url(r'^services/(?P\w+)/(?P\w+)/regen/$', views.services_server_service_regen), url(r'^services/(?P\w+)/$', views.services_server), + # DNS + url(r'^dns/mac-ip-dns/$', views.dns_mac_ip_dns), + url(r'^dns/alias/$', views.dns_alias), + url(r'^dns/corresp/$', views.dns_corresp), + url(r'^dns/mx/$', views.dns_mx), + url(r'^dns/ns/$', views.dns_ns), + url(r'^dns/txt/$', views.dns_txt), + url(r'^dns/srv/$', views.dns_srv), + url(r'^dns/zones/$', views.dns_zones), + # Firewall url(r'^firewall/ouverture_ports/$', views.firewall_ouverture_ports), diff --git a/api/views.py b/api/views.py index ce373d05..cc2237de 100644 --- a/api/views.py +++ b/api/views.py @@ -115,6 +115,191 @@ def services_server(request, server_name): return JSONSuccess(seria.data) +@csrf_exempt +@login_required +@permission_required('machines.serveur') +@accept_method(['GET']) +def dns_mac_ip_dns(request): + """The list of all active interfaces with all the associated infos + (MAC, IP, IpType, DNS name and associated zone extension) + + Returns: + GET: + A JSON Success response with a field `data` containing: + * a list of dictionnaries (one for each interface) containing: + * a field `ipv4` containing: + * a field `ipv4`: the ip for this interface + * a field `ip_type`: the name of the IpType of this interface + * a field `ipv6` containing `null` if ipv6 is deactivated else: + * a field `ipv6`: the ip for this interface + * a field `ip_type`: the name of the IpType of this interface + * a field `mac_address`: the MAC of this interface + * a field `domain`: the DNS name for this interface + * a field `extension`: the extension for the DNS zone of this interface + """ + interfaces = all_active_assigned_interfaces(full=True) + seria = FullInterfaceSerializer(interfaces, many=True) + return JSONSuccess(seria.data) + + +@csrf_exempt +@login_required +@permission_required('machines.serveur') +@accept_method(['GET']) +def dns_alias(request): + """The list of all the alias used and the DNS info associated + + Returns: + GET: + A JSON Success response with a field `data` containing: + * a list of dictionnaries (one for each alias) containing: + * a field `name`: the alias used + * a field `cname`: the target of the alias (real name of the interface) + * a field `cname_entry`: the entry to write in the DNS to have the alias + * a field `extension`: the extension for the DNS zone of this interface + """ + alias = Domain.objects.filter(interface_parent=None).filter(cname__in=Domain.objects.filter(interface_parent__in=Interface.objects.exclude(ipv4=None))).select_related('extension').select_related('cname__extension') + seria = DomainSerializer(alias, many=True) + return JSONSuccess(seria.data) + + +@csrf_exempt +@login_required +@permission_required('machines.serveur') +@accept_method(['GET']) +def dns_corresp(request): + """The list of the IpTypes possible with the infos about each + + Returns: + GET: + A JSON Success response with a field `data` containing: + * a list of dictionnaries (one for each IpType) containing: + * a field `type`: the name of the type + * a field `extension`: the DNS extension associated + * a field `domain_ip_start`: the first ip to use for this type + * a field `domain_ip_stop`: the last ip to use for this type + * a field `prefix_v6`: `null` if IPv6 is deactivated else the prefix to use + * a field `ouverture_ports_tcp_in`: the policy for TCP IN ports + * a field `ouverture_ports_tcp_out`: the policy for TCP OUT ports + * a field `ouverture_ports_udp_in`: the policy for UDP IN ports + * a field `ouverture_ports_udp_out`: the policy for UDP OUT ports + """ + ip_type = IpType.objects.all().select_related('extension') + seria = TypeSerializer(ip_type, many=True) + return JSONSuccess(seria.data) + + +@csrf_exempt +@login_required +@permission_required('machines.serveur') +@accept_method(['GET']) +def dns_mx(request): + """The list of MX record to add to the DNS + + Returns: + GET: + A JSON Success response with a field `data` containing: + * a list of dictionnaries (one for each MX record) containing: + * a field `zone`: the extension for the concerned zone + * a field `priority`: the priority to use + * a field `name`: the name of the target + * a field `mx_entry`: the full entry to add in the DNS for this MX record + """ + mx = Mx.objects.all().select_related('zone').select_related('name__extension') + seria = MxSerializer(mx, many=True) + return JSONSuccess(seria.data) + + +@csrf_exempt +@login_required +@permission_required('machines.serveur') +@accept_method(['GET']) +def dns_ns(request): + """The list of NS record to add to the DNS + + Returns: + GET: + A JSON Success response with a field `data` containing: + * a list of dictionnaries (one for each NS record) containing: + * a field `zone`: the extension for the concerned zone + * a field `ns`: the DNS name for the NS server targeted + * a field `ns_entry`: the full entry to add in the DNS for this NS record + """ + ns = Ns.objects.exclude(ns__in=Domain.objects.filter(interface_parent__in=Interface.objects.filter(ipv4=None))).select_related('zone').select_related('ns__extension') + seria = NsSerializer(ns, many=True) + return JSONSuccess(seria.data) + + +@csrf_exempt +@login_required +@permission_required('machines.serveur') +@accept_method(['GET']) +def dns_txt(request): + """The list of TXT record to add to the DNS + + Returns: + GET: + A JSON Success response with a field `data` containing: + * a list of dictionnaries (one for each TXT record) containing: + * a field `zone`: the extension for the concerned zone + * a field `field1`: the first field in the record (target) + * a field `field2`: the second field in the record (value) + * a field `txt_entry`: the full entry to add in the DNS for this TXT record + """ + txt = Txt.objects.all().select_related('zone') + seria = TxtSerializer(txt, many=True) + return JSONSuccess(seria.data) + + +@csrf_exempt +@login_required +@permission_required('machines.serveur') +@accept_method(['GET']) +def dns_srv(request): + """The list of SRV record to add to the DNS + + Returns: + GET: + A JSON Success response with a field `data` containing: + * a list of dictionnaries (one for each SRV record) containing: + * a field `extension`: the extension for the concerned zone + * a field `service`: the name of the service concerned + * a field `protocole`: the name of the protocol to use + * a field `ttl`: the Time To Live to use + * a field `priority`: the priority for this service + * a field `weight`: the weight for same priority entries + * a field `port`: the port targeted + * a field `target`: the interface targeted by this service + * a field `srv_entry`: the full entry to add in the DNS for this SRV record + """ + srv = Srv.objects.all().select_related('extension').select_related('target__extension') + seria = SrvSerializer(srv, many=True) + return JSONSuccess(seria.data) + + +@csrf_exempt +@login_required +@permission_required('machines.serveur') +@accept_method(['GET']) +def dns_zones(request): + """The list of the zones managed + + Returns: + GET: + A JSON Success response with a field `data` containing: + * a list of dictionnaries (one for each zone) containing: + * a field `name`: the extension for the zone + * a field `origin`: the server IPv4 for the orgin of the zone + * a field `origin_v6`: `null` if ipv6 is deactivated else the server IPv6 for the origin of the zone + * a field `soa` containing: + * a field `mail` containing the mail to contact in case of problem with the zone + * a field `param` containing the full soa paramters to use in the DNS for this zone + * a field `zone_entry`: the full entry to add in the DNS for the origin of the zone + """ + zones = Extension.objects.all().select_related('origin') + seria = ExtensionSerializer(zones, many=True) + return JSONSuccess(seria.data) + @csrf_exempt @login_required @permission_required('machines.serveur')