Today I gave up on my attempt to allow relay via SSL certificate fingerprints. Instead, I will use sasl auth.
Yesterday I wrote about my SMTP deliver test which broke when an SSL certificate was updated. Later that day, I finished writing scripts which delivered that fingerprint file to all hosts which needed it. Today, I abandoned that approach in favor of sasl. From the time I decided to use sasl to my first successful test was about an hour. I was already using sasl so I just needed to duplicate an existing solution.
But here are the scripts which I was using.
Generate the fingerprints file
This script runs on the same host as security/acme.sh and cert-shifter (part of sysutils/anvil). The file it creates is later rsync‘d to the certificate distribution webserver.
#!/bin/sh
CERT_SRC="/var/db/acme/certs"
CERT_DST_ROOT="/var/db/certs-for-rsync"
FINGERPRINT_DIR="${CERT_DST_ROOT}/cert-fingerprints"
FINGERPRINT_FILE="fingerprints"
FINGERPRINT_ALGORITHM=sha1
TMP="${CERT_DST_ROOT}/tmp"
CERTS=""
# items below here are not usually altered
CONFIG="/usr/local/etc/anvil/cert-fingerprints.conf"
if [ -f ${CONFIG} ]; then
. ${CONFIG}
fi
BASENAME="/usr/bin/basename"
CHMOD="/bin/chmod"
CP="/bin/cp"
DIFF="/usr/bin/diff"
LOGGER="/usr/bin/logger -t fingerprint-shifter"
OPENSSL="/usr/bin/openssl"
POSTALIAS="/usr/local/sbin/postalias"
SED="/usr/bin/sed"
${LOGGER} starting $0
temp_file=$(mktemp)
for cert in ${CERTS}
do
# XXX FINGERPRINT_ALGORITHM needs to be validated and the correct
fingerprint=`${OPENSSL} x509 -noout -fingerprint -${FINGERPRINT_ALGORITHM} -inform pem -in ${CERT_SRC}/${cert}/${cert}.cer | ${SED} 's/^.* Fingerprint=//g'`
echo ${fingerprint} ${cert} >> ${temp_file}
done
DST_FILE="${FINGERPRINT_DIR}/${FINGERPRINT_FILE}"
if [ -f ${DST_FILE} ]
then
if [ -r ${DST_FILE} ]
then
diff=`${DIFF} ${temp_file} ${DST_FILE}`
if [ "${diff}X" != "X" ]
then
${LOGGER} installing ${temp_file} to ${DST_FILE}
${CP} ${temp_file} ${DST_FILE}
${CHMOD} +r ${DST_FILE}
${POSTALIAS} ${DST_FILE}
else
${LOGGER} fingerprints unchanged. No update will occur.
fi
else
${LOGGER} FINGERPRINT_FILE is not readable: ${DST_FILE}
fi
else
${LOGGER} FINGERPRINT_FILE does not exist, creating it: ${DST_FILE}
${CP} ${temp_file} ${DST_FILE}
${CHMOD} +r ${DST_FILE}
${POSTALIAS} ${DST_FILE}
fi
${LOGGER} stopping $0
This is the corresponding configuration file:
$ cat /usr/local/etc/anvil/cert-fingerprints.conf FINGERPRINT_DIR="/var/db/certs-for-rsync/cert-fingerprints" FINGERPRINT_FILE="relay_clientcerts" FINGERPRINT_ALGORITHM=sha1 CERTS="cliff.int.example.org cliff.example.org"
Pulling the fingerprints
Once the fingerprint file is on the webserver, each client can get access to it.
This script runs on each host which needs the fingerprints file. This is very similar to the cert-puller script which comes with sysutils/anvil.
$ cat /usr/local/bin/finger-puller
#!/bin/sh
# the following variables can be overridden by the config file
FINGERPRINT_DEST="/usr/local/etc/postfix-config"
CERT_SERVER="https://certs.example.org/cert-fingerprints"
# a space separated list of fingerprint files which will be downloaded
MY_FINGERPRINTS="relay_clientcerts"
# a space separated list of services to restart
SERVICES="postfix"
DOWNLOAD_DIR="/var/db/anvil"
# be sure to specify the agument & have no spaces in between the single quotes
USER_AGENT="anvil-finger-puller"
#
# --mirror avoids replacement when identical
# --quiet avoids noise
#
FETCH="/usr/bin/fetch --mirror --quiet --user-agent='${USER_AGENT}'"
CURL="/usr/local/bin/curl --silent --user-agent '${USER_AGENT}' --remote-time"
WGET="/usr/local/bin/wget --quiet --user-agent='${USER_AGENT}'"
# items above can be overridden via the configuration file
# items below here are not usually altered
CONFIG="/usr/local/etc/anvil/finger-puller.conf"
if [ -f ${CONFIG} ]; then
. ${CONFIG}
fi
# initialize variables used below
SUDO_EXAMPLES=0
NEW_FINGERPRINTS_FOUND=0
# various commands used by this script
BASENAME="/usr/bin/basename"
CP="/bin/cp"
DIFF="/usr/bin/diff"
# These are the downlaoded fingerprints which we will consider for installation
FIND_FINGERPRINT_FILES="/usr/bin/find ${DOWNLOAD_DIR} -type f"
# if you want to disable logging, not recommend, put a hash before /usr
LOGGER="/usr/bin/logger -t finger-puller"
MV="/bin/mv"
SERVICE="/usr/sbin/service"
SUDO="/usr/local/bin/sudo"
TOUCH="/usr/bin/touch"
usage(){
echo "Usage: $0 [-s] [-h]"
exit 1
}
sudo_examples(){
# this function prints out commands which you can use with visudo to copy/paste
#
# NOTE: the code here must closely match that within install_new_fingerprints() & restart_services()
#
for fingerprint in ${MY_FINGERPRINTS}
do
echo "anvil ALL=(ALL) NOPASSWD:${CP} -a ${DOWNLOAD_DIR}/${fingerprint} ${FINGERPRINT_DEST}/${fingerprint}.tmp"
echo "anvil ALL=(ALL) NOPASSWD:${MV} ${FINGERPRINT_DEST}/${fingerprint}.tmp ${FINGERPRINT_DEST}/${fingerprint}"
done
for service in ${SERVICES}
do
case ${service} in
"apache24")
echo "anvil ALL=(ALL) NOPASSWD:${SERVICE} ${service} graceful"
;;
"dovecot"|"postfix"|"nginx")
echo "anvil ALL=(ALL) NOPASSWD:${SERVICE} ${service} restart"
;;
esac
done
}
sanity_checks(){
if [ ! -r "${FINGERPRINT_DEST}" -o ! -d "${FINGERPRINT_DEST}" ]; then
${LOGGER} "${FINGERPRINT_DEST}" is NOT readable and a directory
${LOGGER} $0 exits
exit 2
fi
}
fetch_new_fingerprints(){
# first, we fetch the fingerprints are looking for.
${LOGGER} fetching into ${DOWNLOAD_DIR}
for fingerprint in ${MY_FINGERPRINTS}
do
${LOGGER} checking for ${fingerprint}
case ${FETCH_TOOL} in
"wget")
${LOGGER} running: ${WGET} --output-document=${DOWNLOAD_DIR}/${fingerprint} ${CERT_SERVER}/${fingerprint}
${WGET} --output-document=${DOWNLOAD_DIR}/${fingerprint} ${CERT_SERVER}/${fingerprint}
;;
"curl")
${LOGGER} running: ${CURL} -o ${DOWNLOAD_DIR}/${fingerprint} ${CERT_SERVER}/${fingerprint}
${CURL} -o ${DOWNLOAD_DIR}/${fingerprint} ${CERT_SERVER}/${fingerprint}
;;
*)
${LOGGER} running: ${FETCH} -o ${DOWNLOAD_DIR} ${CERT_SERVER}/${fingerprint}
${FETCH} -o ${DOWNLOAD_DIR} ${CERT_SERVER}/${fingerprint}
;;
esac
RESULT=$?
if [ "${RESULT}" != "0" ]; then
${LOGGER} "error '${RESULT}' on fetch - perhaps the remote file does not exist."
fi
done
}
install_new_fingerprints(){
${LOGGER} looking for fingerprint files: ${FIND_FINGERPRINT_FILES}
# for each new thing we find, move it over to the right place
NEW_FILES=`${FIND_FINGERPRINT_FILES}`
for new_file in ${NEW_FILES}
do
filename=`${BASENAME} ${new_file}`
${LOGGER} validating ${filename}
# that fingerprint may not be installed
if [ -f ${FINGERPRINT_DEST}/${filename} ]; then
diff=`${DIFF} ${new_file} ${FINGERPRINT_DEST}/${filename}`
else
diff="x";
${LOGGER} ${filename} does not exist and will be installed
fi
# only install if the fingerprints are different.
if [ "${diff}X" != "X" ]; then
${LOGGER} installing ${filename}
#
# NOTE: the code here must closely match that within sudo_examples() & restart_services()
#
${SUDO} ${CP} -a ${new_file} ${FINGERPRINT_DEST}/${filename}.tmp
${SUDO} ${MV} ${FINGERPRINT_DEST}/${filename}.tmp ${FINGERPRINT_DEST}/${filename}
NEW_FINGERPRINTS_FOUND=1
fi
done
}
restart_services(){
#
# NOTE: the code here must closely match that within sudo_examples() & install_new_fingerprints()
#
for service in ${SERVICES}
do
case ${service} in
"apache24")
${LOGGER} doing a graceful on ${service}
${SUDO} ${SERVICE} ${service} graceful
;;
# it might be better if we do a reload.
# will that be sufficient?
"dovecot"|"postfix"|"nginx")
${LOGGER} restarting ${service}
${SUDO} ${SERVICE} ${service} restart
;;
*)
${LOGGER} "Unknown service requested in $0: ${service}"
;;
esac
done
}
#
# main code starts here
#
while getopts "hs" opt; do
case $opt in
s)
SUDO_EXAMPLES=1
shift
;;
h)
usage
shift
;;
* )
usage
;;
esac
done
#
# give them samples for visudo
#
if [ ${SUDO_EXAMPLES} == "1" ]; then
sudo_examples
exit 1
fi
${LOGGER} starting $0
sanity_checks
fetch_new_fingerprints
install_new_fingerprints
if [ ${NEW_FINGERPRINTS_FOUND} == "1" ]; then
restart_services
else
${LOGGER} no new fingerprints found
fi
${LOGGER} stopping $0
This is the corresponding configuration file:
$ cat /usr/local/etc/anvil/finger-puller.conf DOWNLOAD_DIR="/var/db/anvil/fingerprints" CERT_SERVER="https://certs.example.org/cert-fingerprints" MY_FINGERPRINTS="relay_clientcerts relay_clientcerts.db" SERVICES="postfix"
What am I doing instead?
Instead of this approach, I have each sending Postfix server doing something like this.
Client side
On the sending servers, I have this in main.cf:
smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/usr/local/etc/postfix/sasl_passwd smtp_sasl_type = cyrus
sasl_passwd contains lines like this:
[receivinghost.example.org]:25 username:password
After amending that file, be sure to run postmap sasl_passwd.
See the Postfix SASL Howto for more detail.
NOTE: I have cyrus-sasl installed, but there is nothing cyrus-specific running.
Server side
On the receiving server, I have this:
smtpd_sasl_auth_enable = yes smtpd_sasl_local_domain = $myhostname smtpd_sasl_path = private/auth smtpd_sasl_type = dovecot
Configuring dovecot is outside scope for this post but here are two things I found:
* A post I made to the Postfix Users mailing list.
* An old FreeBSDDiary article from 2007 when I was more concerned about self-generated certs. Today, we have Let’s Encrypt.
Hope that helps.











