Description
En 2018, je me suis posé la question de la gestion des enregistrements TLSA, selon le protocole DANE, lié au protocole de sécurité DNSSEC, dans mes zones DNS. (cf: DNS: Générer un enregistrement TLSA) - je vous invite à le lire…
Certains vont utiliser l’outil knot, fourni en tant que paquet sous OpenBSD, car ils trouvent complexe à gérer. Mais nous verrons avec un peu d’astuces comment gérer cela de manière automatisé en shell, sous OpenBSD.
Pour rappel, mon service DNS fonctionne depuis plus de 4 ans, sous OpenBSD grâce au logiciel natif nsd. La gestion des enregistrements DNSSEC se fait grâce aux outils ldns à installer en tant que paquet tiers. J’utilise dans les faits l’outil ldnscript qui permet de gérer la création des clés nécessaires puis s’occupe de gérer les enregistrements DNSSEC adéquats.
⇒ En Juin 2022, j’ai décidé de basculer du chiffrement RSA par l’utilisation de l’algorithme à courbes elliptiques nommés ECDSA.
Avant d’aller plus loin en ce sens, passons à l’installation des prérequis nécessaires :
Installation
ldnscript
Pour me rappeler comment faire, je me suis fait le petit mémo suivant :
-
installer les binaires nécessaires :
$ doas pkg_add ldns-utils git
-
télécharger et installer ldnscripts
$ cd /usr/local/src/
$ doas mkdir ldnscripts
$ doas chown $USER ldnscripts
$ git clone https://framagit.org/22decembre/ldnscripts.git
$ cd ldnscripts
$ doas make install
- configurer le fichier /etc/ns/ldnscript.conf
; SHA256 est largement suffisant et sécuritaire
ALG=ECDSAP256SHA256
NSEC3_ALG=SHA-256
-
initialisation du domaine
$ doas ldnscript init domain.tld
-
nécessité de créer un lien symbolique /usr/bin/dig vers /usr/sbin/dig :
$ doas ln -sf /usr/bin/dig /usr/sbin/dig
(sans ce dernier point, l’outilldnscript
ne pourra trouver le binairedig
et donc refusera de fonctionner en se mettant en erreur).
Configuration
/etc/monthly.local
Tous les mois, je crée le rollover nécessaire des clés en intégrant dans
le script /etc/monthly.local
, le code shell suivant :
### ldnscript
printf '%s\n' "=> ldnscript rollover"
/usr/local/sbin/ldnscript rollover all
Let’s Encrypt
Dans la foulée, du rollover des clés DNSSEC, mon script interroge les services Let’s Encrypt pour savoir s’il faut un renouvellement des certificats pour les noms de domaines que j’utilise.
Normalement, on utiliserait le client acme
natif sous OpenBSD, mais ayant
eu certains déboires, j’ai décidé de basculer sur l’usage de certbot
.
Une simple écriture shell suffit pour le renouvellement :
/usr/local/bin/certbot renew --pre-hook "rcctl stop nginx" --post-hook "rcctl start nginx"
(en effet, j’utilise aussi le serveur nginx, en lieu et place du serveur web natif httpd - mais il suffit de remplacer le nom du service, si jamais c’est votre cas).
Bien-sûr, cette partie n’entre pas directement en ligne de compte de la gestion DNS. Néanmoins, c’est important à prendre en compte car lors du renouvellement de certificat, il est nécessaire de regénérer les enregistrements TLSA - nous verrons ce point plus tard.
Exemple zone DNS
Voici pour l’exemple une zone DNS minimaliste, gérée par le serveur ns :
$TTL 1H
$ORIGIN domain.tld.
@ IN SOA domain.tld. dns.domain.tld. (
2022090101 ;
3H ; refresh
1H ; retry
2W ; expire
1H ; negative
)
@ IN NS ns1.domain.tld.
@ IN NS ns2.domain.tld.
@ IN A 46.23.90.29
IN AAAA 2a03:6000:6e65:619::29
; enregistrement CAA
@ IN CAA 0 iodef "mailto:mail@domain.tld"
@ IN CAA 0 issue "letsencrypt.org"
@ IN CAA 0 issuewild "letsencrypt.org"
www IN A 46.23.90.29
IN AAAA 2a03:6000:6e65:619::29
; TLSA
_443._tcp.domain.tld. IN TLSA 3 1 2 5c8fdd68178ce4cd8d88bd90b82a96df41674d555340b88283c24a0b3416aa375144cd6c16a58160ba3b168e59f5003bff656ce67cb24931b462fe4910bd62f5
shell
Générer un Enregistrement TLSA
En shell, générer un enregistrement TLSA n’est pas compliqué :
openssl x509 -noout -pubkey -in "${cert}" | openssl "${command}" -pubin -outform der 2>/dev/null | "${algo}" | tr "a-z" "A-Z"
Explications :
⇒ La commande ci-dessus nous permet de générer une variable intermédiaire
nommée tlsa_cert_associated
où :
- la variable
$cert
est le nom du chemin absolu du certificat TLS serveur, dans le système de fichier du serveur, lié au domaine cible. - la variable
$command
est le nom de la commande utilisée par OpenSSL, soitrsa
ouec
(réciproquement pour les enregistrements selon le chiffrement RSA ou ECDSA). - et, où la variable
$algo
est le nom de l’utilitairesha256
, ousha512
, au choix personnel et restituera un condensé de message lié à l’algorithme choisi.
⇒ l’écriture relative à l’enregistrement TLSA est aussi simple :
tlsa_record="_${tls_port}._${tls_proto}.${domain}. IN TLSA ${tlsa_usage} ${tlsa_selector} ${tlsa_method} ${tlsa_cert_associated}"
où :
${tls_port}
est le numéro de port du serveur web utilisé, ici 443${tls_proto}
est le nom du protocole utilisé, ici tls${domain}
est le nom de domaine cible${tlsa_usage}
est le chiffre correspondant à la contrainte utilisée, ici 3, correspondant à la contrainte DANE-EE, et recommandée par Let’s Encrypt.${tlsa_selector}
est le chiffre correspondant au nom de sélecteur utilisé, ici 1, correspondant au sélecteur SPKI, là aussi recommandé par Let’s Encrypt.${tlsa_method}
étant la méthode utilisant l’algorithme de chiffrement choisi ; ici SHA256, qui offre un niveau actuel de chiffrement dit sécurisé et tout autant recommandé par Let’s Encrypt.- et pour finir la variable
tlsa_cert_associated
précédemment créée.
Vérifier un Enregistrement TLSA
Vérifier un enregistrement TLSA est un poil plus compliqué ; il faut :
- 1/ interroger le serveur DNS pour connaître l’enregistrement TLSA actuel
par le biais de l’outil
dig
, pour l’exemple. - 2/ le comparer avec la sortie fournie par l’outil
openssl
qui va interroger le certificat installé sur le serveur web utilisé, lié au nom de domaine en question.
- interroger le serveur DNS se fait ainsi :
tlsa="$(dig TLSA _443._tcp."${domain}" +short)"
d_tlsa="$(echo "${tlsa}" | awk '{ for(i=4;i<=NF;++i) printf "%s", tolower($i); print "" }')"
- utiliser openssl pour interroger le certificat TLS utilisé sur le serveur web :
tlsa="$(echo | openssl s_client -servername "${domain}" -showcerts -connect "${domain}":443 2>/dev/null | openssl x509 -noout -pubkey | openssl pkey -outform der -pubin 2>/dev/null | openssl dgst -"${algo}" 2>/dev/null )"
o_tlsa="$(echo "${tlsa}" | awk -F'=' '{ print $2 }' | tr -d ' ')"
Pour finir, il suffit vraiment de comparer les deux variables shell
d_tlsa
et o_tlsa
pour savoir si elles correspondent, ce qui en temps
normal doit être le cas. Sauf en cas, de renouvellement de certificat
TLS, qui nécessitera donc de renouveller l’enregistrement TLSA, dans la
zone du nom de domaine en question, sur le serveur DNS.
Pour rappel, si cela n’est pas fait, une interrogation du serveur DNS sur le protocole DNSSEC générera une erreur puisque l’enregistrement TLSA ne correspondra pas un certificat web fraîchement utilisé, ou renouvellé, ce qui va entraîner cette conséquence : l’accès au serveur, lié au nom de domaine cible, sera impossible.
Il faudra donc générer un nouvel enregistrement TLSA correspondant au renouvellement du certificat TLS, puis régénerer la signature des enregistrements DNSSEC liés à la zone DNS du domaine cible.
Scripts Shell
Pour info, les scripts shell tlsa.sh, dns.conf, dns.ksh sont écrits dans un répertoire nommé dns-tools dans le répertoire personnel. À vous de voir où vous désirez les mettre, et modifier le script mensuel.
monthly.local
Voici la teneur de mon script shell monthly.local :
#!/bin/sh
### ldnscript
printf '%s\n' "=> ldnscript rollover"
/usr/local/sbin/ldnscript rollover all
### renew ssl by certbot
printf '%s\n' "=> renew letsencrypt certs"
/usr/local/bin/certbot renew --pre-hook "rcctl stop nginx" --post-hook "rcctl start nginx"
### check tlsa records for domain; only for tcp:443
for domain in "sub.domain.tld" "domain.tld" "www.domain.tld" "sub.domain2.tld" "domain2.tld" "www.domain2.tld"; do
printf '%s\n' "=> Test TLSA for ${domain}"
/home/-your-user-/dns-tools/tlsa.sh "${domain}"
done
(…)
tlsa.sh
Voici mon script shell nommé tlsa.sh :
#!/bin/sh
set -e
#set -x
########################################################################
#
# Author: Stéphane HUC
# mail: devs@stephane-huc.net
# gpg:fingerprint: CE2C CF7C AB68 0329 0D20 5F49 6135 D440 4D44 BD58
#
# License: BSD Simplified
#
# Github:
#
# Date: 2022/07/01 06:45
#
########################################################################
#
# Purpose: tool to test TLSA record
# - for the geek: DANE-TLSA...
#
# Needed tools: dig, openssl
#
# OS: Tested on OpenBSD, Devuan
#
########################################################################
###
##
# see: https://www.bortzmeyer.org/monitor-dane.html
##
###
########################################################################
ROOT="$(dirname "$(readlink -f -- "$0")")"
. "${ROOT}/dns.conf"
dir_admin="/home/-your-user-/dns-tools"
domain="$1"
### DO NOT TOUCH!
d_tlsa='' # TLSA record by dig
o_tlsa='' # TLSA record by openssl
tlsa_record='' # TLSA record
tlsa_method=2
########################################################################
####
##
# All needed functions! DO NOT TOUCH-IT!
##
###
########################################################################
byebye() {
mssg "KO" "Script stop here!"
mssg "KO" "Please, search to understand reasons."
exit 1
}
check_uid() {
if [ "$(id -u)" -ne 0 ]; then
mssg "KO" "ERROR: Script not launch with rights admin!"
byebye
fi
}
_dig() {
tlsa="$(dig TLSA _443._tcp."${domain}" +short)"
d_tlsa="$(echo $tlsa | awk '{ for(i=4;i<=NF;++i) printf "%s", tolower($i); print "" }')"
}
_openssl() {
tlsa="$(echo | openssl s_client -servername "${domain}" -showcerts -connect "${domain}":443 2>/dev/null | openssl x509 -noout -pubkey | openssl pkey -outform der -pubin 2>/dev/null | openssl dgst -"${algo}" 2>/dev/null )"
o_tlsa="$(echo $tlsa | awk -F'=' '{ print $2 }' | tr -d ' ')"
}
mssg() {
typeset statut info text
statut="$1" info="$2"
case "${statut}" in
"KO") text="[ ${red}${statut}${neutral} ] ${info}" ;;
"OK") text="[ ${green}${statut}${neutral} ] ${info}" ;;
#*) mssg="${text}" ;;
esac
printf "%s \n" "${text}"
unset info statut text
}
new_tlsa() {
cert="/etc/letsencrypt/live/${domain}/cert.pem"
case "${le_key_type}" in
"ecdsa")
tlsa_cert_associated="$(openssl x509 -noout -pubkey -in "${cert}" | openssl ec -pubin -outform der 2>/dev/null | "${algo}")"
;;
"rsa")
tlsa_cert_associated="$(openssl x509 -noout -pubkey -in "${cert}" | openssl rsa -pubin -outform der 2>/dev/null | "${algo}")"
;;
esac
tlsa_record="_${tls_port}._${tls_proto}.${domain}. IN TLSA ${tlsa_usage} ${tlsa_selector} ${tlsa_method} ${tlsa_cert_associated}"
unset tlsa_cert_associated
}
########################################################################
####
##
# Execution
##
###
########################################################################
if [ -z "${domain}" ]; then printf '%s\n' "[ KO ] Script stops here; no domain!"; exit; fi
_dig
_openssl
if [ "${d_tlsa}" = "${o_tlsa}" ]; then
mssg "OK" "Similar TLSA records! :D"
else
mssg "KO" "There seems to be a problem with the TLSA records of the domain: ${domain}!"
printf '%s\n' "Have you renew recently the TLS certs for the domain? If yes, change the TLSA record into the DNS zone relevent!"
printf '%s\n%s\n' "⇒ Perhaps, the dns.sh script shell can help you. ;-)"
check_uid
printf '%s\n' "⇒ Display new TLSA record:"
new_tlsa
printf '%s\n%s\n' "Add/modify tlsa into your DNS zone for ${domain}: " "${tlsa_record}"
printf '%s\n' "⇒ Modify TLSA record into the domain zone for ${domain}"
"${dir_admin}"/dns.ksh tlsa "${domain}"
fi
Modifiez la valeur de la variable dir_admin
!
Comme vous pouvez le remarquer :
- il fait appel au fichier de configuration dns.conf, publié ci-dessous
- et si la correspondance des enregistrements TLSA ne se fait pas, il
appelle le script pdksh dns.ksh avec l’argument
tlsa
suivi du nom de domaine à cibler
dns.conf
Voici le fichier de configuration qui sert à la fois pour le script shell tlsa.sh et le script pdksh dns.ksh.
########################################################################
#
# Author: Stéphane HUC
# mail: devs@stephane-huc.net
# gpg:fingerprint: CE2C CF7C AB68 0329 0D20 5F49 6135 D440 4D44 BD58
#
# License: BSD Simplified
#
# Github: https://framagit.org/hucste/AH.git
#
# Date: 2022/06/01 07:20
#
########################################################################
###
##
# Config file to dns.ksh script
##
###
########################################################################
### Algorithm
## values: sha256, sha512; choose-it segun TLSA Method
algo="sha256"
### SOA Serial type
## values: date, timestamp
## DNS recommandation: prefer date
SOA_serial_type="date"
### Port number
tls_port=443
### Protocols
## values: stcp, tcp, udp
tls_proto="tcp"
### TLSA
## Lets Encrypt Recommandation;
## see: https://community.letsencrypt.org/t/please-avoid-3-0-1-and-3-0-2-dane-tlsa-records-with-le-certificates/7022
## usage: Lets Encrypt recommands 3, at least 2
## values: 0 => 3; or (PKIX-TA, PKIX-EE, DANE-TA, DANE-EE; respectivly: 0 -> 3)
tlsa_usage=3
## selector: Lets Encrypt recommands 1
## values: 0 or 1; or (CERT, SPKI; respectively: O or 1)
tlsa_selector=1
## method: Lets Encrypt recommands 1
## values: 0 => 2; or (FULL, SHA256, SHA512; respectively: 0 -> 2)
# this change segun algo
tlsa_method=1
### Key Type Letsencrypt
## rsa or ecdsa
## if ecdsa, specify elliptic curve: secp256r1, secp384r1, secp512r1 (256 is enough)
le_key_type=ecdsa
le_curve=secp256r1
Personnellement, je fais le choix d’utiliser :
- l’algorithme sha512.
- le type de clé ecdsa qui déclenche l’utilisation de la commande ec
lors de l’utilisation d’openssl, par les scripts dns.ksh ou tlsa.sh.
Il est bien sûr possible d’utiliser rsa ; dans ce cas, les scripts
shell n’utiliseront pas la variable
le_curve
.
dns.ksh
Ce script long et complexe est capable de :
- générer les signatures DNSSEC pour la zone DNS d’un domaine cible.
- modifier l’enregistrement TLSA puis regénérer les signatures DNSSEC ;
dans ce cas :
- récupèrer l’enregistrement de numéro de série SOA en cours dans la zone DNS du domaine cible
- créer un nouveau fichier de zone DNS et sauvegarder l’actuel
- si le nouveau fichier de zone DNS est accessible, le script écrit :
- un nouveau numéro de série SOA
- remplace l’ancien enregistrement TLSA par le nouveau
- essaye de signer à nouveau la zone DNS à l’aide du protocole DNSSEC
- s’il réussit, il supprime l’ancien fichier de zone DNS
- s’il échoue, il avertit, arrête son exécution et dans ce cas,
il faudra renommer à la main la sauvegarde de la zone DNS
pour en faire à nouveau l’actuelle, puis chercher à comprendre
pourquoi le script à échouer - dans ce cas-là, il est intéressant
de positionner la variable
debug
à1
, ce qui activera la journalisation des différentes étapes, et pourra aider à l’analyse, lors d’une exécution manuelle.
Un petit coup de ./dns.ksh help
vous en dira peut-être plus sur son utilisation. ;)
#!/bin/ksh
set -e
#set -x
################################################################################
#
# Author: Stéphane HUC
# mail: devs@stephane-huc.net
# gpg:fingerprint: CE2C CF7C AB68 0329 0D20 5F49 6135 D440 4D44 BD58
#
# License: BSD Simplified
#
# Github:
#
# Date: 2022/06/01 07:25
#
################################################################################
#
# Purpose: to add a TLSA Record into DNS zone, segun your cert TLS (LE)
# - for the geek: DANE-TLSA...
#### IMPORTANT: recreate your TLSA Record after (re?)new cert...
#
# Needed tools: nsd* and ldnscript
## ldnscript is a tool to sign dns zone. (DNSSEC)
## https://framagit.org/22decembre/ldnscripts.git
#
# OS: Tested on OpenBSD
#
################################################################################
ROOT="$(dirname "$(readlink -f -- "$0")")"
. "${ROOT}/dns.conf"
################################################################################
###
##
# DONT TOUCH THOSES VARIABLES!
##
###
################################################################################
debug=0
dir_le="/etc/letsencrypt/live"
dir_ns_cfg="/etc/ns" # folder config ns
dir_sbin="/usr/local/sbin"
log="${ROOT}/dns-script.log"
nsd_cfg="/var/nsd/etc/nsd.conf"
timestamp="$(date +%s)"
today="$(date +"%Y%m%d")"
server="nsd"
SOA_ns=""
tlsa_record=""
set -A tlsa_records # if X509 DNS Alternative Names > 1
set -A tlsa_method_names -- "FULL" "SHA256" "SHA512"
set -A tlsa_selector_names -- "CERT" "SPKI"
set -A tlsa_usage_names -- "PKIX-TA" "PKIX-EE" "DANE-TA" "DANE-EE"
NB_PARAMS="$#"
set -A PARAMS -- "$@"
ROOT="$(dirname "$(readlink -f -- "$0")")"
if [ -z "${PARAMS[0]}" ]; then MENU_CHOICE="help"
else
PARAMS[0]="$(printf '%s' "${PARAMS[0]}" | tr -s "[:upper:]" "[:lower:]")"
MENU_CHOICE=${PARAMS[0]}
fi
[ -n "${PARAMS[1]}" ] && domain="$(printf '%s' "${PARAMS[1]}" | tr -s "[:upper:]" "[:lower:]")"
################################################################################
####
##
# All needed functions! DO NOT TOUCH-IT!
##
###
################################################################################
_add_tlsa() {
check_var_algo
check_var_soa_serial_type
check_var_tls_port
check_var_tls_proto
check_tlsa_methods
check_tlsa_selectors
check_tlsa_usages
get_soa_ns
danefile="${zonefile}.dane"
newzonefile="${zonefile}.${today}"
oldzonefile="${zonefile}.${OLD_SOA_sn}"
create_new_filezone
if [ -f "${newzonefile}" ]; then
write_soa_serial_number
build_tlsa_record
write_tlsa_record
mv_new_file_zone
if _resign; then del_old_zonefile; fi
fi
unset danefile newzonefile oldzonefile
}
build_needed_variables() {
check_var_domain
printf '%s\n' "*** Build needed variables:"
# build cert variable if menu 'tlsa'
if [ "${MENU_CHOICE}" = "tlsa" ]; then
cert="${dir_le}/${domain}/cert.pem"
if [ ! -f "${cert}" ]; then
display_mssg "KO" "*** It seems cert file not exists!"
byebye
else
printf '%s\n' "cert: ${cert}"
fi
fi
# build zonedir and zonefile variables
zonedir="$(awk -F '"' '/zonesdir/ { print substr($2,-1) }' "${nsd_cfg}")"
if [ -z "${zonedir}" ]; then zonedir="/var/nsd/zones/"; fi
printf '%s\n' "zonedir: ${zonedir}"
#zonefile="$(awk -F '"' '/zonefile: "[a-z]*\/'"${domain}"'"/ { print substr($2, -1) }' "${nsd_cfg}")"
#if [ "$(printf '%s' "${zonefile}" | awk -F'/' '{ print $1}')" == "signed" ]; then
##zonefilesigned=$zonefile
#zonefile="$(find "${dir_ns_cfg}" -name "${domain}")"
#if [ -z "${zonefile}" ]; then zonefile="$(find "${zonedir}" -name "${domain}")"; fi
#if [ -z "${zonefile}" ]; then
#display_mssg "KO" "ERROR: It seems zonefile for domain: '${domain}' not exists!"
#byebye
#fi
#else
#zonefile="${zonedir}${zonefile}"
#fi
zonefile="${dir_ns_cfg}/${domain}"
printf '%s\n' "zonefile: ${zonefile}"
}
build_tlsa_record() {
# get TLSA by reading cert pem
case "${le_key_type}" in
"ecdsa")
command="ec"
;;
"rsa")
command="rsa"
;;
esac
tlsa_cert_associated="$(openssl x509 -noout -pubkey -in "${cert}" | openssl "${command}" -pubin -outform der 2>/dev/null | "${algo}")"
_log "TLSA Cert Associated: ${tlsa_cert_associated}"
unset command
if [ -z "${tlsa_cert_associated}" ]; then
display_mssg "KO" "ERROR: TLSA Cert Associated could not generated!"
byebye
else
# rebuild tlsa method segun algo choosed; possible: 0 (no match), 1 (sha256), 2 (sha512)
case "${algo}" in
"sha256") tlsa_method=1 ;;
"sha512") tlsa_method=2 ;;
*) tlsa_method=0 ;;
esac
if [ "${tls_port}" = "443" ] && [ "${tls_proto}" = "tcp" ]; then
get_dns_alternative_names
count="${#domains[@]}"
if [ "${count}" -eq 1 ]; then
set_tlsa_record
else
set_tlsa_records
fi
fi
fi
unset tlsa_cert_associated
}
byebye() {
display_mssg "KO" "Script stop here!"
display_mssg "KO" "Please, search to understand reasons."
exit 1
}
check_domain_name() {
pattern="^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$" # like RFC 1123
if [ ${#domain} -gt 67 ]; then # Larg domain name <= 67
display_mssg "KO" "Error: Domain Length: ${domain}; >= 67 carachters!"
byebye
fi
if printf '%s\n' "${domain}" | grep -Eio "${pattern}"; then
display_mssg "OK" "Domain Name: ${domain} is valid!"
sleep 1
else
display_mssg "KO" "Error: Bad Domain Name: ${domain}"
byebye
fi
unset pattern
}
check_tlsa_methods() {
case "${tlsa_method}" in
0|"FULL") tlsa_method=0 ;;
1|"SHA256") tlsa_method=1 ;;
2|"SHA512") tlsa_method=2 ;;
*)
display_mssg "KO" "/!\ The TLSA Method: not correctly configurated!"
byebye
::
esac
_log "TLSA Method: ${tlsa_method}"
}
check_tlsa_selectors() {
case "${tlsa_selector}" in
0|"CERT") tlsa_selector=0 ;;
1|"SPKI") tlsa_selector=1 ;;
*)
display_mssg "KO" "/!\ The TLSA Selector: not correctly configurated!"
byebye
;;
esac
_log "TLSA Selector: ${tlsa_selector}"
}
check_tlsa_usages() {
case "${tlsa_usage}" in
0|"PKIX-TA") tlsa_usage=0 ;;
1|"PKIX-EE") tlsa_usage=1 ;;
2|"DANE-TA") tlsa_usage=2 ;;
3|"DANE-EE") tlsa_usage=3 ;;
*)
display_mssg "KO" "/!\ The TLSA Usage: not correctly configurated!"
byebye
;;
esac
_log "TLSA Usage: ${tlsa_usage}"
}
check_uid() {
if [ "$(id -u)" -ne 0 ]; then
display_mssg "KO" "ERROR: Script not launch with rights admin!"
byebye
fi
}
check_var_algo () {
if [ "${algo}" != "sha256" ] && [ "${algo}" != "sha512" ]; then
display_mssg "KO" "/!\ Algorythm: not correctly configurated!"
byebye
fi
_log "Algo: ${algo}"
}
check_var_domain() {
if [ -z "${domain}" ]; then
display_mssg "KO" "*** It seems fault informations!"
help
byebye
fi
_log "Domain: ${domain}"
}
check_var_soa_serial_type() {
if [ "${SOA_serial_type}" != "date" ] && [ "${SOA_serial_type}" != "timestamp" ]; then
display_mssg "KO" "/!\ SOA Serial Type: not correctly configurated!"
byebye
fi
_log "SOA Serial Type: ${SOA_serial_type}"
}
check_var_tls_port(){
if [ "${tls_port}" -lt 0 ]; then
display_mssg "KO" "/!\ TLS port: not correctly configurated!"
byebye
fi
_log "TLS port: ${tls_port}"
}
check_var_tls_proto() {
if [ "${tls_proto}" != "sctp" ] && [ "${tls_proto}" != "tcp" ] && [ "${tls_proto}" != "tcp" ]; then
display_mssg "KO" "/!\ TLS proto: not correctly configurated!"
byebye
fi
_log "TLS proto: ${tls_proto}"
}
checkconf() {
nsd-checkconf "${nsd_cfg}"
}
checkzone() {
nsd-checkzone "${domain}" "${zonefile}"
}
confirm () {
read -r response?"${1} [y|n] "
case "${response}" in
# 'o', 'O': Oui and not 0!
y|Y|o|O|1) true ;;
*) false ;;
esac
unset response
}
create_new_filezone() {
cp "${zonefile}" "${newzonefile}"
}
del_old_zonefile() {
if [ -f "${oldzonefile}" ]; then
rm -fP "${oldzonefile}"
fi
}
display_mssg() {
typeset statut info text
statut="$1" info="$2"
case "${statut}" in
"KO") text="[ ${red}${statut}${neutral} ] ${info}" ;;
"OK") text="[ ${green}${statut}${neutral} ] ${info}" ;;
#*) mssg="${text}" ;;
esac
printf "%s \n" "${text}"
unset info statut text
}
get_dns_alternative_names() {
# get "X509 DNS Alternative Names" characters
domains="$(echo | openssl x509 -text -noout -in "${cert}" | awk -F ',' '/DNS:/ { for(i=1;i<NF;i++) { p=match($i,":"); print substr($i,p+1) }}')"
# convert into array; no double-quotes, else not run!
set -A domains -- ${domains[@]}
printf '%s\n' "domains: ${domains[*]}"
_log "domains: ${domains[*]}"
}
get_soa_ns() {
OLD_SOA_sn="$(grep -A1 "SOA" "${zonefile}" | tail -n1 | awk -F ' ' '{ print $1 }')"
_log "Old SOA Serial Number: ${OLD_SOA_sn}!"
}
get_soa_serial_number() {
OLD_SOA_sn="$(printf '%s\n' "${line}" | awk -F ' ' '{ print $1 }')"
_log "OLD SOA Serial Number: ${OLD_SOA_sn}"
}
help() {
printf '%s\n' "
$0 sign domain # to sign a domain
$0 tlsa domain # to add a tlsa record into domain zone
----
when use tlsa, this script will resign the domain zone...
"
}
init_zone() {
"${dir_sbin}"/ldnscript init "${domain}"
}
in_array() {
local i=0 need="$1" IFS=" "; shift; set -A array -- $*
count="${#array[@]}"
while [ $i -le $count ]; do
if [ "${array[$i]}" = "${need}" ]; then return 0; fi # true
#let "i=$i+1"
(( i=i+1 ))
done
return 1
unset i need IFS array
}
_log() {
if [ "${debug}" -eq 1 ]; then printf '%s\n' "$1" >> "${log}"; fi
}
main() {
check_uid
verify_need_softs
build_needed_variables
check_domain_name
case "${MENU_CHOICE}" in
"help") help ;;
"sign") _resign ;;
"tlsa") _add_tlsa ;;
*)
display_mssg "KO" "ERROR: this option ${MENU_CHOICE} is not exists!"
help
byebye
;;
esac
}
mv_new_file_zone() {
if [ -f "${newzonefile}" ]; then
mv "${zonefile}" "${oldzonefile}"
mv "${newzonefile}" "${zonefile}"
fi
}
_resign() {
if checkzone && checkconf; then
display_mssg "OK" "file config nsd and zone ${domain} are good! :D"
sign_zone
else
display_mssg "KO" "ERROR: it exists a problem with file config nsd or zone ${domain}"
byebye
fi
}
restart_server() {
printf '%s\n' "=> Restart Server: "
stop_server
start_server
status_server
}
set_soa_serial_number() {
case "${SOA_serial_type}" in
"date")
SOA_date="$(printf '%s' "${OLD_SOA_sn}" | awk '{print substr($0, 0, 8)}')";
SOA_number="$(printf '%s' "${OLD_SOA_sn}" | awk '{print substr($0, 9)}')";
if [ "${SOA_date}" == "${today}" ]; then
#let SOA_number=$SOA_number+1
(( SOA_number=${SOA_number}+1 )) || true
if [ "${SOA_number}" -lt 10 ]; then SOA_number="0${SOA_number}"; fi
SOA_sn="${SOA_date}${SOA_number}"
else
SOA_sn="${today}01"
fi
;;
"timestamp")
SOA_sn="${timestamp}"
;;
*)
display_mssg "KO" "Invalid SOA Serial Type!"
byebye
;;
esac
_log "New SOA Serial Number: ${SOA_sn}!"
}
set_tlsa_record() {
# build tlsa record
tlsa_records[0]="_${tls_port}._${tls_proto}.${domains[0]}. IN TLSA ${tlsa_usage} ${tlsa_selector} ${tlsa_method} ${tlsa_cert_associated}"
_log "TLSA Record: ${tlsa_records[0]}"
}
set_tlsa_records() {
# do not use domain variable here
i=0
for dom in "${domains[@]}"; do
tlsa_records[$i]="_${tls_port}._${tls_proto}.${dom}. IN TLSA ${tlsa_usage} ${tlsa_selector} ${tlsa_method} ${tlsa_cert_associated}"
(( i=i+1 ))
done
unset i dom
_log "TLSA Records: ${tlsa_records[*]}"
}
sign_zone() {
"${dir_sbin}"/ldnscript signing "${domain}"
}
start_server() {
printf '%s\n' "Start serveur: ${server}"
rcctl start "${server}"
sleep 1s
}
status_server() {
printf '%s\n' "Check serveur: ${server}"
rcctl check "${server}"
}
stop_server() {
printf '%s\n' "Stop serveur: ${server}"
rcctl stop "${server}"
sleep 1s
}
verify_need_softs() {
if [ ! -f "${dir_sbin}/ldnscript" ]; then
display_mssg "KO" "ERROR: ldnscript seems not install!"
byebye
elif [ ! -x "${dir_sbin}/ldnscript" ]; then
display_mssg "KO" "ERROR: ldnscript is not executable!"
byebye
fi
}
write_soa_serial_number() {
set_soa_serial_number
if sed -i -e "s#\(.*\)${OLD_SOA_sn} \;#\1${SOA_sn} \;#" "${newzonefile}"; then
_log "SOA serial number changed!"
else
display_mssg "KO" "/!\ Script cant change SOA serial number!"
fi
}
write_tlsa_record() {
if [ ! -f "${danefile}" ]; then touch "${danefile}"; fi
i=0
# add tlsa records into dns zone
for tlsa_record in "${tlsa_records[@]}"; do
dom="${domains[$i]}"
_log "domain: $dom"
### /!\ ERROR with $domain /!\
if sed -i -e "s#_${tls_port}._${tls_proto}.${dom}. IN TLSA\(.*\)#${tlsa_record}#" "${newzonefile}"; then
_log "TLSA Record rewrited!"
else
printf '%s\n' "${tlsa_record}" >> "${newzonefile}"
_log "TLSA Record added!"
fi
(( i=i+1 ))
# add record in first line into dane file
printf '%s\n' "${timestamp}:${tlsa_record}" >> "${danefile}"
_log "${timestamp}:${tlsa_record}"
unset dom
done
unset i tlsa_record
}
################################################################################
main
EOD
End Of Documentation
Voilà mon processus de gestion de mes zones DNS, avec le protocole DNSSEC et les enregistrements TLSA pour le service web.
D’un processus long et complexe, je peux ainsi gérer simplement à l’aide de deux commandes bien utiles :
- signer par DNNSEC mes zones DNS :
./dns.ksh sign domain
- tester les enregistrements TLSA à tout moment, et si besoin les regénérer :
./tlsa.sh domain
Mais si vous avez bien compris, c’est le cron mensuel qui s’en occupe tout seul et qui me fait le rapport adéquat, ainsi je sais comment cela s’est exécuté.