Comment sauvegarder ses zones DNS

Tu gères plusieurs domaines et tu aimerais sauvegarder toutes les configurations de tes zones DNS ?

Je me suis retrouvé confronté au même problème et j’ai voulu faire un outil qui me permettait de sauvergarder toutes mes zones DNS d’un seul coup.

J’ai découvert l’outil dns-crawler, et je l’ai adapté pour faire une sauvegarde simple et facile à utiliser. Je prends ensuite le résultat et je le décortique avec Python, pour en faire une base de données SQLite facile à utiliser dans le futur.

 ```mermaid
graph TD;
    A[Chargement des bibliothèques et du jeu de données] --> B[Conversion de la colonne parent en DataFrame];
    B --> C[Explosion de la colonne NS pour créer un nouveau DataFrame];
    C --> D1[Extraction des adresses IPv4];
    C --> D2[Extraction des adresses IPv6];
    A --> E[Extraction des valeurs de la colonne results];
    A --> F[Préparation des DataFrames finaux];
    B & E --> F;
    D1 & D2 & F --> G[Enregistrement des données dans une base de données SQLite]
```

Ce graphe montre le flux du programme, depuis le chargement des bibliothèques et des données jusqu’à l’enregistrement final des données traitées. Il illustre également les étapes de conversion, d’explosion de colonnes et d’extraction de données effectuées avant la préparation finale des DataFrames et leur enregistrement dans une base de données SQLite.

Créer la liste des domaines à sauvegarder

Nous avons souvent une liste de sous-domaines qui vont se répéter au travers des domaines de nos différents services sur le web. Ce sont des sous-domaines utilisés pour l’authentification des courriels ou pour la vérification de l’appartenance du domaine pour différents services d’indexation. Un exemple, c’est le sous-domaine _dmarc pour la validation DMARC.

Pour créer la liste de tous les domaines à sauvegarder, je conseille de listes les domaines dans un fichier dns-list.txt. Puis, on liste les préfixes dnas un fichier prefix-list.txt. On ajoute une ligne vide au début pour le cas sans préfixe.

On peut joindre les deux listes à l’aide de la commande Unix join :

#!/bin/zsh

join -t '' -j 2 prefix-list.txt dns-list.txt | sort -u > domain-list-n.txt
tr < domain-list-n.txt -d '\000' > domain-list.txt
rm domain-list-n.txt

L’outil join va inclure des caractères null dans mon fichier intermédiaire domain-list-n.txt, que j’enlève à la deuxième ligne avec tr.

Extraire les données


J’utilise ensuite l’outil dns-crawler, qui retourne un fichier JSON pour chaque domaine. Je les écris tous dans un fichier JSONL, qui inclus un JSON par ligne.

dns-crawler domain-list.txt > results.jsonl

Nettoyer les données

Les fichiers JSON enregistrés contiennent beaucoup de données. Elles ne sont pas toutes nécessaires pour notre sauvegarde de zone DNS. Nous allons donc utiliser Python pour nettoyer ces données et les enregistrer dans une base de données SQLite prête à l’usage.

Je vais d’abord importer Pandas et SQLAlchemy

import pandas as pd
from sqlalchemy import create_engine

Je vais ensuite charger les données JSONL.

results = pd.read_json('results.jsonl', lines=True)

Données parent

Je convertis la colonne parent en DataFrame pour Pandas. Cette conversion facilite l’accès et le traitement des données contenues dans la colonne ‘parent’, étant donné que nous avons une structure de données complexe.

parent_df = results['parent'].apply(pd.Series)

La méthode explode est utilisée pour transformer chaque valeur de liste en une ligne distincte. Enfin, les lignes avec des valeurs manquantes sont supprimées à l’aide de la méthode dropna.

ns_df_ = pd.DataFrame({'ns': parent_df['ns']}).explode('ns').dropna()['ns'].apply(pd.Series)

Nous allons maintenant extraire les adresses IPv4 et IPv6 du DataFrame ns_df_. Tout d’abord, nous remplissons les valeurs manquantes dans les colonnes ‘ipv4’ et ‘ipv6’ par des chaînes vides à l’aide de la méthode fillna.

Ensuite, nous appliquons une fonction lambda pour extraire uniquement l’adresse IP de chaque dictionnaire contenu dans la liste. Les colonnes d’origine (‘ipv4’ et ‘ipv6’) sont supprimées à la fin.

Données results

Dans cette section, nous allons extraire les données contenues dans la colonne ‘results’, qui contient la plupart des informations publiques disponibles sur le serveur de noms.

   results_df = results['results'].apply(lambda x: x['DNS_LOCAL']).apply(pd.Series)

Nous appliquons une fonction lambda pour extraire le dictionnaire ‘DNS_LOCAL’ de chaque valeur, puis convertissons ces dictionnaires en un nouveau DataFrame appelé results_df. Chaque type d’enregistrement DNS se trouve dans une colonne.

Depuis ces données, je construis un DataFrame pour chaque type d’enregistrement DNS en extrayant la colonne, distribue la liste sur plusieurs lignes avec explode, puis en convertissant en série Pandas.

Je termine en ajoutant un préfixe, sinon je ne sais plus de quelle colonne ça provient.

base_df = results.drop(columns=['parent', 'results']).copy()
ns_df = ns_df_.explode(['ipv4_', 'ipv6_']).copy()
mail_df = results_df['MAIL'].explode().dropna().apply(pd.Series).add_prefix('MAIL_')
web4_df = results_df['WEB4'].explode().dropna().apply(pd.Series).add_prefix('WEB4_')
web4www_df = results_df['WEB4_www'].explode().dropna().apply(pd.Series).add_prefix('WEB4_www_')
web6_df = results_df['WEB6'].explode().dropna().apply(pd.Series).add_prefix('WEB6_')
web6www_df = results_df['WEB6_www'].explode().dropna().apply(pd.Series).add_prefix('WEB6_www_')
txt_df = results_df['TXT'].explode().dropna().apply(pd.Series).add_prefix('TXT_')

Suppression des données GeoIP et ajout de la date

Je n’ai pas utilisé GeoIP, donc je vais supprimer toutes les colonnes qui ont trait à ça. GeoIP est une base de données qui permet de géolocaliser des adresses IP. C’est assez précis, allant de la ville jusqu’au quartier.

web4_df = web4_df.drop(columns=['WEB4_geoip'])
web4www_df = web4www_df.drop(columns=['WEB4_www_geoip'])
web6_df = web6_df.drop(columns=['WEB6_geoip'])
web6www_df = web6www_df.drop(columns=['WEB6_www_geoip'])

Sauvegarde des enregistrements DNS dans une base de données SQLite

Enfin, je vais enregistrer chacun des DataFrame produit dans une base de données SQLite. Cette base de données légère prend la forme d’un fichier sur l’ordinateur et est facile à transporter et à réutiliser.

Nous utilisons la méthode to_sql de Pandas pour chaque table. Chacune représente un type d’enregistrement DNS.

Le nom de fichier de la base de données inclus la date du jour.

date_today = pd.to_datetime('today').strftime('%Y-%m-%d')
engine = create_engine(f'sqlite:///dns_results_{date_today}.db')

base_df.to_sql('base_df', engine, if_exists='replace', index=True)
ns_df.to_sql('ns_df', engine, if_exists='replace', index=True)
mail_df.to_sql('mail_df', engine, if_exists='replace', index=True)
web4_df.to_sql('web4_df', engine, if_exists='replace', index=True)
web4www_df.to_sql('web4www_df', engine, if_exists='replace', index=True)
web6_df.to_sql('web6_df', engine, if_exists='replace', index=True)
web6www_df.to_sql('web6www_df', engine, if_exists='replace', index=True)
txt_df.to_sql('txt_df', engine, if_exists='replace', index=True)

Conclusion

Ce script Python permet d’extraire des données d’un fichier JSONL généré par l’outil dns-extract, de les traiter et de les stocker dans une base de données SQLite pour une utilisation ultérieure.

Le code complet est disponible sur mon serveur de code: https://git.jevalide.ca/partage/dns-backup-tool/src/branch/main

Il pourrait avoir évolué depuis l’écriture de cette page.

Consultation Express 🧠

La consultation express, c’est une heure où je me consacre entièrement à résoudre tes problèmes informatiques avec toi.