diff --git a/docs/debian-conf.d/main/00_vexim_listmacrosdefs b/docs/debian-conf.d/main/00_vexim_listmacrosdefs index 850ea8de..15d82603 100644 --- a/docs/debian-conf.d/main/00_vexim_listmacrosdefs +++ b/docs/debian-conf.d/main/00_vexim_listmacrosdefs @@ -22,6 +22,11 @@ MAIN_RELAY_TO_DOMAINS = MAIN_RELAY_TO_DOMAINS : ${lookup mysql{VEXIM_RELAY_DOMAI # enable TLS #MAIN_TLS_ENABLE = true +# CHANGE the fallback certificate 'DEFAULT' at the end of both lines! +# This will be used if no SNI value is provided by the connecting client +MAIN_TLS_CERTIFICATE = ${if exists{/etc/letsencrypt/live/${tls_sni}/fullchain.pem}{/etc/letsencrypt/live/${tls_sni}/fullchain.pem}{/etc/letsencrypt/live/DEFAULT/fullchain.pem}} +MAIN_TLS_PRIVATEKEY = ${if exists{/etc/letsencrypt/live/${tls_sni}/privkey.pem}{/etc/letsencrypt/live/${tls_sni}/privkey.pem}{/etc/letsencrypt/live/DEFAULT/privkey.pem}} + # enable av scanner #av_scanner = clamd:/var/run/clamav/clamd.ctl diff --git a/docs/mon-certificates b/docs/mon-certificates new file mode 100644 index 00000000..004efef2 --- /dev/null +++ b/docs/mon-certificates @@ -0,0 +1,288 @@ +#!/bin/bash +########### +# Vexim, mon-certificates +# by Markus Gschwendt +# +# This script will query the vexim database.domains table +# and try to create/renew certificates for all domains +# Certbot will listen on the IP resolved by DNS +# and the ACME server will use the same IP for validating +########### + +# whitespace separated list of subdomain prefixes +# this prefixes will be added to all domains +# e.g. with "mail smtp", for the domain example.com +# we will request the certificates mail.example.com +# and smtp.example.com +# additionally certificates for all DNS MX records of example.com +# will be requested automagically +# no certificate for example.com itself will be requested +SUBDOMAIN_LIST_DOVECOT="mail" +SUBDOMAIN_LIST_EXIM="smtp" +SUBDOMAIN_LIST_WWW="vexim" + +# if you want to use a dedicated DNS resolver put it here +DNSSERVER="ns1.runout.at" +# type of IP address should be used "AAAA" or "A" +DNSTYPE="AAAA" + +# If you're behind a nat or you have some kind of port forwarding +# you can set a global IP for certbot here. +# certbot will listen on this IP but the ACME server will use the IP from DNS +DOMAIN_IP_OVERRIDE="" + +# create a fallback certificate for this domain +# and put all other domains in the SAN field +# eg. put the main fqdn of your server here +#FALLBACK_DOMAIN=$(cat /etc/mailname) +FALLBACK_DOMAIN="mx.example.com" + +# some options for letsencrypt certificates deployed by certbot +# you will need the certbot package from stretch-backports +CERTBOT_OPTS="certonly -n --standalone --preferred-challenges http --rsa-key-size 4096" +#CERTBOT_OPTS+=" -q" + +# enable for certbot debugging +#CERTBOT_OPTS+=" -v" +#CERTBOT_TESTCERT="--test-cert" +#CERTBOT_DRYRUN="--dry-run" + +# configure vexim DB here +DB_HOST="mysql-server" +DB_PORT="3306" +DB_DB="vexim2-db" +DB_USER="vexim2-db-user" +DB_PASS="vexim2-db-password" +DB_OPT="" + +# set this if a config file for dovecot should be created +# this will also reload dovecot +# will be used only if the dovecot binary is executable +DOVECOT_SSL="/etc/dovecot/conf.d/11-ssl-domlist.conf" +DOVECOT_BIN="/usr/sbin/dovecot" + +# set this if a config file for dovecot should be created +# this will also reload dovecot +# will be used only if the dovecot binary is executable +EXIM_SSL="/etc/exim4/ssl.conf" +EXIM_BIN="/usr/sbin/exim4" + +# set this if a config file for nginx/vexim should be created +# this will also reload nginx +# do not forget to enable this config with a symlink in sites-enabled +# will be used only if the nginx binary is executable +#NGINX_SSL="/etc/nginx/sites-available/vexim" +#NGINX_BIN="/usr/sbin/nginx" + +# settings for the nginx config file +# where should nginx pass the request. e.g.: +# proxy_pass http://mail1; +# uwsgi_pass 0.0.0.0:8000; +NGINX_PASS="proxy_pass http://mail1;" +# a file where additional parameters for proxy configurations will be +NGINX_PROXYPARAMS="include /etc/nginx/proxy_params;" + +# logging +LOG_FILE="/var/log/letsencrypt/mon-certificates.log" + + +########## +# end of config +########## + +rm -f ${LOG_FILE}.* +# keep one oldlogfile +mv ${LOG_FILE} ${LOG_FILE}.1 2>/dev/null +echo -e "##### ${0} logfile #####\n##### $(date +"%Y%m%d %H:%M:%S") #####" >> "${LOG_FILE}" + +DB_OPT+=" --skip-column-names" +DB_MYSQL="mysql -u ${DB_USER} -p${DB_PASS} -h ${DB_HOST} -P ${DB_PORT} ${DB_OPT} ${DB_DB}" +DB_QUERY_DOMAINS="SELECT \`domain\` FROM \`domains\`" +if [[ ! -z "${DNSSERVER}" ]]; then + DNSSERVER="@$(dig +short -t ${DNSTYPE} ${DNSSERVER})" +fi +echo -e "DNS RECURSOR: ${DNSSERVER}" >> "${LOG_FILE}" + +if [ ! -z "${DOVECOT_SSL}" ] && [ -x "${DOVECOT_BIN}" ]; then + mv -f ${DOVECOT_SSL} ${DOVECOT_SSL}.1 2>/dev/null + SUBDOMAIN_LIST_DOVECOT="$(echo -e "${SUBDOMAIN_LIST_DOVECOT}" | sed "s/\s/\n/g" | sed '/^[[:space:]]*$/d')" + echo -e "DOVECOT config file: ${DOVECOT_SSL}" >> "${LOG_FILE}" +else # unset DOVECOT_SSL if no executable binary is found + DOVECOT_SSL= + SUBDOMAIN_LIST_DOVECOT="" +fi + +if [ ! -z "${EXIM_SSL}" ] && [ -x "${EXIM_BIN}" ]; then + mv -f ${EXIM_SSL} ${DOVECOT_SSL}.1 2>/dev/null + SUBDOMAIN_LIST_EXIM="$(echo -e "${SUBDOMAIN_LIST_EXIM}" | sed "s/\s/\n/g" | sed '/^[[:space:]]*$/d')" + echo -e "EXIM config file: ${EXIM_SSL}" >> "${LOG_FILE}" +else # unset EXIM_SSL if no executable binary is found + EXIM_SSL= + SUBDOMAIN_LIST_EXIM="" +fi + +if [ ! -z "${NGINX_SSL}" ] && [ -x "${NGINX_BIN}" ]; then + mv -f ${NGINX_SSL} ${NGINX_SSL}.1 2>/dev/null + SUBDOMAIN_LIST_WWW="$(echo -e "${SUBDOMAIN_LIST_WWW}" | sed "s/\s/\n/g" | sed '/^[[:space:]]*$/d')" + echo -e "NGINX config file: ${NGNX_SSL}" >> "${LOG_FILE}" +else # unset NGINX_SSL if no executable binary is found + NGINX_SSL= + SUBDOMAIN_LIST_WWW="" +fi + +SUBDOMAIN_LIST="$(echo -e "${SUBDOMAIN_LIST_DOVECOT}\n${SUBDOMAIN_LIST_EXIM}\n${SUBDOMAIN_LIST_WWW}" | sort -u | sed '/^[[:space:]]*$/d')" +echo -e "##### mail:\n${SUBDOMAIN_LIST_DOVECOT}\n\n${SUBDOMAIN_LIST_EXIM}\n\n##### www:\n${SUBDOMAIN_LIST_WWW}\n\n##### all:\n${SUBDOMAIN_LIST}\n" >> "${LOG_FILE}.SUBDOMAIN.log" + +DOMAINS_PROCESSED="" +DOMAINS_WRONGIP="" +DOMAINS_DUPLICATES="" + + +########## +# retreive domains from mysql +########## + +getdomlist () { + DOMAIN_LIST=$(${DB_MYSQL} <<< ${DB_QUERY_DOMAINS} | sed '/^[[:space:]]*$/d') + #echo -e "${DOMAIN_LIST}" +} + +# get the IP for a domain from DNS or set it to the defined override +getdomip () { + DOMAIN_=${1} + if [[ -z "${DOMAIN_IP_OVERRIDE}" ]]; then + echo "$(dig +short -t ${DNSTYPE} ${DOMAIN_} ${DNSSERVER} | tail -n1)" + else + echo "${DOMAIN_IP_OVERRIDE}" + fi +} + +########## +# check certificate +########## + +check_cert () { + DOMAIN_=${1} + # if we did not already process this domain + if [[ -z "$(echo -e "${DOMAINS_PROCESSED}" | grep "${DOMAIN_}")" ]]; then + DOMAIN_IP_="$(getdomip ${1})" + + # get cert only if there is a AAAA/A record in dns + # and this IP is in our network config + if [[ ! -z ${DOMAIN_IP_} ]] && \ + [[ ! -z "$(ip -6 addr show | grep "${DOMAIN_IP_}")" ]]; then + DOMAINS_PROCESSED+="$(echo -e "${DOMAIN_} ${DOMAIN_IP_}")\n" + echo -e "\n##### $(date +"%Y%m%d %H:%M:%S") #####\n${DOMAIN_} ${DOMAIN_IP_}" >> "${LOG_FILE}" + certbot ${CERTBOT_OPTS} ${CERTBOT_TESTCERT} ${CERTBOT_DRYRUN} -d ${DOMAIN_} --http-01-address ${DOMAIN_IP_} >> "${LOG_FILE}" 2>&1 + if [ -e /etc/letsencrypt/live/${DOMAIN_}/fullchain.pem ] && \ + [ -e /etc/letsencrypt/live/${DOMAIN_}/privkey.pem ]; then + + # DOVECOT + if [[ ! -z "${DOVECOT_SSL}" ]] && \ + [[ ! -z "$(echo -e "${SUBDOMAIN_LIST_DOVECOT}" | grep -e "^$(echo "${DOMAIN_}" | sed 's/\..*//')$")" ]]; then + # write to dovecot config file + echo -e "local_name ${DOMAIN_} {" >> "${DOVECOT_SSL}.tmp2" + echo -e " ssl_cert = > "${DOVECOT_SSL}.tmp2" + echo -e " ssl_key = > "${DOVECOT_SSL}.tmp2" + echo -e "}\n" >> "${DOVECOT_SSL}.tmp2" + fi + + # NGINX + if [[ ! -z "${NGINX_SSL}" ]] && \ + [[ ! -z "$(echo -e "${SUBDOMAIN_LIST_WEB}" | grep -e "^$(echo "${DOMAIN_}" | sed 's/\..*//')$")" ]]; then + if [[ -z "$(echo -e "${DOMAIN_IP_}" | grep ':')" ]]; then + DOMAIN_IP6_="::" + else + DOMAIN_IP6_="${DOMAIN_IP_}" + fi + # write to nginx config file + echo -e "server {" >> "${NGINX_SSL}" + echo -e " listen 443 ssl http2;" >> "${NGINX_SSL}" + echo -e " listen [${DOMAIN_IP6_}]:443 ssl http2;" >> "${NGINX_SSL}" + echo -e " server_name ${DOMAIN_};" >> "${NGINX_SSL}" + echo -e " ssl_certificate /etc/letsencrypt/live/${DOMAIN_}/fullchain.pem;" >> "${NGINX_SSL}" + echo -e " ssl_certificate_key /etc/letsencrypt/live/${DOMAIN_}/privkey.pem;" >> "${NGINX_SSL}" + echo -e " location / {" >> "${NGINX_SSL}" + echo -e " ${NGINX_PASS}" >> "${NGINX_SSL}" + echo -e " ${NGINX_PROXYPARAMS}" >> "${NGINX_SSL}" + echo -e " }" >> "${NGINX_SSL}" + echo -e "}\n" >> "${NGINX_SSL}" + fi + + fi + else + DOMAINS_WRONGIP+="$(echo -e "${DOMAIN_} ${DOMAIN_IP_}")\n" + fi + + else + DOMAINS_DUPLICATES+="$(echo -e "${DOMAIN_} ${DOMAIN_IP_}")\n" + fi +} + + +getdomlist + +# DOMAINS_PROCESSED is used at the end to create a certificate with all domains in the SAN field as a fallback +# putting the FALLBACK_DOMAIN in here it will be skipped for now +FALLBACK_IP="$(getdomip ${FALLBACK_DOMAIN})" + +DOMAINS_PROCESSED="${FALLBACK_DOMAIN} ${FALLBACK_IP}\n" + +while read DOMAIN REST; do + + while read SUBDOMAIN REST; do + echo -e "\n##### $(date +"%Y%m%d %H:%M:%S") #####\n${DOMAIN} SUBDOMAIN: ${SUBDOMAIN}.${DOMAIN%.}" >> "${LOG_FILE}.SUBDOMAIN.log" + check_cert ${SUBDOMAIN}.${DOMAIN%.} + done <<< ${SUBDOMAIN_LIST} + + while read DOMAIN_MX REST; do + echo -e "\n##### $(date +"%Y%m%d %H:%M:%S") #####\n${DOMAIN} MX: ${DOMAIN_MX%.}" >> "${LOG_FILE}.MX.log" + check_cert ${DOMAIN_MX%.} + done <<< "$(dig +short -t mx ${DOMAIN} ${DNSSERVER} | cut -d " " -f2 | sed '/^[[:space:]]*\.*$/d')" + +done <<< ${DOMAIN_LIST} + +# create certificate with all domains in the SAN field as fallback +DOMAIN_SAN="$(echo -e ${DOMAINS_PROCESSED} | cut -d " " -f 1 | sed "/^$/d" | sed "s/^/-d /" | tr '\n' ' ')" +#echo -e "${DOMAIN_SAN}" +echo -e "\n##### $(date +"%Y%m%d %H:%M:%S") #####\n${DOMAIN_SAN} ${FALLBACK_IP}" >> "${LOG_FILE}" +certbot ${CERTBOT_OPTS} ${CERTBOT_TESTCERT} ${CERTBOT_DRYRUN} --cert-name ${FALLBACK_DOMAIN} ${DOMAIN_SAN} --http-01-address ${FALLBACK_IP} >> "${LOG_FILE}" 2>&1 + + +# write to dovecot config file and reload dovecot +if [[ ! -z "${DOVECOT_SSL}" ]]; then + if [ -e /etc/letsencrypt/live/${FALLBACK_DOMAIN}/fullchain.pem ] && \ + [ -e /etc/letsencrypt/live/${FALLBACK_DOMAIN}/privkey.pem ]; then + echo -e "ssl_cert = > "${DOVECOT_SSL}.tmp1" + echo -e "ssl_key = > "${DOVECOT_SSL}.tmp1" + else + echo -e "\n##### WARNING #####\n" >> "${LOG_FILE}" + echo -e " No fallback certificate for Dovecot found!\n" >> "${LOG_FILE}" + echo -e " /etc/letsencrypt/live/${FALLBACK_DOMAIN}/fullchain.pem\n" >> "${LOG_FILE}" + fi + cat "${DOVECOT_SSL}.tmp1" "${DOVECOT_SSL}.tmp2" > "${DOVECOT_SSL}" + rm -f "${DOVECOT_SSL}.tmp1" + rm -f "${DOVECOT_SSL}.tmp2" + echo -e "\n##### $(date +"%Y%m%d %H:%M:%S") #####\n" >> "${LOG_FILE}" + service dovecot reload > /dev/null 2>&1 + echo -e "service dovecot reload" >> "${LOG_FILE}" +fi + +# reload nginx +if [[ ! -z "${NGINX_SSL}" ]]; then + echo -e "\n##### $(date +"%Y%m%d %H:%M:%S") #####\n" >> "${LOG_FILE}" + service nginx reload + echo -e "service nginx reload" >> "${LOG_FILE}" +fi + +echo -e "\n##### $(date +"%Y%m%d %H:%M:%S") #####\n" >> "${LOG_FILE}" + +# some more logging +DOMAINS_PROCESSED="$(echo -e "${DOMAINS_PROCESSED}" | sort -u | sed '/^[[:space:]]*$/d')" +DOMAINS_WRONGIP="$(echo -e "${DOMAINS_WRONGIP}" | sort -u | sed '/^[[:space:]]*$/d')" +DOMAINS_DUPLICATES="$(echo -e "${DOMAINS_DUPLICATES}" | sort -u | sed '/^[[:space:]]*$/d')" +echo -e "processed:\n${DOMAINS_PROCESSED}\n" >> "${LOG_FILE}" +echo -e "no/unconfigured IP:\n${DOMAINS_WRONGIP}\n" >> "${LOG_FILE}" +echo -e "already processed before:\n${DOMAINS_DUPLICATES}\n" >> "${LOG_FILE}" + +exit 0