Découvrez le DNS as code avec OctoDNS

Spécialité(s)


Résumé

Chercher à mettre en place des systèmes robustes implique obligatoirement d’anticiper des pannes. N’avez-vous jamais rêvé d’avoir la possibilité de créer des enregistrements DNS dans un environnement multitenant, sans problèmes et sans créer d’incidents de production ? Le faire d’une manière unique et commune, peu importe le fournisseur souhaité, de pouvoir migrer une zone complète en une commande, d’un fournisseur à un autre. OctoDNS répondra parfaitement à ces besoins et nous verrons dans cet article comment.


Body

1. Présentation de l’outil

OctoDNS est un outil écrit en Python 3, rendu à la communauté open source en 2017 par la société GitHub qui propose une plateforme Git du même nom. Cet outil n’est utilisable qu’en ligne de commandes.

Le paquet OctoDNS embarque plusieurs outils qui sont :

  • octodns-compare : permet de comparer les enregistrements de deux zones avant migration ;
  • octodns-dump : permet de récupérer les enregistrements d’un fournisseur et de les transformer en fichier YAML au format OctoDNS ;
  • octodns-report : permet de vérifier l’état des enregistrements créés et de reporter les informations qui divergent (entre la ou les source(s) et l’état de la zone après modification) ;
  • octodns-sync : permet de vérifier les modifications qui vont être à apporter et appliquer les modifications (via l’option --doit) ;
  • octodns-validate : permet de valider la configuration et les fichiers de zone, mais également de vérifier que vous n’avez pas écrit de bêtise (oublier un « . » à la fin d’un enregistrement CNAME par exemple) ;
  • octodns-versions : permet de connaître la version d’OctoDNS et les modules de type provider/processor/plan_output que vous utilisez.

Tout comme beaucoup d’outils dans l’univers DevOps, OctoDNS utilise le format YAML (Yet Another Markup Language) comme Ansible [ANSIBLE] ou encore Kubernetes [KUBERNETES] pour la configuration ainsi que les enregistrements DNS.

OctoDNS se veut un outil dit avec état, ainsi, dans la même trempe que Terraform [TERRAFORM], celui-ci viendra vérifier par le biais de l’API du fournisseur ou de fichiers (comme pour bind9, /etc/hosts), les modifications à apporter (création, modification ou suppression).

Il est également important de souligner que vous pouvez l’utiliser avec à peu près n’importe quel fournisseur, que ça soit sur du Cloud (Route53 d’Amazon Web Services, NS1, CloudDNS de Google, Cloudflare) ou de l’on-premise (PowerDNS, Bind9, etc.).

Il reste qu’OctoDNS n’est aucunement là pour gérer la configuration même des serveurs DNS (configuration des ports d’écoute, des liens avec les bases de données). OctoDNS permet de manipuler les enregistrements.

2. Installer OctoDNS

Comme vous avez pu le lire plus haut, l’outil est écrit en Python 3, celui-ci s’installe par le biais du gestionnaire de paquets Python nommé pip. Pour ne pas interférer avec les dépendances Python locales de votre machine, nous passerons par un environnement virtuel.

Pour ce faire :

apt update
apt install python3 python3-pip python3-venv
python3 -m venv venv
cd venv && source bin/activate
pip install octodns octodns-route53
octodns-sync --version

Vous voilà fin prêt à utiliser OctoDNS.

3. Création du projet

3.1 Création de la structure

OctoDNS est complètement ouvert à la structure de fichiers que vous souhaitez. Pour la démonstration, nous partirons sur une structure simple. Cette structure est la suivante :

.
├── config.yaml
└── zones
   └── mazone.org.yaml

3.2 Configuration de la zone

Une fois que la structure est créée, il faut commencer par déclarer dans le fichier config.yaml les sources et destinations. Ces sources et destinations sont appelées providers, vous pouvez utiliser plusieurs types de sources et destinations, vous pouvez également les cumuler.

Ainsi, vous pouvez avoir en source, des fichiers statiques et un provider NetBox par exemple, pour récupérer la liste des enregistrements à créer.

Dans notre structure ci-dessus, nous partions d’un fichier de zone statique au format YAML, ainsi, nous utiliserons la classe Python suivante : octodns.provider.yaml.YamlProvider et la classe octodns.provider.route53.Route53Provider, c’est par le biais du mot class que nous indiquerons à OctoDNS, quelle classe Python utiliser.

En général, toutes les valeurs sensibles ou qui peuvent être fréquemment modifiées doivent provenir de variables d’environnement (exemple : secret_access_key: env/AWS_SECRET_ACCESS_KEY). Ce n’est pas obligatoire, mais cela reste plus que recommandé.

Ainsi, vous trouverez en Figure 1 une configuration simple d’OctoDNS.

figure1-s 4

Figure 1 : Représentation graphique du processus d’application d’enregistrements.
---
providers:
  config:
    class: octodns.provider.yaml.YamlProvider
    directory: ./zones
    default_ttl: 300
    enforce_order: false
  route53:
    class: octodns.provider.route53.Route53Provider
    access_key_id: env/AWS_ACCESS_KEY_ID
    secret_access_key: env/AWS_SECRET_ACCESS_KEY
zones:
  mazone.org.:
    sources:
      - config
    targets:
- route53
À noter qu’il est important d’aller consulter la documentation officielle de chaque provider, les providers ne prenant pas tous les mêmes paramètres. Cette documentation est le talon d’Achille d’OctoDNS puisqu’elle est en majorité stockée dans des fichiers README du dépôt Git du provider donné ou dans la section docstring de la classe du code Python même.

3.3 Création des enregistrements

Suivant l’architecture réalisée en 3.1, nous allons pouvoir ajouter les enregistrements. Les enregistrements '' sont ceux de la racine de votre zone. C’est ici que vous pouvez déclarer vos records TXT, SOA par exemple.

Le format d’un enregistrement est le suivant :

nom:
  ttl: 60
  type: A)
  value: 1.1.1.1
À noter que vous pouvez retrouver la liste des types d’enregistrements supportés officiellement ici [RECORDS].

Ainsi, pour l’exemple, nous créerons les enregistrements suivants :

Nom

TTL

Type

Valeur(s)

root (@)

60

A

10.0.0.1, 10.0.0.2 (round-robin)

ns1

300

A

10.0.0.1

Ce qui donne le fichier de zone OctoDNS suivant :

---
'':
  ttl: 60
  type: À
  values:
    - 10.0.0.1
    - 10.0.0.2
ns1:
  ttl: 300
  type: À
  value: 10.0.0.1
 

Comme vous le constatez, le format est à la portée de tout le monde. Ce format est lui unique à tous les providers OctoDNS.

4. En action

4.1 Vérifier nos enregistrements

Avant d’appliquer nos modifications, nous préférons vérifier que notre fichier de zone ne comporte pas d’énormités. Via la commande octodns-validate, nous allons donc pouvoir vérifier que nous n’avons pas fait d’erreurs.

export AWS_ACCESS_KEY_ID=’my-access-key-id’
export AWS_SECRET_ACCESS_KEY=’my-secret-access-key’
octodns-validate –config-file=./config.yaml

N’oubliez pas de mettre un espace devant vos exports pour éviter d’avoir vos secrets dans votre historique bash.

Avant d’appliquer nos modifications, nous préférons vérifier que notre fichier de zone ne comporte pas d’énormités. Via la commande octodns-validate, nous allons donc pouvoir vérifier que nous n’avons pas fait d’erreurs.

En cas d’erreur, la commande vous retournera l’endroit précis où corriger cette erreur. L’étape de validation est aussi appelée étape de lint.

4.2 Vérifier des modifications

Les enregistrements ne possèdent pas d’erreur de syntaxe, nous allons pouvoir, à présent, vérifier les modifications qui vont être effectuées. Comme il était dit dans la présentation d’OctoDNS, OctoDNS est un outil avec état autrement appelé : stateful, qui cherchera donc à vérifier l’état de la configuration distante avant de la modifier pour n’appliquer que les modifications nécessaires (idempotence).

Cette étape peut être comparée au plan de Terraform, seules les modifications seront retournées, mais ne seront pas appliquées. Cette étape est idéale lors de l’intégration dans un dépôt Git où l’on viendrait faire des modifications à la zone par le biais d’une Pull Request ou Merge Request, de visualiser les modifications qui vont être appliquées.

Ainsi cette commande vous permettra d’éviter les suppressions ou modifications non souhaitées.

octodns-sync --config-file=./config.yaml
...
********************************************************************************
* mazone.org.
********************************************************************************
* route53 (Route53Provider)
*   Create <ARecord A 60, mazone.org., [u'10.0.0.1', '10.0.0.2']>
*   Create <ARecord A 60, ns1.mazone.org., [u'10.0.0.1']>
*   Summary: Creates=2, Updates=0, Deletes=0, Existing Records=0
********************************************************************************
...

4.3 Appliquer des modifications

Pour appliquer les modifications que nous avons précédemment contrôlées, nous pouvons utiliser l’option --doit :

octodns-sync –config-file=./config.yaml --doit
...

Il ne vous restera plus qu’à vérifier avec dig que les enregistrements ont bien été créés.

4.4 Déployer sur 2 destinations différentes

Pour des raisons de sécurité, nous souhaitons parfois, sur des zones à fort trafic, déployer notre zone sur plusieurs providers différents, par exemple Route53 d’un côté et Cloud DNS de l’autre. Ainsi, il conviendra de modifier simplement le fichier de configuration de la zone et d’y spécifier la configuration du nouveau provider et de le rajouter dans la section targets prévue à cet effet.

figure2-s 4

Fig. 2 : Représentation graphique du processus d’application d’enregistrements.

Ainsi, nous retrouverons dans notre configuration :

---
providers:
  config:
    class: octodns.provider.yaml.YamlProvider
    directory: ./zones
    default_ttl: 300
    enforce_order: false
  route53:
    class: octodns.provider.route53.Route53Provider
    access_key_id: env/AWS_ACCESS_KEY_ID
    secret_access_key: env/AWS_SECRET_ACCESS_KEY
  clouddns:
    class: octodns_googlecloud.GoogleCloudProvider
    project: monprojet
    credentials_file: ~/google_cloud_credentials_file.json
 
zones:
  mazone.org.:
    sources:
      - config
    targets:
      - route53
      - clouddns

Pour installer le provider Cloud DNS, il suffit d’utiliser la commande : pip install octodns-googlecloud. À l’instar du provider Route53, la documentation officielle du provider Cloud DNS préconise l’utilisation du fichier .json contenant les identifiants.

Ainsi si nous vérifions ce qui va être appliqué, nous nous rendons compte qu’il ne cherchera pas à réappliquer la partie Route53, mais qu’il viendra peupler le provider Cloud DNS.

octodns-sync --config-file=./config.yaml
...
********************************************************************************
* mazone.org.
********************************************************************************
* route53 (Route53Provider)
*   Summary: Creates=0, Updates=0, Deletes=0, Existing Records=2
* clouddns (Route53Provider)
*   Create <ARecord A 60, mazone.org., [u'10.0.0.1', '10.0.0.2']>
*   Create <ARecord A 60, ns1.mazone.org., [u'10.0.0.1']>
*   Summary: Creates=2, Updates=0, Deletes=0, Existing Records=0
********************************************************************************
...
octodns-sync –config-file=./config.yaml --doit
...

Il est intéressant de préciser que vous pouvez, dans un contexte de plusieurs cibles, spécifier une cible particulière en rajoutant l’option --target route53 par exemple.

4.5 Récupérer vos zones existantes dans OctoDNS

Si vous prenez la décision de vouloir gérer toutes vos zones avec OctoDNS, c’est tout à fait faisable sans trop d’effort, en effet, OctoDNS embarque la commande octodns-dump qui permet de récupérer la totalité des enregistrements d’une zone donnée et de les transposer au format OctoDNS (YAML).

Il suffira de déclarer la configuration du provider comme suit :

---
providers:
  route53:
    class: octodns.provider.route53.Route53Provider
    access_key_id: env/AWS_ACCESS_KEY_ID
    secret_access_key: env/AWS_SECRET_ACCESS_KEY

Ensuite, vous pourrez lancer la commande suivante pour récupérer le contenu de la zone distante et la copier dans le fichier de zone présent dans tmp/ nommé dump.yaml.

octodns-dump --config-file=dump.yaml --output-dir=tmp/ foobar.io. route53
2022-11-11T13:33:34 INFO Manager __init__: config_file=tmp/dump.yaml
2022-11-11T13:33:34 INFO Manager dump: zone=foobar.io., sources=('route53',)
2022-11-11T13:33:36 INFO Route53Provider[route53] populate:   found 64 records
2022-11-11T13:33:36 INFO YamlProvider[dump] plan: desired=foobar.io.
2022-11-11T13:33:36 INFO YamlProvider[dump] plan:   Creates=184, Updates=0, Deletes=0, Existing Records=0
2022-11-11T13:33:36 INFO YamlProvider[dump] apply: making changes

À noter que dès qu’OctoDNS rencontre un problème sur le formatage des enregistrements déjà présents dans la zone distante, il s’arrêtera, vous pouvez passer outre en utiliser l’option --lenient. Cette option permet d’autoriser les configurations et valeurs non conformes lorsque c’est possible. Par exemple, s’il manque un « . » à la fin d’un enregistrement de type CNAME. Il est d’ailleurs possible d’activer ce mode au niveau d’une zone (en spécifiant lenient: true dans la configuration de la zone même) ou encore d’un enregistrement même, vous trouverez plus d’informations sur ce sujet ici [LENIENCE].

4.6 Migrer une zone d’un fournisseur à un autre

Si vous souhaitez par exemple entreprendre une migration d’un fournisseur  « on-premise » vers du « cloud », OctoDNS permet de le faire aisément. En effet, il s’agira de modifier la source. Jusque là nous utilisions le provider YAML.

Pour l’exemple, nous allons migrer la zone example.org (Figure 3).

figure3-s 4

Fig. 3 : Représentation graphique du processus d’application d’enregistrements.

Et modifier la configuration de la zone comme suit :

---
providers:
  powerdns:
    class: octodns_powerdns.PowerDnsProvider
    host: ns1.example.org
    port: 8081
    api_key: env/POWERDNS_API_KEY
    nameserver_ttl: 300
 
  route53:
    class: octodns.provider.route53.Route53Provider
    access_key_id: env/AWS_ACCESS_KEY_ID
    secret_access_key: env/AWS_SECRET_ACCESS_KEY
 
zones:
  example.org.:
    sources:
      - powerdns
    targets:
      - route53

Pour installer le provider PowerDNS, vous devez utiliser la commande : pip install octodns-powerdns.

Maintenant que la configuration et les sources/targets sont correctement définies, il ne reste plus qu’à valider et appliquer les modifications avec la commande octodns-sync comme cela a été fait plus tôt, il ne faudra pas oublier d’exporter dans notre cas la clé d’API PowerDNS pour la connexion.

4.7 Utiliser une source dynamique

Pour gérer les enregistrements de nos serveurs, le fichier statique montre rapidement ses limites.

Ainsi, si vous avez une CMDB comme NetBox, vous pouvez utiliser le provider associé pour créer les entrées DNS associées aux entrées de type « device » de ce dernier. Par conséquent, pour l’exemple, nous avons une instance NetBox dans laquelle nous avons 2 machines enregistrées, à savoir :

Nom

Adresse IP (v4)

web-server-1

10.0.10.100

web-server-2

10.0.10.101

Dans les informations de nos machines citées ci-dessus, dans NetBox, nous devons rajouter le custom fields nommé dns_name avec la valeur des enregistrements souhaités (par exemple : web-server-1.example.org).

figure4-s 2

Figure 4 : Représentation graphique du processus d’application d’enregistrements.

Côté OctoDNS, il faudra ajouter le provider NetBox pour OctoDNS avec la commande :

pip install octodns-netbox

Ensuite, dans la configuration, nous devons déclarer ce nouveau provider.

providers:
  netbox:
    class: octodns_netbox.NetboxSource
    url: https://netbox.example.org
    ttl: 60
    field_name: dns_name
    multivalue_ptr: true
 
  powerdns:
    class: octodns_powerdns.PowerDnsProvider
    host: ns1.example.org
    port: 8081
    api_key: env/POWERDNS_API_KEY
    nameserver_ttl: 300
 
zones:
  example.org.:
    sources:
      - netbox
    targets:
      - powerdns
 

Les enregistrements créés via le provider NetBox sont obligatoirement des records de type A (IPv4) ou AAAA (IPv6).

Il est important de souligner qu’il est possible de créer des enregistrements de type PTR via :

  0/26.2.0.192.in-addr.arpa.:
    sources:
      - netbox # Ajout d’un enregistrement de type PTR (qui correspond à l’enregistrement de type A)
    targets:
      - powerdns
 
  0.8.b.d.0.1.0.0.2.ip6.arpa:
    sources:
      - netbox # Ajout d’un enregistrement de type PTR (qui correspond à l’enregistrement de type AAAA)
    targets:
      - powerdns

Pour terminer, comme pour les autres actions, il ne restera qu’à valider et appliquer la configuration.

4.8 Découper sa zone dans plusieurs fichiers statiques

OctoDNS embarque par défaut le provider « YAML » qui permet de stocker ses entrées DNS au format YAML. Ce provider possède un gros défaut, celui d’imposer un nom de fichier. Ce nom de fichier est sous le format suivant :

mazone.yaml

Le gros problème avec ce nom imposé est que l’on ne peut pas découper sa zone en plusieurs sous-fichiers dans le même dossier. En effet, il n’est pas possible d’avoir 2 fichiers avec un même nom dans un répertoire.

Pour pallier à cela, nous allons pouvoir utiliser le provider yamlimproved [YAMLIMPROVED]. Vous devez donc utiliser la commande suivante pour l’installer :

pip install octodns-yamlimproved

Une fois installé, nous allons pouvoir découper notre zone en plusieurs sous-fichiers.

Pour l’exemple, nous découperons la zone de sorte que nous ayons dans un premier fichier les enregistrements liés aux serveurs DNS, et dans un second les enregistrements courants. Nous devons donc pour commencer déclarer dans notre configuration, les 2 fichiers souhaités :

providers:
  config_root:
    class: octodns_yamlimproved.YamlProvider
    filename : root
    directory: ./zones
    default_ttl: 300
    enforce_order: false
  config_other:
    class: octodns_yamlimproved.YamlProvider
    filename : other
    directory: ./zones
    default_ttl: 300
    enforce_order: false
zones:
  example.org.:
    sources:
      - config_root
      - config_other
    targets:
      - powerdns

Il n’est pas nécessaire de spécifier l’extension de fichier « .yaml » pour le nom de fichier dans le paramètre filename.

Ainsi grâce à ce provider, vous pourrez découper votre zone en plusieurs sous-fichiers.

4.9 Augmentez votre qualité de code

Pour aller plus loin, vous pouvez utiliser l’outil yamllint [YAMLLINT] pour vérifier la qualité de votre code YAML.

./zones/example.org/example.org.yaml
  6:3       error    wrong indentation: expected 4 but found 2 (indentation)

Ainsi, vous augmenterez naturellement la qualité de code OctoDNS produite. À noter que cet outil vient en complément de la commande octodns-validate.

5. Testez vos enregistrements

La phase souvent oubliée, mais plus que nécessaire reste l’étape de tests. Cette étape est primordiale pour s’assurer que les enregistrements créés le sont bien et qu’ils sont bien fonctionnels.

OctoDNS répond à ce besoin par le biais de la commande octodns-report. Son fonctionnement est simple, une fois que vous avez appliqué vos enregistrements via octodns-sync et l’option --doit, la commande octodns-report va lire les informations déclarées dans la configuration des fournisseurs sources et s’assurer que chaque enregistrement possède bien le contenu déclaré.

Cet outil permet d’éviter de devoir utiliser manuellement la commande nslookup ou dig pour vérifier l’état de l’enregistrement.

Par exemple :

octodns-report --config-file=./config.yaml --zone example.org. --source config 1.1.1.1
2022-12-01T14:22:34 [281473753326624] INFO Manager __init__: config_file=./config.yaml (octoDNS 0.9.21)
2022-12-01T14:22:34 [281473753326624] INFO Manager _config_executor: max_workers=2
2022-12-01T14:22:34 [281473753326624] INFO Manager _config_include_meta: include_meta=False
2022-12-01T14:22:34 [281473753326624] INFO Manager __init__: global_processors=[]
2022-12-01T14:22:34 [281473753326624] INFO Manager __init__: provider=config (octodns.provider.yaml 0.9.21)
2022-12-01T14:22:34 [281473753326624] INFO Manager __init__: provider=route53 (octodns_route53 0.0.5)
2022-12-01T14:22:34 [281473753326624] INFO YamlProvider[config] populate:   found 1 records, exists=False
name,type,ttl,1.1.1.1,consistent
2022-12-01T14:22:34 [281473753326624] INFO report server=1.1.1.1
ns1.example.org.,A,60,13.248.158.159,True

À noter qu’il faut préciser en toute fin le serveur DNS résolveur pour que celui-ci puisse être interrogé et fournir une réponse sur l’état des enregistrements.

Conclusion

Vous l’aurez compris, depuis l’arrivée en 2017 d’OctoDNS, il n’aura jamais été aussi simple de gérer ses enregistrements DNS, peu importe l’environnement ou la solution technique qui se trouve derrière.

Il n’aura jamais été aussi simple de migrer de l’« on-premise » vers une solution cloud et vice-versa.

En bref, OctoDNS est un outil simple, clair, qui n’attend que d’être utilisé.

Pour aller plus loin, et parce que ce n’était pas le but de cet article, vous pouvez intégrer OctoDNS dans un environnement de CI/CD combiné à un dépôt Git, afin d’obtenir une notion de « dynamisme » dans la vérification et l’application, mais également pour effectuer des retours en arrière rapides en cas de dysfonctionnements ou d’erreurs. Cela vous permettra également d’avoir un souci clair, précis et daté des modifications apportées à vos zones DNS.

Références

[ANSIBLE] Lien du site officiel d’Ansible : https://www.ansible.com/

[KUBERNETES] Lien du site officiel de Kubernetes : https://kubernetes.io/fr/

[TERRAFORM] Lien du site officiel de Terraform : https://www.terraform.io/

[RECORDS] Types d’enregistrement supportés : https://github.com/octodns/octodns/blob/main/docs/records.md#record-types

[LENIENCE] Documentation OctoDNS section « Lenience » : https://github.com/octodns/octodns/blob/main/docs/records.md#lenience

[YAMLIMPROVED] Lien du dépôt Git d’octodns-yamlimproved :
https://github.com/junnhy5/octodns-yamlimproved

[YAMLLINT] Lien du dépôt Git de yamllint : https://github.com/adrienverge/yamllint



Article rédigé par

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous