Aller au contenu

Creation et exposition d'un service DNS publique

cover

Qu'est-ce que le DNS ?

Le Domain Name System (DNS) est un système qui fournit des noms lisibles par l'homme pour les ordinateurs, services et autres ressources connectés à Internet. Les enregistrements de base permettent de traduire un Nom de domaine (que les humains peuvent comprendre) en une IP Address (que l'ordinateur comprend pour le routage).

DNS est utilisé tout le temps, comme en ce moment, pour accéder à ce blog, votre ordinateur fait une requête DNS pour traduire lunik.tiwabbit.fr en une [IP] [adresse-ip-wikipedia]. Ensuite, il a demandé la page Web à l'adresse IP résolue par la requête DNS. Vous pouvez visualiser ce comportement en ouvrant la console développeur sur votre navigateur web :

blog-dns-debug-through-developper-console

Warning

Toutes les adresses IP et les noms DNS utilisés dans cet article de blog ne sont plus utilisés par mon projet. S'il vous plaît, ignorez-les.

Faire une requête DNS

Vous pouvez effectuer une requête de base DNS en utilisant nslookup sur votre terminal :

nslookup lunik.tiwabbit.fr

Vous obtiendrez quelque chose comme :

Nom : lunik.tiwabbit.fr
Adresse : 13.224.247.30
Nom : lunik.tiwabbit.fr
Adresse : 13.224.247.31
Nom : lunik.tiwabbit.fr
Adresse : 13.224.247.51
Nom : lunik.tiwabbit.fr
Adresse : 13.224.247.100

Où déployer le service ?

J'ai décidé d'héberger mon service DNS sur un fournisseur de cloud public nommé Scaleway.

Voici une vue globale de l'architecture :

scalway-architecture

J'ai décidé d'exposer deux serveurs publics DNS un dans chaque région : France (Paris 1) et Pays-Bas (Amsterdam 1).

J'ai choisi ces deux régions car elles sont les seules à proposer des instances Stardust. Les serveurs DNS ne nécessitent pas une tonne de ressources, cela me permet de réduire le coût du projet au minimum.

Les deux serveurs sont exposés avec des adresses publiques flexible IP (une v4 et une v6 chacune) qui peuvent être résolues via deux entrées DNS pour plus de commodité.

La sécurité était ma principale préoccupation, j'ai donc mis en place un groupe de sécurité sur mes instances me permettant de filtrer le trafic entrant et sortant.

Déploiement de l'infrastructure

Maintenant que je sais quelle architecture je veux pour le service. Je dois le déployer sur le fournisseur de cloud. J'ai décidé d'utiliser Terraform pour créer toutes les ressources et les lier ensemble.

ça a été assez simple de créer ma ressource en suivant la documentation Terraform. Après 1 heure j'avais le code nécessaire. Il ne me restait plus qu'à l'appliquer.

Voici à quoi ressemblait le plan (tronqué) :

Terraform will perform the following actions:

  # scaleway_instance_ip.france["tiwabbit-dns-01"] will be created
  + resource "scaleway_instance_ip" "france" {
      + address         = (known after apply)
      + id              = (known after apply)
      + reverse         = "dns01.tiwabbit.fr"
      + server_id       = (known after apply)
      + zone            = "fr-par-1"
    }

  # scaleway_instance_security_group.france will be created
  + resource "scaleway_instance_security_group" "france" {
      + description             = "dns"
      + enable_default_security = true
      + external_rules          = false
      + id                      = (known after apply)
      + inbound_default_policy  = "drop"
      + name                    = "public-dns"
      + outbound_default_policy = "accept"
      + stateful                = true
      + zone                    = "fr-par-1"

      + inbound_rule {
          + action   = "accept"
          + ip       = "X.X.X.X"
          + port     = 22
          + protocol = "TCP"
        }
      + inbound_rule {
          + action   = "accept"
          + ip_range = "0.0.0.0/0"
          + port     = 53
          + protocol = "UDP"
        }
      + inbound_rule {
          + action   = "accept"
          + ip_range = "::/0"
          + port     = 53
          + protocol = "UDP"
        }
    }

  # scaleway_instance_server.france["tiwabbit-dns-01"] will be created
  + resource "scaleway_instance_server" "france" {
      + enable_dynamic_ip                = false
      + enable_ipv6                      = true
      + id                               = (known after apply)
      + image                            = "fr-par-1/5c8bbf4b-10f0-4cac-863b-4561781043ff"
      + ip_id                            = (known after apply)
      + ipv6_address                     = (known after apply)
      + ipv6_gateway                     = (known after apply)
      + ipv6_prefix_length               = (known after apply)
      + name                             = "tiwabbit-dns-01"
      + private_ip                       = (known after apply)
      + public_ip                        = (known after apply)
      + security_group_id                = "fr-par-1/dns"
      + state                            = "started"
      + type                             = "STARDUST1-S"
      + zone                             = "fr-par-1"

      + root_volume {
          + size_in_gb            = "10"
          + volume_id             = (known after apply)
        }
    }

[...]

Plan: 8 to add, 0 to change, 0 to destroy.

Après avoir terminé la création, voici ce que j'avais sur la console Scaleway :

Instances : scaleway-console-instances

Volumes : scaleway-console-volumes

[Adresses IP][adresse-ip-wikipedia] : scaleway-console-flexible-ips

Groupes de sécurité : scaleway-console-security-group Ici, vous pouvez voir que je n'autorise le trafic entrant que sur le port 53 qui est celui utilisé par les serveurs DNS. (La première règle avec le port 22 me permet de gérer le serveur depuis un emplacement privé en utilisant SSH)

Installation et configuration du service

Maintenant que j'ai deux nouveaux serveurs à ma disposition, je dois installer et configurer un service DNS à faire tourner dessus. Ils tournent avec Fedora 32 avec un 5.6 Linux Kernel :

[root@tiwabbit-dns-01 ~]# screenfetch
           /:-------------:\          root@tiwabbit-dns-01
        :-------------------::        OS: Fedora 
      :-----------/shhOHbmp---:\      Kernel: x86_64 Linux 5.6.6-300.fc32.x86_64
    /-----------omMMMNNNMMD  ---:     Uptime: 18m
   :-----------sMMMMNMNMP.    ---:    Packages: 405
  :-----------:MMMdP-------    ---\   Shell: bash 5.0.11
 ,------------:MMMd--------    ---:   Disk: 1.0G / 9.6G (11%)
 :------------:MMMd-------    .---:   CPU: AMD EPYC 7281 16-Core @ 2.096GHz
 :----    oNMMMMMMMMMNho     .----:   RAM: 293MiB / 969MiB
 :--     .+shhhMMMmhhy++   .------/  
 :-    -------:MMMd--------------:   
 :-   --------/MMMd-------------;    
 :-    ------/hMMMy------------:     
 :-- :dMNdhhdNMMNo------------;      
 :---:sdNMMMMNds:------------:       
 :------:://:-------------::         
 :---------------------://  

Fait amusant les instance Scaleway Stardust fonctionne sur AMD EPYC SoC !

J'ai décidé d'utiliser le serveur Bind9 DNS car il est déjà packagé dans de nombreuses distributions, il y a beaucoup de documentation et la communauté est importante.

J'utilise Ansible pour déployer toute la pile : config linux de base, Bind9, Firewalld, Fail2Ban

Tip

J'ai déjà parlé de Firewalld et de Fail2Ban dans un autre article de blog : Sécurisation de mon point d'entré web des menaces externes

Mais pour les besoins de cet article de blog, je vais détailler les commandes bash équivalentes qui peuvent être utilisées.

Installation et configuration de Bind9

L'installation de Bind9 est assez simple. Comme il est déjà packagé, je n'ai qu'à faire une simple commande pour l'installer :

[root@tiwabbit-dns-01 ~]# dnf install bind bind-utils

Last metadata expiration check: 0:02:41 ago on Fri 05 Nov 2021 09:49:15 AM UTC.
Dependencies resolved.
============================================================================================================================================================================================================
 Package                                                       Architecture                            Version                                               Repository                                Size
============================================================================================================================================================================================================
Installing:
 bind                                                          x86_64                                  32:9.11.28-1.fc32                                     updates                                  2.0 M
 bind-utils                                                    x86_64                                  32:9.11.28-1.fc32                                     updates                                  233 k
Installing dependencies:
 bind-dnssec-doc                                               noarch                                  32:9.11.28-1.fc32                                     updates                                   46 k
 bind-libs                                                     x86_64                                  32:9.11.28-1.fc32                                     updates                                   90 k
 bind-libs-lite                                                x86_64                                  32:9.11.28-1.fc32                                     updates                                  1.1 M
 bind-license                                                  noarch                                  32:9.11.28-1.fc32                                     updates                                   16 k
 fstrm                                                         x86_64                                  0.5.0-2.fc32                                          fedora                                    28 k
 mariadb-connector-c                                           x86_64                                  3.1.12-1.fc32                                         updates                                  203 k
 mariadb-connector-c-config                                    noarch                                  3.1.12-1.fc32                                         updates                                   11 k
 policycoreutils-python-utils                                  noarch                                  3.0-2.fc32                                            fedora                                    83 k
 protobuf-c                                                    x86_64                                  1.3.2-2.fc32                                          fedora                                    35 k
 python3-bind                                                  noarch                                  32:9.11.28-1.fc32                                     updates                                   64 k
Installing weak dependencies:
 bind-dnssec-utils                                             x86_64                                  32:9.11.28-1.fc32                                     updates                                  128 k

Transaction Summary
============================================================================================================================================================================================================
Install  13 Packages

Total download size: 4.0 M
Installed size: 10 M
Is this ok [y/N]:

Une fois installé, en utilisant la documentation Bind9 j'ai mis la configuration suivante dans /etc/named.conf :

acl "managment" {
    X.X.X.X/32;
};
acl "public" {
    0.0.0.0/0;
    ::/0;
};

options {

  dump-file          "/etc/named/data/cache_dump.db";
  statistics-file    "/etc/named/data/named_stats.txt";
  memstatistics-file "/etc/named/data/named_mem_stats.txt";
  secroots-file      "/etc/named/data/named.secroots";
  recursing-file     "/etc/named/data/named.recursing";

  listen-on port 53 { any; };
  listen-on-v6 port 53 { any; };

  allow-transfer { none; };

  max-cache-size 70%;
  allow-query-cache {
    127.0.0.1;
    localhost;
    managment;
    public;
  };

  allow-query {
    127.0.0.1;
    localhost;
    managment;
    public;
  };

  recursion yes;
  allow-recursion {
    127.0.0.1;
    localhost;
    managment;
    public;
  };

  dnssec-enable yes;
  dnssec-validation yes;

  prefetch 4 10;

  rate-limit {
    ipv4-prefix-length 28;
    ipv6-prefix-length 56;
    responses-per-second 20;
    window 5;
    slip 3;
  };

  managed-keys-directory "/var/named/dynamic";
  geoip-directory        "/usr/share/GeoIP";

  pid-file        "/run/named/named.pid";
  session-keyfile "/run/named/session.key";

  hostname "dns01.tiwabbit.fr";
  server-id "dns01.tiwabbit.fr";

  /* https://fedoraproject.org/wiki/Changes/CryptoPolicy */
  include "/etc/crypto-policies/back-ends/bind.config";
};

statistics-channels {
  inet 127.0.0.1 port 8053 allow { 127.0.0.1; };
};


logging {
  channel client_file {
    file "/var/log/named/client.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel cname_file {
    file "/var/log/named/cname.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel config_file {
    file "/var/log/named/config.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel database_file {
    file "/var/log/named/database.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel default_file {
    file "/var/log/named/default.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel delegation-only_file {
    file "/var/log/named/delegation-only.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel dispatch_file {
    file "/var/log/named/dispatch.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel dnssec_file {
    file "/var/log/named/dnssec.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel dnstap_file {
    file "/var/log/named/dnstap.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel edns-disabled_file {
    file "/var/log/named/edns-disabled.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel general_file {
    file "/var/log/named/general.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel lame-servers_file {
    file "/var/log/named/lame-servers.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel network_file {
    file "/var/log/named/network.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel notify_file {
    file "/var/log/named/notify.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel queries_file {
    file "/var/log/named/queries.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel query-errors_file {
    file "/var/log/named/query-errors.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel rate-limit_file {
    file "/var/log/named/rate-limit.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel resolver_file {
    file "/var/log/named/resolver.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel rpz_file {
    file "/var/log/named/rpz.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel security_file {
    file "/var/log/named/security.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel spill_file {
    file "/var/log/named/spill.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel trust-anchor-telemetry_file {
    file "/var/log/named/trust-anchor-telemetry.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel unmatched_file {
    file "/var/log/named/unmatched.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel update_file {
    file "/var/log/named/update.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel update-security_file {
    file "/var/log/named/update-security.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel xfer-in_file {
    file "/var/log/named/xfer-in.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };
  channel xfer-out_file {
    file "/var/log/named/xfer-out.log" versions 3 size 5m;
    severity dynamic;
    print-time yes;
    print-category yes;
    print-severity yes;
  };

  category client { client_file; default_debug; };
  category cname { cname_file; default_debug; };
  category config { config_file; default_debug; };
  category database { database_file; default_debug; };
  category default { default_file; default_debug; };
  category delegation-only { delegation-only_file; default_debug; };
  category dispatch { dispatch_file; default_debug; };
  category dnssec { dnssec_file; default_debug; };
  category dnstap { dnstap_file; default_debug; };
  category edns-disabled { edns-disabled_file; default_debug; };
  category general { general_file; default_debug; };
  category lame-servers { lame-servers_file; default_debug; };
  category network { network_file; default_debug; };
  category notify { notify_file; default_debug; };
  category queries { queries_file; default_debug; };
  category query-errors { query-errors_file; default_debug; };
  category rate-limit { rate-limit_file; default_debug; };
  category resolver { resolver_file; default_debug; };
  category rpz { rpz_file; default_debug; };
  category security { security_file; default_debug; };
  category spill { spill_file; default_debug; };
  category trust-anchor-telemetry { trust-anchor-telemetry_file; default_debug; };
  category unmatched { unmatched_file; default_debug; };
  category update { update_file; default_debug; };
  category update-security { update-security_file; default_debug; };
  category xfer-in { xfer-in_file; default_debug; };
  category xfer-out { xfer-out_file; default_debug; };
};

Waouh c'est beaucoup !

Les lignes importantes à configurer sont :

Listes de contrôle d'accès

Les ACLs permettent de choisir le comportement du service en fonction du client IP address.

Jetons un œil à ma configuration :

acl "managment" {
    X.X.X.X/32;
};
acl "public" {
    0.0.0.0/0;
    ::/0;
};

Ici, je crée deux groupes ACL : public et managment. Dans chacun de ces blocs acl, je peux mettre autant de CIDR que je veux. public contient l'IPv4 et l'IPv6 global CIDR. managment contient une unique IP CIDR (celle utilisée pour configurer le service).

Vous pouvez réutiliser ces groupes ACL dans d'autres parties de la configuration comme dans allow-query, allow-query-cache, allow-recursion.

Comportement des requêtes

J'ai choisi de n'implémenter que trois comportements de requête pour rester simple :

  • allow-query définit qui est autorisé à interroger mon service DNS. Si l'IP du client n'est pas dans la liste, le serveur ne répondra pas.
  • allow-query-cache définit depuis quel client le serveur doit mettre en cache les réponses. Cela permet à la requête d'être résolue plus rapidement la prochaine fois.
  • allow-recursion définit qui peut faire des requêtes récursives. Query recursion est un mécanisme DNS qui trouve l'IP associé à une entrée DNS en faisant tout les requêtes nécessaires une par une depuis les serveurs racine. Cela permet d'être indépendant lors de la résolution des requêtes (vous n'avez pas besoin de transmettre la requête à un autre serveur public DNS comme 8.8.8.8 ou 1.1.1.1)

Prefetching

Tout est dans le nom. Cette configuration permet au serveur de faire une requête DNS par lui-même en prévision d'autres requêtes. Cela permet d'être plus rapide à répondre la plupart du temps si une entrée DNS est demandée très souvent.

Chaque entrée DNS a un Time To Live (ou TTL). Vous pouvez l'obtenir en utilisant dig :

dig lunik.tiwabbit.fr

; <<>> DiG 9.11.28-RedHat-9.11.28-1.fc32 <<>> lunik.tiwabbit.fr
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41640
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;lunik.tiwabbit.fr.   IN  A

;; ANSWER SECTION:
lunik.tiwabbit.fr.  66  IN  A 52.222.158.86
lunik.tiwabbit.fr.  66  IN  A 52.222.158.38
lunik.tiwabbit.fr.  66  IN  A 52.222.158.55
lunik.tiwabbit.fr.  66  IN  A 52.222.158.103

;; Query time: 0 msec
;; SERVER: 10.194.3.3#53(10.194.3.3)
;; WHEN: Fri Nov 05 10:13:42 UTC 2021
;; MSG SIZE  rcvd: 110

Dans la ANSWER SECTION :

lunik.tiwabbit.fr.  66  IN  A 52.222.158.86

66 est le TTL de cette entrée DNS. Cela signifie que dans 66 secondes, elle n'est plus valide et le client doit faire une autre demande pour obtenir la nouvelle IP (la plupart du temps, cela ne change pas).

Dans ma configuration j'ai :

prefetch 4 10;

Si le serveur a mis en cache une entrée DNS avec 4 secondes ou moins de TTL restant alors il fera une requête DNS pour actualiser celle mise en cache. 10 est un paramètre facultatif qui définit l'éligibilité de l'enregistrement pour la prélecture.

Limitation de requêtes

C'est peut-être l'un des paramètres les plus importants d'un serveur public DNS. Le but de cette configuration est de limiter le nombre de réponses du serveur si un client demande plusieurs fois la même entrée DNS. Il est très utile pour atténuer les attaques par amplification DNS.

Voici la configuration que j'ai :

rate-limit {
  ipv4-prefix-length 28;
  ipv6-prefix-length 56;
  responses-per-second 20;
  window 5;
  slip 3;
};

Si un client demande plus de 20 fois la même entrée DNS sur une période de 5 secondes, le serveur ignore 3 réponses avant de répondre (indéfiniment). Pour empêcher les attaques via de large sous-réseaux, la limite de débit s'étend à un /28 sous-réseau IPv4 et à un /56 sous-réseau IPv6.

Journalisation

j'ai fait le choix d'être très verbeux avec les logs c'est pourquoi j'ai configuré tous les canaux possibles.

Les plus intéressants sont - queries qui journalise chaque requête résolue par le serveur :

# /var/log/named/queries.log
05-Nov-2021 10:21:43.536 queries: info: client @0x7f76073dfe50 X.X.X.X#14821 (mms-iad.sp-prod.net): query: mms-iad.sp-prod.net IN A +E(0)DV (10.70.2.235)
05-Nov-2021 10:21:43.545 queries: info: client @0x7f760793d450 X.X.X.X#47583 (psja.isd.us): query: psja.isd.us IN MX +E(0)DV (10.70.2.235)
05-Nov-2021 10:21:43.547 queries: info: client @0x7f760759b400 X.X.X.X#50020 (global.reputation.invincea.com): query: global.reputation.invincea.com IN A +E(0)DV (10.70.2.235)
05-Nov-2021 10:21:43.556 queries: info: client @0x7f76075dddc0 X.X.X.X#38383 (forconzoomnyc233mmr.zoom.us): query: forconzoomnyc233mmr.zoom.us IN A + (10.70.2.235)
05-Nov-2021 10:21:43.566 queries: info: client @0x7f76077ba820 X.X.X.X#39161 (ps-membership.us-ctkip-ps3.dell.com): query: ps-membership.us-ctkip-ps3.dell.com IN A + (10.70.2.235)
05-Nov-2021 10:21:43.618 queries: info: client @0x7f7607a9f2c0 X.X.X.X#39161 (ps-membership.usgit.dell.com): query: ps-membership.usgit.dell.com IN A + (10.70.2.235)
05-Nov-2021 10:21:43.622 queries: info: client @0x7f76077fcbc0 X.X.X.X#56424 (icn.intl-global-adns.alibabacloud.com): query: icn.intl-global-adns.alibabacloud.com IN A + (10.70.2.235)
05-Nov-2021 10:21:43.623 queries: info: client @0x7f760765f400 X.X.X.X#38383 (forcon-zoomca193-123-14-158mmr.zoom.us): query: forcon-zoomca193-123-14-158mmr.zoom.us IN A + (10.70.2.235)
05-Nov-2021 10:21:43.635 queries: info: client @0x7f75f0ce5910 X.X.X.X#56548 (sfc-idzwww.riotgames.roblox.com.ru): query: sfc-idzwww.riotgames.roblox.com.ru IN A + (10.70.2.235)
- rate-limit qui enregistre tous les événements concernant le comportement de limitation de requêtes :
# /var/log/named/rate-limit.log
05-Nov-2021 10:21:39.631 rate-limit: info: client @0x7f76077cdd80 X.X.X.X#38383 (zoomff134-224-74-182mmrforcon.zoom.us): rate limit drop NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:39.694 rate-limit: info: client @0x7f75f0cf3f10 X.X.X.X#38383 (bisdtx-orgforcon.zoom.us): rate limit drop NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:39.752 rate-limit: info: client @0x7f76077fcbc0 X.X.X.X#38383 (kirklandwa-forcon.zoom.us): rate limit slip NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:39.863 rate-limit: info: client @0x7f76077eab00 X.X.X.X#38383 (friendsnrcforcon.zoom.us): rate limit drop NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:39.942 rate-limit: info: client @0x7f76078e8610 X.X.X.X#38383 (deltatrust-org-uk-forcon.zoom.us): rate limit drop NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:40.857 rate-limit: info: client @0x7f7607a13120 X.X.X.X#38383 (12mmrforcon.zoom.us): rate limit slip NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:40.871 rate-limit: info: client @0x7f76076509a0 X.X.X.X#38383 (datamarkgisforcon.zoom.us): rate limit drop NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:40.891 rate-limit: info: client @0x7f7607866d60 X.X.X.X#38383 (wvsd208-forcon.zoom.us): rate limit drop NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:40.907 rate-limit: info: client @0x7f7607146a70 X.X.X.X#38383 (zoomva198-251-217-184mmrforcon.zoom.us): rate limit slip NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:40.968 rate-limit: info: client @0x7f760713b020 X.X.X.X#38383 (zoomdvs185mmr-forcon.zoom.us): rate limit drop NXDOMAIN response to X.X.X.X/28 for zoom.us  (4ef96ce9)
05-Nov-2021 10:21:43.761 rate-limit: info: *stop limiting NXDOMAIN responses to X.X.X.X/28 for buffer.com  (bc75c42e)
05-Nov-2021 10:21:43.761 rate-limit: info: *stop limiting error responses to X.X.X.X/28
05-Nov-2021 10:21:43.761 rate-limit: info: *stop limiting NXDOMAIN responses to X.X.X.X/28 for restintergamma.nl  (97dd2767)
05-Nov-2021 10:21:43.761 rate-limit: info: *stop limiting NXDOMAIN responses to X.X.X.X/28 for alibabacloud.com  (d22fa033)
05-Nov-2021 10:21:43.761 rate-limit: info: *stop limiting NXDOMAIN responses to X.X.X.X/28 for dell.com  (98e605b5)
05-Nov-2021 10:21:43.761 rate-limit: info: *stop limiting error responses to X.X.X.X/28
05-Nov-2021 10:21:43.761 rate-limit: info: *stop limiting NXDOMAIN responses to X.X.X.X/28 for zoom.us  (4ef96ce9)

Surveillance

Je choisis d'utiliser Datadog pour surveiller les serveurs DNS.

Installation de l'agent Datadog

L'installation est assez simple avec Ansible car ils fournissent un role galaxy. Je l'ai utilisé avec la configuration suivante :

---

datadog_api_key: "{{ vault_datadog_api_key }}"
datadog_site: "datadoghq.eu"

datadog_agent_flavor: "datadog-agent"
datadog_agent_major_version: 7

datadog_enabled: yes

datadog_bind9_integration_version: 1.0.0

datadog_config:
  logs_enabled: true

datadog_checks:
  bind9:
    init_config:
    instances:
      - name: bind9
        url: "http://127.0.0.1:{{ bind_statistics_port }}"
    logs:
      - type: file
        path: /var/log/named/queries.log
        service: named
        source: bind9
        sourcecategory: queries
      - type: file
        path: /var/log/named/rate-limit.log
        service: named
        source: bind9
        sourcecategory: rate-limit

J'ai activé les intégrations Bind9 et les journaux.

Une fois déployé, je peux voir mes agents via la console web Datadog dans le panneau Infrastructure :

datadog-host-map

Métriques et journaux Datadog

Métriques

Vu que j'ai activé l'intégration Bind9 dans la configuration de l'agent, je dois faire de même dans la console Web. Dans le panneau Integrations, recherchez Bind9 et suivez le guide de configuration.

datadog-bind-integration

Maintenant, je peux voir les métriques apparaître.

Journaux

Les journaux s'affichent déjà dans l'explorateur de journaux dans le panneau Logs :

datadog-log-explorer

Mais Datadog ne sait pas comment interpréter ces journaux. Pour l'instant, il ne voit qu'une grande chaîne de caractères. Si je veux analyser ces journaux, Datadog doit les parser pour moi.

Dans le pipeline de journaux intégré Datadog dans le panneau Logs, je peux définir une liste de modèles et d'actions pour parser le fichier journal produit par le service Bind9. Sur certains logiciels plus populaires, Datadog a déjà fait le travail pour vous, mais il semble que Bind9 soit une exception.

datadog-pipeline-library

Définition des journaux d'analyse des pipelines

J'ai d'abord créé un nouveau pipeline et utilisé des filtres prédéfinis : source:bind9 et sourcecategory:queries. Ces deux filtres sont déduits de la configuration de l'agent Datadog précédente :

datadog_checks:
  bind9:
[...]
    logs:
[...]
      - type: file
        path: /var/log/named/queries.log
        service: named
        source: bind9
        sourcecategory: queries
[...]

Maintenant que je vais uniquement parser les journaux à partir de la bonne source, j'ai besoin d'un parser Grok pour parser la chaîne de journal. Le parser Grok définit des blocs dans la chaîne et les place dans des variables. Lorsque la ligne de journal est parsé, elle renvoie un objet JSON magnifiquement formaté.

Les blocs sont définis par des expressions rationnelles "simplifiées" : number, word, date, ip, ...

Datadog me permet de le faire très simplement en ayant une vue d'analyse "en direct" où vous pouvez voir en temps réel quelle partie du journal est analysée par quel bloc.

datadog-grok-parser

Voici donc la règle finale de parsing Grok pour les journaux requêtes :

default %{number}-%{word}-%{number}\s+%{date("HH:mm:ss.SSS"):timestamp}\s+queries:\s+%{word:status}:\s+client\s+@%{word:client.data}\s+%{ip:client.ip}\#%{number:client.port}\s+\(%{hostname:query.hostname}\):\s+query:\s+%{hostname:query.fqdn}\s+%{word:query.location}\s+%{word:query.qcode}\s+.*\s+\(%{ip:server.ip}\).*

Avec cet exemple :

27-Oct-2021 16:53:43.594 queries: info: client @0x7fad52ee1fd0 127.0.0.1#64318 (google.fr): query: google.fr IN A +E(0) (10.69.86.243)
Je reçois :
{
  "status": "info",
  "query": {
    "qcode": "A",
    "location": "IN",
    "hostname": "google.fr",
    "fqdn": "google.fr"
  },
  "client": {
    "ip": "127.0.0.1",
    "data": "0x7fad52ee1fd0",
    "port": 64318
  },
  "timestamp": 1636131223594,
  "server": {
    "ip": "10.69.86.243"
  }
}

Génial !

Datadog permet d'améliorer ce nouvel objet JSON avec des métadonnées supplémentaires. Dans mon cas, puisque j'ai l'addresse IP du client, je peux utiliser l'analyseur GeoIP pour trouver des métadonnées sur cette IP. Maintenant, je peux déterminer le FAI, le pays et même la ville de ce client.

Voici un exemple :

05-Nov-2021 14:07:16.442 queries: info: client @0x7f6b462b2900 X.X.X.X#38383 (zoom.us): query: zoom.us IN A + (10.70.2.235)
{
  "client": {  
    "geoip": { 
      "as": {  
        "domain": "online.net",
        "name": "ONLINE S.A.S.",
        "number": "AS12876",
        "route": "51.15.0.0/16",
        "type": "isp"
      },
      "city": {
        "name": "Paris"
      },
      "continent": {
        "code": "EU",
        "name": "Europe"
      },
      "country": {
        "iso_code": "FR",
        "name": "France"
      },
      "ipAddress" : "X.X.X.X",
      "location": {
        "latitude": "48.85341",
        "longitude" "2.3488"
      },
      "subdivision": {
        "name": "Île-de-France"
      },
      "timezone": "Europe/Paris"
    }
  }
}

Tableau de bord

Je peux maintenant commencer la partie amusante de l'utilisation d'un service de surveillance : Make Dashboard and Graphs !

Voici ceux que j'ai créés :

datadog-dashboard-overview datadog-dashboard-queries-by-code datadog-dashboard-client-by-location

Conclusion

J'ai maintenant un serveur public DNS pleinement opérationnel. Je peux maintenant faire des configurations avancées si je veux, comme bloquer certains noms de domaine (malware, trucs illégaux, ...).

Note : Si vous souhaitez rendre votre DNS public accessible à tous, vous pouvez le publier sur public-dns.info