Gandi Live DNS v5

Thursday, 26 April 2018
|
Écrit par
Grégory Soutadé

La toute récente sortie de Pannous a été l'occasion de créer un nouveau sous-domaine pour héberger le service. Service qui comporte une partie d'authentification, donc obligation de passer par une communication sécurisée (SSL/TLS). Autrefois, la chose était plus aisée, puisque je pouvais générer mes propres certificats et notamment des certificats "wildcards", donc valides pour tous les sous-domaines.

Sauf que je suis passé à Let's Encrypt. Il a donc fallu attendre la sortie de la version 2 du protocole (qui a eu du retard) afin de bénéficier de cette fonctionnalité. Surtout, qu'au passage, le paquet Debian (backport) de certbot a été cassé, ce qui m'a forcé à revenir à une version encore plus ancienne.

Bref, les choses sont maintenants stables et déployées sur les serveurs respectifs. Petit problème néanmoins, la génération d'un certificat wildcard par Let's Encrypt requiert l'ajout d'une entrée DNS (comme challenge). Fatalité, le DNS de Gandi a lui aussi évolué pour passer en version 5. Avec pour principal avantage une mise à jour immédiate des entrées DNS (là où il fallait plusieurs minutes/heures auparavant). Autre nouveauté : l'API Gandi change de format. On oublie l'ancien XML-RPC (ce qui était pratique avec des bindings Python déjà tout faits), pour passer au REST (un peu moins formel).

Mélangeons tout ça pour obtenir un joli cocktail, dont la recette nous est donnée par Sébastien Blaisot qui, pour nous simplifier la vie, a créé des scripts de génération de certificats wildcards via Let's Encrypt. Le code est disponible sur GitHub et supporte le logiciel bind (en local), l'API OVH et la nouvelle API Gandi. Il ne reste plus qu'à cloner le dépôt et lancer la commande magique :

cd certbot-dns-01-authenticators/gandi-livedns
certbot certonly --manual --server https://acme-v02.api.letsencrypt.org/directory\
 --manual-auth-hook $PWD/auth.py --manual-cleanup-hook $PWD/cleanup.py -d '*.soutade.fr'

Et voilà un joli certificat tout frais !

Du coup, je me suis grandement inspiré de son code pour mettre à jour mon script de DNS fallback (serveur de secours via redirection DNS). Avec, en prime, un passage en Python 3 ! À terme, il faudra que j'ajoute le support IPv6.

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import requests
import json
import re

# Config
domain="soutade.fr"
API_KEY = "YOUR-KEY"
livedns_api = "https://dns.api.gandi.net/api/v5/"
dyndns_url = 'http://checkip.dyndns.com/'
headers = {
    'X-Api-Key': API_KEY,
}
A_RECORD_NAME="@" # Record of A type

# Get current IP
current_ip = ''
response = requests.get(dyndns_url)
if response.ok:
    pattern = re.compile('[^:]*(\d+\.\d+\.\d+\.\d+)')
    result = pattern.search(response.text, 0)
    if result == None:
        print("No IP found")
        exit(1) 
    else:
        current_ip = result.group(0).strip()
else:
    print("Connexion error")
    response.raise_for_status()
    exit(1)

print('Your Current IP is %s' % (current_ip))

# Retrieve domains address
response = requests.get(livedns_api + "domains", headers=headers)

if (response.ok):
    domains = response.json()
else:
    response.raise_for_status()
    exit(1)

domain_index = next((index for (index, d) in enumerate(domains) if d["fqdn"] == domain), None)

if domain_index == None:
    # domain not found
    print("The requested domain " + domain + " was not found in this gandi account")
    exit(1)

domain_records_href = domains[domain_index]["domain_records_href"]

# Get recorded IP
response = requests.get(domain_records_href + "/" + A_RECORD_NAME + "/A", headers=headers)

if (response.ok):
    record = response.json()
else:
    print("Failed to look for recorded IP")
    response.raise_for_status()
    exit(1)

print('Old IP : %s' % (record['rrset_values'][0]))

if current_ip != record['rrset_values'][0]:
    record['rrset_values'][0] = current_ip

    # Put updated IP
    response = requests.put(domain_records_href + "/" + A_RECORD_NAME + "/A", headers=headers, json=record)

    if (response.ok):
        print("IP updated")
    else:
        print("something went wrong")
        response.raise_for_status()
        exit(1)

    exit(34) # IP updated return !!

exit(0)

Une version téléchargable est disponible ici.

#
1
De
garfi
, le
26 April 2018 21:04
Bonsoir,

Complètement novice en programmation, mais à l'aise en administration linux; je suis tombé sur votre article à partir de mon freshrss.

J'ai activé au passage livedns5 sur gandi et testé le script python pour mettre à jour une machine qui est derrière un FAI à IP dynamique.

J'ai un message d'erreur :
something went wrong
Traceback (most recent call last):
File "gandi5.py", line 88, in <module>
response.raise_for_status()
File "/usr/lib/python3.6/site-packages/requests/models.py", line 935, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 409 Client Error: Conflict for url: https://dns.api.gandi.net/api/v5/domains/my_domain.fr/records/@/A

J'ai renseigné ma key api à partir de la génération du nouvel espace gandi.
Si vous voulez bien regarder le problème, sinon je bricolerai plus tard.
Merci
Répondre
Auteur :


e-mail* :


Le commentaire :


#
2
De
Greg
, le
27 April 2018 05:04
Bonjour,

Je pense que le problème vient de l'entrée de type A (ipv4). Chez moi, elle porte le nom de "@", ce qui n'est peut être pas votre cas. Ou alors, il y a plusieurs entrées A avec le nom @, à voir selon vos règles.
Répondre
Auteur :


e-mail* :


Le commentaire :


#
3
De
Greg
, le
28 April 2018 07:04
Effectivement, je viens de tomber également sur le problème !
Apparemment, il n'est pas possible de mettre à jour une entrée existante. Je met à jour le script pour d'abord supprimer l'entrée avant de la recréer avec la nouvelle valeur.
Dans mes tests, je forçais la mise à jour, mais avec la même valeur.
Répondre
Auteur :


e-mail* :


Le commentaire :


#
4
De
garfi
, le
29 April 2018 00:04
Ok, merci j'ai moi aussi refait l'entrée existante. merci
Répondre
Auteur :


e-mail* :


Le commentaire :


#
5
De
garfi
, le
16 May 2018 09:05
Un petit message pour donner le lien github d'une personne travaillant chez gandi et ayant codé un petit truc.
https://github.com/rmarchant/gandi-ddns

Celui là fonctionne sans suppression de l'entrée type A, je n'ai regardé le support de l'IPV6 par contre.
Répondre
Auteur :


e-mail* :


Le commentaire :


#
6
De
Greg
, le
19 May 2018 08:05
Merci pour le lien ! Je viens de faire une analyse et le script fait exactement la même chose que le miens (difficile de faire autrement en réalité). Mon soucis originel venait de la mise à jour de l'IP. Il faut utiliser une méthode PUT et non POST pour écraser une entrée.
Répondre
Auteur :


e-mail* :


Le commentaire :


#
7
De
Fabrice
, le
08 August 2019 10:08
Ton article est tres interessant. Pour la partie letsencrypt, DNS, et Cerbot as tu pense a utiliser un service en ligne comme easyssl.live ? Je pense aux problemes que tu mentionne (paquet casse, versions etc...). C'est plus simple de manager tout depuis une interface web et de ne pas avoir a se tracasser pour les versions, mises a jour etc. A part pour des scenarios tres peu classiques, et encore. En tout cas merci de ton article, j'ai beaucoup aime. :)
Répondre
Auteur :


e-mail* :


Le commentaire :


#
8
De
Greg
, le
08 August 2019 13:08
De rien. Cela fait toujours plaisir de pouvoir aider d'autres personnes, même si la contribution semble faible.

Concernant easyssl.live, étant donné que je suis administrateur de mon serveur, il ne me sert à rien. Le plus dur est la première configuration de let's encrypt, ensuite les mises à jour sont transparentes. Et puis, je n'ai pas spécialement envie de donner l'accès à mon serveur depuis un service externe que je ne maîtrise absolument pas.
Répondre
Auteur :


e-mail* :


Le commentaire :


#
9
De
Fabrice
, le
08 August 2019 18:08
Ta contribution n'est pas faible, bien au contraire utile.
Pour le serveur, oui Greg je suis d'accord avec toi. Apres tu n'est pas oblige non plus de donner un access a ton serveur. Un compte FTP avec droit uniquement de mettre un fichier au repertoire well-know/acme_challenge. Ou plus simple encore un CNAME du _acme_challenge.tondomaine.
Répondre
Auteur :


e-mail* :


Le commentaire :


#
10
De
Tof
, le
14 May 2020 14:05
Bonjour,
Merci pour ton travail, j'ai testé ton script afin de mettre à jour mon ip dynamique sur mon DNS chez Gandi.
J'ai en retour :
Your Current IP is *******
Traceback (most recent call last):
File "/usr/local/bin/test.py", line 62, in <module>
print("The requested domain " + certbot_domain + " was not found in this gandi account")
NameError: name 'certbot_domain' is not defined
J'ai un peu de mal à cerner cette histoire de certbot, si j'ai compris c'est pour accepter automatiquement le certificat https de Gandi ?
Merci
Répondre
Auteur :


e-mail* :


Le commentaire :


#
11
De
Greg
, le
14 May 2020 14:05
Bonjour Tof,

Merci d'avoir remonté cette erreur. Il ne s'agit pas de "certbot_domain", mais de "domain" tout court (ton nom de domaine que tu dois mettre à jour au début du script).

Quant à certbot, il s'agit en réalité de l'outils créé par Let's Encrypt pour gérer automatiquement la création et la mise à jour des certificats TLS (https).
Répondre
Auteur :


e-mail* :


Le commentaire :


#
12
De
Tof
, le
14 May 2020 18:05
Super ça marche en effet j'avais pas mis le .fr à la fin du domaine.
Par contre je veux mettre à jour la redirection web http://*.xxxxxxx vers http://**.**.**.** que faut il mettre au niveau de A_RECORD_NAME="@" # Record of A type ? Merci
Répondre
Auteur :


e-mail* :


Le commentaire :


#
13
De
Greg
, le
15 May 2020 08:05
Il ne faut pas toucher à cette valeur. Les entrées de type A c'est pour IPv4, sinon c'est AAAA pour IPv6 (non supporté par le script actuellement).
Répondre
Auteur :


e-mail* :


Le commentaire :


#
14
De
Tof
, le
15 May 2020 08:05
Merci, et donc on ne peut pas mettre à jour automatiquement l'IP dans la redirection web ?
Répondre
Auteur :


e-mail* :


Le commentaire :


#
15
De
Greg
, le
15 May 2020 09:05
Au niveau de mon DNS, j'ai :

...
* 300 IN CNAME soutade.fr.
@ 300 IN A XX.XX.XX.XX
...

Donc un enregistrement pour tous les noms de sous-domaine qui pointe vers mon nom de domaine principal, puis un enregistrement de type A avec "@" (nom de domaine principal) qui pointe vers mon IP. C'est cette entrée qui est mise à jour par le script.
Répondre
Auteur :


e-mail* :


Le commentaire :


#
16
De
Tof
, le
15 May 2020 09:05
Dans enregistrement DNS du site Gandi j'ai ça :
Nom Type TTL Valeur
* CNAME 10800 webredir.vip.gandi.net.

@ A 10800 xx.xx.xx.xx (mon ip internet chez moi)

Quand tu dis "au niveau de mon DNS j'ai" tu parles du DNS surchez ton hébergeur ou sur Gandi ?
Encore merci pour ton aide
Répondre
Auteur :


e-mail* :


Le commentaire :


#
17
De
Greg
, le
15 May 2020 10:05
J'utilise le DNS Gandi et je suis auto hébergé (j'ai un serveur à la maison). C'est aussi la raison pour laquelle mon TTL est de 300 et non de 10800, afin de réagir plus vite à un changement d'IP (qui est relativement stable avec la fibre).
Répondre
Auteur :


e-mail* :


Le commentaire :


Auteur :


e-mail* :


Le commentaire :




* Seulement pour être notifié d'une réponse à cet article
* Only for email notification