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.