Renouvellement automatique d'un certificat Let's encrypt
lun. 11 janvier 2021
Développement d'un script python qui automatise le renouvellement d'un certificat Let's encrypt
- Contexte
- Comment fonctionne Certbot
- Le challenge DNS
- Renouvellement du certificat
- Automatisation
- Automatiser la création d'un enregitrement DNS
- Automatisation de la commande Cerbot
Contexte
J'ai beau mettre un rappel sur mon téléphone, tous les 3 mois c'est la même chose, je néglige de mettre à jour mon certificat.
Du coup, résolution 2021, je fais un script pour automatiser tout ça.
Mon certificat est fourni par let's encrypt et pour la mise à jour j'utilise Certbot.
Comme j'ai plusieurs sous-domaine je veux obtenir un certificat Wildcard (*.pasgrave.net) par la méthode du DNS challenge.
Comment fonctionne Certbot
Le challenge DNS
Le challenge DNS consiste à créer un enregistrement DNS sur votre domaine et d'y stocker une chaîne aléatoire de 43 caractères fournie par Cerbot.
Si la valeur de l'enregistrement est conforme à celle fournie, Certbot est en mesure de valider que vous êtes bien le propriétaire du domaine. Il vous délivre alors un certificat valable pendant 3 mois.
Renouvellement du certificat
Quand on installe Cerbot on dispose de la commande certbot-auto
à laquelle on passe en paramètre:
- le domaine concerné,
*.pasgrave.net
pour un certificat valable pour tous les sous-domaine de pasgrave.net. - le type de challenge utilisé.
La commande complète est la suivante:
sudo /usr/local/bin/certbot-auto -d *.pasgrave.net \
--manual --preferred-challenges \
dns certonly
La commmande certbot-auto
ouvre un shell intéractif qui vous demande de créer l'enregistrement et de valider une fois la tâche effectuée.
Voici par exemple le type de message envoyé dans la console:
Please deploy a DNS TXT record under the name
_acme-challenge.pasgrave.net with the following value:
x9maSaUJ6HEmEjuKGzS7VMFTLuC0aUskyQw3myLMuVE
Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
Une fois l'enregistrement DNS en place vous appuyez sur Entrée. Si la vérification aboutie Cerbot installe le nouveau certificat.
Automatisation
Automatiser la création d'un enregitrement DNS
Gandi fourni une API http de gestion d'un domaine. Parmis les requètes disponibles il est possible de créer un enregistrement de la façon suivante:
curl -H 'Content-Type: application/json' \
-H 'Authorization: Apikey xxxxxxxxxxxxxx' \
-X PUT -d {"rrset_values" : ["123"]} \
https://api.gandi.net/v5/livedns/domains/pasgrave.net/records/mon_record/TXT
J'ai automatisé de cette partie avec le code suivant:
import requests
my_gandi_key=open("gandi_key", "r").read()
api_url="https://api.gandi.net/v5/livedns/domains/pasgrave.net/records/{}/TXT"
def put_txt_record(name, value):
url=api_url.format(name)
print(url)
data={"rrset_values":[value]}
print(data)
headers = {'Content-Type': 'application/json', 'Authorization': my_gandi_key}
print(headers)
return requests.put(url, json=data, headers=headers)
Automatisation de la commande Cerbot
On peut interagir avec la commande certbot-auto
en utilisant Popen
.
Il faut noter que le paramètre universal_newlines=True
est nécessaire dans notre cas.
Lors de l'initialisation de Popen
le programme démarre et on entre dans
la boucle while out
. A chaque itération on lit le contenu de la ligne et
on retourne la réponse appropriée.
Par exemple, quand cerbot retourme une ligne correspondant à la regex ^([^:\s]{43})\n$
nous sommes dans le cas du challenge DNS. On récupère la valeur de l'enregistrement
(certkey = match_challenge.group(1)
) et on l'envoie à Gandi avec la fonction
gandi_http.put_txt_record
.
Il ne reste plus alors qu'à appuyer sur entrée (p.stdin.write('\n')
) pour laisser
Cerbot valider le challenge et nous fournir un nouveau certificat.
Voici le code complet du programme également disponible sur framagit.
from subprocess import Popen, PIPE
import re
import gandi_http
SOUS_DOMAIN="dev"
DOMAIN="pasgrave.net"
DNS_RECORD="_acme-challenge.%s" % SOUS_DOMAIN
MODE_TEST=True
dry_run = " --dry-run" if MODE_TEST else ""
command="/usr/local/bin/certbot-auto -d {} --manual --preferred-challenges dns certonly %s" % dry_run
command=command.format("{}.{}".format(SOUS_DOMAIN, DOMAIN))
p = Popen(command, shell=True, stdin=PIPE, stdout=PIPE, bufsize=0, universal_newlines=True)
challenge_pattern=re.compile("^([^:\s]{43})\n$")
out = p.stdout.readline()
while out:
line = out
match_challenge = challenge_pattern.match(line)
if "Are you OK with your IP being logged" in line:
p.stdin.write('Y\n')
out = p.stdout.readline()
continue
elif match_challenge:
certkey = match_challenge.group(1)
response = gandi_http.put_txt_record(DNS_RECORD, certkey)
if response.ok:
print("Successful update DNS record %s with value %s" % (DNS_RECORD, certkey))
else:
print("Fail to save DNS record %s with value %s" % (DNS_RECORD, certkey))
p.stdin.write('\n')
out = p.stdout.readline()
continue
else:
print("++%s" % line.rstrip("\n"))
out = p.stdout.readline()
continue
p.wait()