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 " + certbot_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 :


Auteur :


e-mail* :


Le commentaire :




* Seulement pour être notifié d'une réponse à cet article