Initial commit

This commit is contained in:
Christoph 2016-11-16 14:53:49 +01:00
commit d8880d36c7
5 changed files with 3532 additions and 0 deletions

43
README.md Normal file
View File

@ -0,0 +1,43 @@
## dehydrated_cron
### Das Skript _install_dehydrated.sh_
* installiert _dehydrated_ aus dem git Repository
* erstellt notwendige Verzeichnisse und Konfigurationen
* erstellt ein Cron Job Skript _dehydrated_cron.sh_ zur Generierung der Zertifikate
* erstellt die Skripte _create_domains_file.sh_, _change_ssl_directives.sh_ und _dh_tlsgen.sh_ im _tools_-Verzeichnis
* Konfiguriert einen bereits installierten Apache Webserver
### Installation
Das Skript _install_dehydrated.sh_ auf den Zielrechner kopieren und (als root) ausführen. Die Konfigurationsdateien _config_ und __dehydrated_cron.conf_ im Konfigurationsverzeichnis (default: `/etc/dehydrated`) gegebenenfalls anpassen. **Voreingestellt ist der Testing Modus**
Alle Verzeichnisse können während der Installation angegeben werden:
* _DH Install Dir_: hierhin wird das Repository geclont
* _DH Conf Dir_: Hier werden die Konfigurationdateien für das dehydrated Skript (_config_) und für den Cron Job (_dehydrated_cron.conf_) hinterlegt.
* _DH Base Dir_: das Hauptverzeichnis für dehydrated bzw. Let's Encrypt. Dises Verzeichnis enthält die Accountdaten für den Zugang bei Let's Encrypt, die Zertifikate und Keys, das Skript _hook.sh_ und die Datei _domains.txt_, mit den zu erstellenden Zertifikatsnamen und -aliasen. Im Unterverzeichnis _cron_ liegt das Cronskript _dehydrated_cron.sh_, im Unterverzeichnis tools die Skripte _change_ssl_directives.sh_, _create_domains_file.sh_ und _dh_tlsgen.sh_.
Während Installation kann angegeben werden, wohin der Cronjob installiert werden soll: in das Verzeichnis `/etc/cron.d/` oder als root cronjob angehängt an die datei `/var/spool/cron/crontabs/root`.
**Das Installationsskript überschreibt keine vorhandenen Konfigurationen.** In einer Umgebung, in der _dehydrated_cron_ bereits installiert ist werden durch das erneute Ausführen vom _install_dehydrated.sh_ Skripte und das git Repository von _dehydrated_ gegebenenfalls aktualisiert. Haben sich Skripte oder das Repository geändert, so wird eine Sicherung (mit Zeitstempel) erstellt.
### _dehydrated_cron.sh_
Der Cronjob _dehydrated_cron.sh_ erstellt oder eneuert Zertikikate und stellt diese im Verzeichnis _DH_BASE/certs/\<hostname\>/_ zur Verfügung. Soll ein Host für einen bestimmten Service _DANE_ unterstützen, so erstellt der Cronob die notwendigen _TLSA_-Records und veröffentlicht diese im Nameserver. Hierfür ist der zuständige Nameserver entsprechend vorzubereiten/zu konfigurieren (SSH-Zugang, Bereitstellung von Skripten, die die generierten _TLSA_-records dem Zonefile hinzufügen, die Seriennumer erhöhen und die Zone reloaden (s.u. Verzeichnis bind)).
Ausgeführt auf einer Konsole, ist das Skript informativ, ausgeführt als Cronjob werden nur dann Ausgaben produziert, falls Zertifikate ausgestellt/erneuert wurden oder Fehler aufgetreten sind.
### Tools
Es werden drei Hilfs-Skripte im Verzeichnis _DH_BASE/tools_ zur Verfogung gestellt:
* _create_domains_file.sh_: sucht in den VHost Konfigurationen nach Hosts, für die ein Zertifikat erstellt werden kann/soll und trägt common name sowie eventuell alternative names in eine Zeile der Datei _DH_BASE/domains.txt_ ein. Diese Datei wird von _dehydrated_ eingelesen um die entsprechenden Zertifikate zu generieren (falls sie noch nicht existieren oder bald abgelaufen sind).
* _change_ssl_directives.sh_: Setzt in den Direktiven `SSLCertificateFile` und `SSLCertificateKeyFile` die entsprchenden Pfade zum Zertifikat/Key
* _dh_tlsgen.sh_ : Generiert _TLSA__Records für _hostname:port_. So kann beispielsweise überprüft werden, ob durch den Cronjob die korrekten Records im Nameserver eingetragen wurden.
***
### (Repository-)Verzeichnis tools-bind
In diesem Verzeichnis liegen drei Skripte, die für die Bereitstellung von TLSA Records benötigt werden. Diese Scripte müssen sich auf dem zuständigen Nameserver befinden und über SSH vom Cronjob _dehydrated_cron.sh_ ausgeführt werden können. Entsprechende Parameter können in der Konfigurationsdatei _dehydrated_cron.conf_ angepasst werden. Die ist nur dann notwendig, wenn für einen Service _DANE_ Unterstützung implementiert ist.
* _bind_get_domain_by_hostname.sh_
* _bind_set_renew_tlsa.sh_
* _bind_set_new_serial.sh_

2744
install_dehydrated.sh Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,113 @@
#!/usr/bin/env bash
# - Bind configuration file containing zone definitions
# -
ZONE_CONF_FILE=/etc/bind/named.conf.local
## --
## -- End: Variable definitions
## ***
## *** Don't make changes after this line ***
## ***
## --- some functions
## ---
echononl(){
echo X\\c > /tmp/shprompt$$
if [ `wc -c /tmp/shprompt$$ | awk '{print $1}'` -eq 1 ]; then
echo -e -n "$*\\c" 1>&2
else
echo -e -n "$*" 1>&2
fi
rm /tmp/shprompt$$
}
warn (){
echo ""
echo -e "\t[ \033[33m\033[1mWarning\033[m ]: $*"
echo ""
}
info (){
echo ""
echo -e "\t[ \033[32m\033[1mInfo\033[m ]: $*"
echo ""
}
error(){
echo ""
echo -e "\t[ \033[31m\033[1mFehler\033[m ]: $*"
echo ""
}
echo_ok() {
echo -e "\033[75G[ \033[32mok\033[m ]"
}
echo_failed(){
echo -e "\033[75G[ \033[1;31mfailed\033[m ]"
}
echo_skipped() {
echo -e "\033[75G[ \033[33m\033[1mskipped\033[m ]"
}
## ---
## --- END: functions
hostname=$1
# - Test whether stdout (file descriptor 1) is a terminal or not (e.g. cron
# - or if you pipe the output to some other program)
#
if [[ -t 1 ]] ; then
verbose=true
else
verbose=false
fi
$verbose && echo ""
# - Validate Syntax of given domain
# -
valid_domain_regex="^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$"
$verbose && echononl "\tValidate syntax of given hostname/domain.."
if [[ $hostname =~ $valid_domain_regex ]]; then
if [[ ! $hostname =~ \. ]]; then
$verbose && echo_failed
$verbose && error "Invalid hostname/domain given!"
exit 10
else
$verbose && echo_ok
fi
else
$verbose && echo_failed
$verbose && error "Invalid hostname/domain given!"
exit 10
fi
_failed=false
_hostname=$(echo ${hostname//\./\\.})
while ! grep -e "$_hostname" $ZONE_CONF_FILE > /dev/null 2>&1 ; do
hostname=${hostname#*.}
_hostname=$(echo ${hostname//\./\\.})
if [[ ! $_hostname =~ \. ]]; then
_failed=true
break
fi
done
if $_failed ; then
$verbose && error "hostname \"$1\" not supported by this nameserver!"
else
domain=$hostname
if $verbose ; then
info "Domain: $domain"
else
echo "$domain"
fi
fi
$verbose && echo
exit 0

281
tools-bind/bind_set_new_serial.sh Executable file
View File

@ -0,0 +1,281 @@
#!/usr/bin/env bash
# - Sets new serial and reloads zone
# -
# - Return (Exit) Codes:
# - success:
# - 0: Serial is replaced and Zone is reloaded
# - error:
# - 10: Invalid Hostname/Domain given
# - 15: Hostname/Domain not supported
# - 11: No zonefile found
# - 12: Determin new Serial failed
# - 13: Increasing Serial failed
# - 14: Reloading Zone failed
# - 99: Fatal error
# -
# - usage: ./nd_set_new_serial.sh <hostname|domain>
# -
# - example: ./nd_set_new_serial.sh a.mx.open.de
# -
## -- Variable definitions
## --
# - Bind configuration file containing zone definitions
# -
ZONE_CONF_FILE=/etc/bind/named.conf.local
BIND_USER=bind
BIND_GROUP=bind
## --
## -- End: Variable definitions
## ***
## *** Don't make changes after this line ***
## ***
## --- some functions
## ---
echononl(){
echo X\\c > /tmp/shprompt$$
if [ `wc -c /tmp/shprompt$$ | awk '{print $1}'` -eq 1 ]; then
echo -e -n "$*\\c" 1>&2
else
echo -e -n "$*" 1>&2
fi
rm /tmp/shprompt$$
}
fatal(){
echo ""
echo -e "[ \033[31m\033[1mError\033[m ]: $*"
echo ""
echo -e "\t\033[31m\033[1mScript is canceled\033[m\033[m"
echo ""
exit 1
}
warn (){
echo ""
echo -e "\t[ \033[33m\033[1mWarning\033[m ]: $*"
echo ""
}
info (){
echo ""
echo -e "\t[ \033[32m\033[1mInfo\033[m ]: $*"
echo ""
}
ok (){
echo ""
echo -e "\t[ \033[36m\033[1mOk\033[m ]: $*"
echo ""
}
error(){
echo ""
echo -e "\t[ \033[31m\033[1mFehler\033[m ]: $*"
echo ""
}
echo_ok() {
echo -e "\033[75G[ \033[32mok\033[m ]"
}
echo_failed(){
echo -e "\033[75G[ \033[1;31mfailed\033[m ]"
}
echo_skipped() {
echo -e "\033[75G[ \033[33m\033[1mskipped\033[m ]"
}
containsElement () {
local e
for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
## ---
## --- END: functions
# - Parameter "check" can be used, to test whether this script
# - is accessable (e.g. from a script on a remote host)
# -
if [[ "$1" = "check" ]]; then
echo "\$1: $1"
exit 0
fi
host_name=$1
echo ""
# - Validate Syntax of given domain
# -
valid_domain_regex="^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$"
echononl "\tValidate syntax of given domain.."
if [[ $host_name =~ $valid_domain_regex ]]; then
if [[ ! $host_name =~ \. ]]; then
echo_failed
error "Invalid hostname/domain \"$1\" given!"
exit 10
else
echo_ok
fi
else
echo_failed
error "Invalid hostname/domain ($1) given!"
exit 10
fi
# - Determin zone (domain)
# -
_failed=false
_host_name=$host_name
_tmp_host_name=$(echo ${_host_name//\./\\.})
while ! grep -e "$_tmp_host_name" $ZONE_CONF_FILE > /dev/null 2>&1 ; do
_host_name=${_host_name#*.}
_tmp_host_name=$(echo ${_host_name//\./\\.})
if [[ ! $_tmp_host_name =~ \. ]]; then
_failed=true
break
fi
done
if $_failed ; then
error "Given hostname/domain \"$1\" not supported by this nameserver!"
else
domain=$_host_name
fi
# - Determine zonefile (by reading bind configuration)
# -
_found=false
declare -i _number=0
regex_zone="^[[:space:]]*zone[[:space:]]+\"$_tmp_host_name\""
regex_file="^[[:space:]]*file"
while IFS='' read -r line || [[ -n "$line" ]] ; do
if [[ $line =~ $regex_zone ]]; then
_found=true
fi
if $_found ; then
if [[ $line =~ $regex_file ]]; then
zone_file=`echo $line | awk '{print$2}'`
shopt -s extglob
if [[ $zone_file =~ \; ]]; then
zone_file=${zone_file%%*(\;)}
fi
if [[ $zone_file =~ ^\" ]]; then
zone_file=${zone_file##*(\")}
zone_file=${zone_file%%*(\")}
fi
shopt -u extglob
let number++
break
fi
fi
done < $ZONE_CONF_FILE
zone_file_dir=`dirname $zone_file`
if [[ $number -eq 0 ]] ; then
error "No Zonefile (master) found for domain \"$domain\" ."
exit 11
fi
echononl "\tBackup existing directory containg zonefiles.."
if [[ -d "$zone_file_dir" ]] ; then
cp -a $zone_file_dir ${zone_file_dir}.BAK.`date +%Y-%m-%d-%H%M`
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
echo ""
exit 99
fi
else
echo_failed
error "Zonefile directory not found for domain \"$domain\" ."
exit 99
fi
# - Determin new serial
# -
echononl "\tDetermin new serial.."
_failed=false
declare -i serial_new=`date +%Y%m%d01`
serial_cur=`grep -e "^\s*[0-9]\{10\}" $zone_file | grep serial | awk '{print$1}'`
if [[ $? -gt 0 ]] ; then
_failed=true
fi
while [ ! $serial_new -gt $serial_cur ]; do
let serial_new++
done
if [[ $? -gt 0 ]] ; then
_failed=true
fi
if $_failed ; then
echo_failed
error "Determin Serial failed!"
exit 12
else
echo_ok
fi
# - Replace serial with the new one
# -
echononl "\tIncrease serial for zone file \"`basename $zone_file`\".."
perl -i -n -p -e "s#^(\s*)\s$serial_cur(.*)#\1 $serial_new\2#" $zone_file > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
error "Increasing Serial failed!"
exit 13
fi
echo ""
echononl "\tCorrect Owner for $zone_file .."
chown $BIND_USER:$BIND_GROUP $zone_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
exit 99
fi
echononl "\tCorrect permissions on $zone_file .."
chmod 644 $zone_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
exit 99
fi
# - Reload Zone
# -
echononl "\tReloading zone \"$domain\".."
rndc reload $domain > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
echo_ok
info "Serial increased and zone reloaded ($domain)"
exit 0
else
echo_failed
error "Increasing Serial failed!"
exit 13
fi
echo
exit 99

351
tools-bind/bind_set_renew_tlsa.sh Executable file
View File

@ -0,0 +1,351 @@
#!/usr/bin/env bash
# - Replaces a TLSA Record
# -
# - Return (Exit) Codes:
# - success:
# - 0: TLSA record is up to date
# - 1: TLSA record replaced
# - 2: New TLSA record written
# - error:
# - 10: Invalid TLSA record given
# - 11: No zonefile for TLSA record found
# - 15: Hostname/Domain not supported
# - 20: Replacing record failed
# 21: Adding Record failed
# - 99: Fatal error
# -
# - example:
# - ./replace_dns_tlsa.sh _25._tcp.mail.initiativenserver.de. IN TLSA 3 1 1 aab3a46b387dd543ed8d...
## -- Variable definitions
## --
# - Bind configuration file containing zone definitions
# -
ZONE_CONF_FILE=/etc/bind/named.conf.local
BIND_USER=bind
BIND_GROUP=bind
## --
## -- End: Variable definitions
## ***
## *** Don't make changes after this line ***
## ***
## --- some functions
## ---
echononl(){
echo X\\c > /tmp/shprompt$$
if [ `wc -c /tmp/shprompt$$ | awk '{print $1}'` -eq 1 ]; then
echo -e -n "$*\\c" 1>&2
else
echo -e -n "$*" 1>&2
fi
rm /tmp/shprompt$$
}
warn (){
echo ""
echo -e "\t[ \033[33m\033[1mWarning\033[m ]: $*"
echo ""
}
info (){
echo ""
echo -e "\t[ \033[33m\033[1mInfo\033[m ]: $*"
echo ""
}
error(){
echo ""
echo -e "\t[ \033[31m\033[1mFehler\033[m ]: $*"
echo ""
}
echo_ok() {
echo -e "\033[75G[ \033[32mok\033[m ]"
}
echo_failed(){
echo -e "\033[75G[ \033[1;31mfailed\033[m ]"
}
echo_skipped() {
echo -e "\033[75G[ \033[33m\033[1mskipped\033[m ]"
}
backup_dir () {
dir_to_backup=$1
echononl "\tBackup existing directory \"$dir_to_backup\" .."
if [[ -d "$dir_to_backup" ]] ; then
cp -a $dir_to_backup ${dir_to_backup}.BAK.`date +%Y-%m-%d-%H%M`
if [[ $? -eq 0 ]]; then
echo_ok
else
echo_failed
echo ""
exit 99
fi
else
echo_failed
error "Directory \"$dir_to_backup\" not found. No Backup written!"
exit 99
fi
}
## ---
## --- END: functions
# - Parameter "check" can be used, to test whether this script
# - is accessable (e.g. from a script on a remote host)
# -
if [[ "$1" = "check" ]]; then
echo "\$1: $1"
exit 0
fi
# - Split given Record into an array
# -
declare -a record_arr=($@);
if [[ ${#record_arr[@]} -eq 7 ]]; then
record_name=${record_arr[0]}
record_ttl=""
record_type="${record_arr[1]} ${record_arr[2]} ${record_arr[3]} ${record_arr[4]} ${record_arr[5]}"
record_hash=${record_arr[6]}
elif [[ ${#record_arr[@]} -eq 8 ]]; then
record_name=${record_arr[0]}
record_ttl=${record_arr[1]}
record_type="${record_arr[2]} ${record_arr[3]} ${record_arr[4]} ${record_arr[5]} ${record_arr[6]}"
record_hash=${record_arr[7]}
else
error "Invalid TLSA record given!"
exit 10
fi
# - Split record_name, to get port,protocol,hostnaem,domain
# -
CUR_IFS=$IFS
IFS='\.'
declare -a split_record_name_arr=($record_name)
IFS=$CUR_IFS
_port=${split_record_name_arr[0]}
port=${_port##*_}
_protocol=${split_record_name_arr[1]}
protocol=${_protocol##*_}
hostname="${split_record_name_arr[2]}"
declare -i _index=3
while [[ $_index -lt ${#split_record_name_arr[@]} ]] ; do
hostname="${hostname}.${split_record_name_arr[$_index]}"
let _index++
done
# - Determin zone (domain)
# -
_failed=false
_hostname=$hostname
_tmp_hostname=$(echo ${_hostname//\./\\.})
while ! grep -e "$_tmp_hostname" $ZONE_CONF_FILE > /dev/null 2>&1 ; do
_hostname=${_hostname#*.}
_tmp_hostname=$(echo ${_hostname//\./\\.})
if [[ ! $_tmp_hostname =~ \. ]]; then
_failed=true
break
fi
done
if $_failed ; then
error "Given hostname/domain \"$hostname\" not supported by this nameserver!"
exit 15
else
domain=$_hostname
fi
# - Determine zonefile (by reading bind configuration)
# -
_found=false
declare -i _number=0
regex_zone="^[[:space:]]*zone[[:space:]]+\"$_tmp_hostname\""
regex_file="^[[:space:]]*file"
while IFS='' read -r line || [[ -n "$line" ]] ; do
if [[ $line =~ $regex_zone ]]; then
_found=true
fi
if $_found ; then
if [[ $line =~ $regex_file ]]; then
zone_file=`echo $line | awk '{print$2}'`
shopt -s extglob
if [[ $zone_file =~ \; ]]; then
zone_file=${zone_file%%*(\;)}
fi
if [[ $zone_file =~ ^\" ]]; then
zone_file=${zone_file##*(\")}
zone_file=${zone_file%%*(\")}
fi
shopt -u extglob
let number++
break
fi
fi
done < $ZONE_CONF_FILE
if [[ $number -eq 0 ]] ; then
error "No Zonefile (master) found for domain \"$domain\" ."
exit 11
fi
zone_file_dir=`dirname $zone_file`
# - Backup existing zone file directory
# -
# - Update/Add TLSA recotd if needed
# -
if grep -E "^$record_name.+$record_type" $zone_file > /dev/null 2>&1 ; then
if [[ -n "$record_ttl" ]]; then
search_string="^$record_name\\s+$record_ttl\\s+$record_type"
else
search_string="^$record_name\\s+$record_type"
fi
if grep -E "$search_string" $zone_file | grep $record_hash > /dev/null 2>&1 ; then
info "TLSA record is already up to date.."
echo ""
exit 0
else
_replac_string=${record_arr[@]}
# - Backup Zone directory
backup_dir $zone_file_dir
# - Replace TLSA Record
echononl "\tGoing to replace TLSA Record.."
perl -i -n -p -e "s#^${record_name}.+${record_type}.*#$_replac_string#" $zone_file
if [[ $? -eq 0 ]] ; then
echo_ok
echo ""
exit 1
else
echo_failed
echo ""
exit 20
fi
fi
else
warn "No Record for replacing fount in zonefile \"`basename $zone_file`\"!"
declare -i _count
search_string="^_${port}\._(tcp|udp)\.$hostname"
_count=`grep -Eo "$search_string" $zone_file | wc -l`
_tlsa_record_found=true
if [[ $_count -eq 0 ]]; then
search_string="^_[0-9]{1,4}\._(tcp|udp)\.$hostname"
_count=`grep -Eo "$search_string" $zone_file | wc -l`
if [[ $_count -eq 0 ]]; then
search_string="^_[0-9]{1,4}\._(tcp|udp).*TLSA"
_count=`grep -Eo "$search_string" $zone_file | wc -l`
if [[ $_count -eq 0 ]]; then
_tlsa_record_found=false
search_string="^[^;].+\s+IN\s+MX"
_count=`grep -Eo "$search_string" $zone_file | wc -l`
if [[ $_count -eq 0 ]]; then
search_string="^[^;].+\s+IN\s+NS"
_count=`grep -Eo "$search_string" $zone_file | wc -l`
if [[ $_count -eq 0 ]]; then
error "No place for adding a new TLSA record found. Check manually!"
exit 99
fi
fi
fi
fi
fi
CUR_IFS=$IFS
IFS=''
_tmpfile=`mktemp`
> $_tmpfile
# - backup zone directory
backup_dir $zone_file_dir
# - Add new TLSA record
echononl "\tAdd new TLSA record to zonefile \"\".."
while read -r line || [[ -n "$line" ]]; do
echo $line >> $_tmpfile
if echo "$line" | grep -E "$search_string" > /dev/null 2>&1 ; then
let _count--
fi
if [[ $_count -eq 0 ]]; then
echo "" >> $_tmpfile
if ! $_tlsa_record_found ; then
echo ";" >> $_tmpfile
echo "; DANE" >> $_tmpfile
echo ";" >> $_tmpfile
fi
echo "${record_arr[@]}" >> $_tmpfile
echo "" >> $_tmpfile
_count=-1
fi
done < "$zone_file"
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
rm $_tmpfile
exit 21
fi
IFS=$CUR_IFS
mv $_tmpfile $zone_file
# - Set Coorect Owner/Permission
echo ""
echononl "\tCorrect Owner for $zone_file .."
chown $BIND_USER:$BIND_GROUP $zone_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
exit 99
fi
echononl "\tCorrect permissions on $zone_file .."
chmod 644 $zone_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
exit 99
fi
echo ""
exit 2
fi
echo
exit 99