docker-mailserver/test/helper/tls.bash
Brennan Kinney 306592fcad tests: Adjusted files not directly related to tests
`tls.bash` helper was adapted to the new helper scripts location. The `setup.bash` helper saw a bugfix (expanding the array properly) and updates the container default config to configure for IPv4 explicitly.

The IPv4 default was added after recent Docker pushes and I saw weird IPv6 related errors in the logs.. now we're sure IPv4 is the default during tests.

Added functionality to check if a process is running:
- This change adds a helper function to check whether a program is running inside a container or not.
- This added the need for a function like `_run_in_container` but allowing for providing an explicit container name.
- Future PRs can use this helper function now to check whether a process is running or not. This was done for the tests of Fail2Ban, but can be used for other tests in the future as well.

---

chore: Restructured BATS flags in `Makefile`

The `Makefile` has seen a bit of a restructuring when it comes to flags:

1. The `MAKEFLAGS` variables is used by `make`, and allows for adding additional flags that can be used within in recursive calls (via `$(MAKE)`) too,  thus DRY approach.
2. The flags for calling BATS were adjusted. `--no-parallelize-within-files` has been added as well to ensure tests  _inside_ a single file are run sequentially.

`dms-test` prefix matching changed to expect a `_` suffix as a delimiter.

---

docs: Add a note regarding output from running tests in parallel
2023-01-06 16:50:09 +13:00

129 lines
4.7 KiB
Bash

#!/bin/bash
load "${REPOSITORY_ROOT}/test/helper/common"
# `_should_*` methods are useful for common high-level functionality.
# ? --------------------------------------------- Negotiate TLS
# For certs actually provisioned from LetsEncrypt the Root CA cert should not need to be provided,
# as it would already be available by default in `/etc/ssl/certs`, requiring only the cert chain (fullchain.pem).
function _should_succesfully_negotiate_tls() {
local FQDN=${1}
# shellcheck disable=SC2031
local CA_CERT=${2:-${TEST_CA_CERT}}
# Postfix and Dovecot are ready:
wait_for_smtp_port_in_container_to_respond "${CONTAINER_NAME}"
wait_for_tcp_port_in_container 993 "${CONTAINER_NAME}"
# Root CA cert should be present in the container:
assert docker exec "${CONTAINER_NAME}" [ -f "${CA_CERT}" ]
local PORTS=(25 587 465 143 993)
for PORT in "${PORTS[@]}"
do
_negotiate_tls "${FQDN}" "${PORT}"
done
}
# Basically runs commands like:
# docker exec "${TEST_NAME}" sh -c "timeout 1 openssl s_client -connect localhost:587 -starttls smtp -CAfile ${CA_CERT} 2>/dev/null | grep 'Verification'"
function _negotiate_tls() {
local FQDN=${1}
local PORT=${2}
# shellcheck disable=SC2031
local CA_CERT=${3:-${TEST_CA_CERT}}
local CMD_OPENSSL_VERIFY
CMD_OPENSSL_VERIFY=$(_generate_openssl_cmd "${PORT}")
# Should fail as a chain of trust is required to verify successfully:
run docker exec "${CONTAINER_NAME}" sh -c "${CMD_OPENSSL_VERIFY}"
assert_output --partial 'Verification error: unable to verify the first certificate'
# Provide the Root CA cert for successful verification:
CMD_OPENSSL_VERIFY=$(_generate_openssl_cmd "${PORT}" "-CAfile ${CA_CERT}")
run docker exec "${CONTAINER_NAME}" sh -c "${CMD_OPENSSL_VERIFY}"
assert_output --partial 'Verification: OK'
_should_support_fqdn_in_cert "${FQDN}" "${PORT}"
}
function _generate_openssl_cmd() {
# Using a HOST of `localhost` will not have issues with `/etc/hosts` matching,
# since hostname may not be match correctly in `/etc/hosts` during tests when checking cert validity.
local HOST='localhost'
local PORT=${1}
local EXTRA_ARGS=${2}
# `echo '' | openssl ...` is a common approach for providing input to `openssl` command which waits on input to exit.
# While the command is still successful it does result with `500 5.5.2 Error: bad syntax` being included in the response.
# `timeout 1` instead of the empty echo pipe approach seems to work better instead.
local CMD_OPENSSL="timeout 1 openssl s_client -connect ${HOST}:${PORT}"
# STARTTLS ports need to add a hint:
if [[ ${PORT} =~ ^(25|587)$ ]]
then
CMD_OPENSSL="${CMD_OPENSSL} -starttls smtp"
elif [[ ${PORT} == 143 ]]
then
CMD_OPENSSL="${CMD_OPENSSL} -starttls imap"
elif [[ ${PORT} == 110 ]]
then
CMD_OPENSSL="${CMD_OPENSSL} -starttls pop3"
fi
# `2>/dev/null` prevents openssl interleaving output to stderr that shouldn't be captured:
echo "${CMD_OPENSSL} ${EXTRA_ARGS} 2>/dev/null"
}
# ? --------------------------------------------- Verify FQDN
function _get_fqdn_match_query() {
local FQDN
FQDN=$(escape_fqdn "${1}")
# 3rd check is for wildcard support by replacing the 1st DNS label of the FQDN with a `*`,
# eg: `mail.example.test` will become `*.example.test` matching `DNS:*.example.test`.
echo "Subject: CN = ${FQDN}|DNS:${FQDN}|DNS:\*\.${FQDN#*.}"
}
function _should_support_fqdn_in_cert() {
_get_fqdns_for_cert "$@"
assert_output --regexp "$(_get_fqdn_match_query "${1}")"
}
function _should_not_support_fqdn_in_cert() {
_get_fqdns_for_cert "$@"
refute_output --regexp "$(_get_fqdn_match_query "${1}")"
}
# Escapes `*` and `.` so the FQDN literal can be used in regex queries
# `sed` will match those two chars and `\\&` says to prepend a `\` to the sed match (`&`)
function escape_fqdn() {
# shellcheck disable=SC2001
sed 's|[\*\.]|\\&|g' <<< "${1}"
}
function _get_fqdns_for_cert() {
local FQDN=${1}
local PORT=${2:-'25'}
# shellcheck disable=SC2031
local CA_CERT=${3:-${TEST_CA_CERT}}
# `-servername` is for SNI, where the port may be for a service that serves multiple certs,
# and needs a specific FQDN to return the correct cert. Such as a reverse-proxy.
local EXTRA_ARGS="-servername ${FQDN} -CAfile ${CA_CERT}"
local CMD_OPENSSL_VERIFY
# eg: "timeout 1 openssl s_client -connect localhost:25 -starttls smtp ${EXTRA_ARGS} 2>/dev/null"
CMD_OPENSSL_VERIFY=$(_generate_openssl_cmd "${PORT}" "${EXTRA_ARGS}")
# Takes the result of the openssl output to return the x509 certificate,
# We then check that for any matching FQDN entries:
# main == `Subject CN = <FQDN>`, sans == `DNS:<FQDN>`
local CMD_FILTER_FQDN="openssl x509 -noout -text | grep -E 'Subject: CN = |DNS:'"
run docker exec "${CONTAINER_NAME}" sh -c "${CMD_OPENSSL_VERIFY} | ${CMD_FILTER_FQDN}"
}