Description
Since 2018, I asked me about how to manage TLSA records, according to the DANE and DNSSEC protocols, for my DNS. (I wroted one article in french, on March 2018, about creating TLSA records in shell or PHP languages; if you read french, see: DNS: Générer un enregistrement TLSA…)
Someone prefers using knot, as package on OpenBSD, because they thing that’s complex to manage. Here, we’ll see some tips to manage this, an automated way, in shell, on OpenBSD.
My DNS service run since 4 years, under OpenBSD native tool named nsd. I manage DNSSEC with ldns tools, a package into ports. In the facts, I use ldnscript tool to create all needed keys and manage DNSSEC.
⇒ Starting Juin 2022, I decided to switch from RSA to use ECDSA.
Before going any further in this direction, let’s move on to the installation of the necessary prerequisites necessary:
Installation
ldnscript
To remind myself how to do this, I made myself the following little memo:
-
Install ldns-utils:
$ doas pkg_add ldns-utils git
-
Download and install 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
- Configure /etc/ns/ldnscript.conf
; SHA256 est largement suffisant et sécuritaire
ALG=ECDSAP256SHA256
NSEC3_ALG=SHA-256
-
Init the domain name:
$ doas ldnscript init domain.tld
-
You need to create symbolic link from
/usr/bin/dig
to/usr/sbin/dig
:$ doas ln -sf /usr/bin/dig /usr/sbin/dig
(without this, theldnscript
tool will not be able to find the binary and therefore will refuse to run, displaying error message).
Configuration
/etc/monthly.local
Into the /etc/monthly.local
script, I include this shellcode to create
the needed rollover keys:
### ldnscript
printf '%s\n' "⇒ ldnscript rollover"
/usr/local/sbin/ldnscript rollover all
Let’s Encrypt
Next, the shell script queries the Let’s Encrypt services to know if it’s necessary to renew the certificates for the domain names I use.
Normally, one would use the native acme
client on OpenBSD, but I
had some problems, I decided to switch to certbot
.
To renew, on shell, that’s enough:
/usr/local/bin/certbot renew --pre-hook "rcctl stop nginx" --post-hook "rcctl start nginx"
(Yesss, I known; I use nginx, because I prefer… but, I you like httpd, the native webserver, replace by his name service).
In fact, this part is not directly related to the DNS(SEC) management. This is important to take carefull because during certificate renewall, it’s necessary to regenerate TLSA records. We’ll see that later…
DNS Zone Example
Here, for instance, a minimilastic DNS Zone, managed by ns server:
$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
Generate TLSA Record
On shellcode, managing a TLSA record is simple — need to use openssl:
openssl x509 -noout -pubkey -in "${cert}" | openssl "${command}" -pubin -outform der 2>/dev/null | "${algo}" | tr "a-z" "A-Z"
Explains :
⇒ The above command generate a tlsa_cert_associated
variable, where:
$cert
: the absolute pathname for the server TLS cert, on the filesystem, about one domain name.$command
: the command name used by openssl, eitherrsa
orec
(reciprocally for RSA or ECDSA encryption records).$algo
:sha256
, orsha512
tool to use.
⇒ Writing the TLSA record is also simple:
tlsa_record="_${tls_port}._${tls_proto}.${domain}. IN TLSA ${tlsa_usage} ${tlsa_selector} ${tlsa_method} ${tlsa_cert_associated}"
${tls_port}
: port number of the webservice; by default 443${tls_proto}
: protocol name used; by default tls${domain}
: the domain name${tlsa_usage}
: the number according the DANE-EE constraint; 3 is recommended by Let’s Encrypt,${tlsa_selector}
: the number according to the SPKI selector; 1 is recommended by Let’s Encrypt,${tlsa_method}
: the method segun the choosed algorythm; by default: SHA256, which offers currently a secure level of encryption, egually recommended by Let’s Encrypt.- finishing with the previous
tlsa_cert_associated
variable.
Check TLSA Record
Check a TLSA record is more complex; you need to:
- 1/ query the DNS server to known the actual TLSA record, with
dig
tool, for example. - 2/ compare with the
openssl
output; OpenSSL requesting the cert to the webserver, linked to the domain name.
- queries the DNS server, like:
tlsa="$(dig TLSA _443._tcp."${domain}" +short)"
d_tlsa="$(echo "${tlsa}" | awk '{ for(i=4;i<=NF;++i) printf "%s", tolower($i); print "" }')"
- use openssl to request TLS certificate used on the webserver:
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 ' ')"
Finally, it enoughs to compare both shell variables, d_tlsa
and o_tlsa
.
In normal time, that should be case.
Except in case of renewal certificate, which will require the renewal
of the TLSA record in the domain name zone, on the DNS server.
NOTE: if this is not done, a DNS server query on the DNSSEC protocol will generate an error since the TLSA record will not match a freshly used, or renewed TLS cert, which will result in that consequence: access to the server, linked to the target domain name, will be impossible. It will be necessary to generate a new TLSA record corresponding to the renewal of the TLS certificate, then egual to regenerate the signature of the DNSSEC records, for the DNS zone of the target domain.
Shell Scripts
Note: Put the tlsa.sh, dns.conf, dns.ksh shell scripts on your home. If you change their location on the filesystem, think to modify your monthly local.
monthly.local
Here, a 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
Here, the tlsa.sh script:
#!/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
ATTENTION: You need to modify the dir_admin
variable, at the top of
script!
Explains:
- it call the dns.conf file config; see below,
- and, if the TLSA records does not match, it call the pdksh dns.ksh
script, with
tlsa
and targeted domain name as arguments.
dns.conf
Here, the file config — needed for both dns.ksh and tlsa.sh shell scripts:
########################################################################
#
# 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
I personally choose to use:
- the sha512 algorythm
- the ecdsa to use the ec command with openssl. Of course, it’s
possible to use rsa; in this case, those shell scripts will not
use the
$le_curve
variable.
dns.ksh
This complex and large script is available to:
- generate DNSSEC signs for the DNS zone.
- modify TLSA records and regenerate DNSSEC signs; in this case:
- find the SOA record
- create a new file for the DNS zone and backup the actual
- if the new file is available, the script will write:
- a new SOA record
- replace the oldier TLSA record with the new
- try to sign the DNS zone with the DNSSEC protocol:
- if succeeded, it destroy the oldier DNS zone file,
- if it fails, it warns, stops the execution and this case
you need to rename manually the backuped file.
You can modify the
debug
variable to 1, and relaunch the script; it logs the differents steps. This helps to review and analyse why…
Maybe, ./dns.ksh help
will show you more information.
#!/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
Voila; this is my process to manage my DNS zones, with DNSSEC for the TLSA records.
From a large and complex process, I can manage simply with the two useful commands:
- sign my DNS zones by DNSSEC:
./dns.ksh sign domain
- check the TLSA records, at any time, and if necessary, regenerate them:
./tlsa.sh domain
But if you have understood, the monthly cron takes care of it all and makes the appropriate report, so I know how it was executed.