%

OpenBSD : Gérer DNS, DNSSEC (puis automatiser ses enregistrements TLSA)

Article publié, le et modifié le
18 minutes de lecture

Cet article contient 3804 mots.
Source brute de l'article :
Commit version : 403d4ae

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 :

  1. installer les binaires nécessaires : $ doas pkg_add ldns-utils git

  2. 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
  1. configurer le fichier /etc/ns/ldnscript.conf
; SHA256 est largement suffisant et sécuritaire
ALG=ECDSAP256SHA256 
NSEC3_ALG=SHA-256
  1. initialisation du domaine $ doas ldnscript init domain.tld

  2. 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’outil ldnscript ne pourra trouver le binaire dig 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, soit rsa ou ec (réciproquement pour les enregistrements selon le chiffrement RSA ou ECDSA).
  • et, où la variable $algo est le nom de l’utilitaire sha256, ou sha512, 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. 1/ interroger le serveur DNS pour connaître l’enregistrement TLSA actuel par le biais de l’outil dig, pour l’exemple.
  2. 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é.