tests: Adjust parallel tests

- The usual serial to parallel test conversion to utilize the `setup.bash` common setup structure, and adding a `TEST_PREFIX` var for each test case to leverage.
- Standardize on parallel test naming conventions for variables / values.
- More consistent use of `bash -c` instead of `/bin/bash -c` or `/bin/sh -c`.
- Using the `_run_in_container` helper instead of `run docker exec ${CONTAINER_NAME}`.
- Updates tests to use the `check_if_process_is_running` helper.

---

chore: Revise inline docs for the `ssl_letsencrypt` test

- Moves the override to be in closer proximity to the `initial_setup` call, and better communicates the intent to override.
- Removes top comment block that is no longer providing value or correct information to maintainers.
- Revised `acme.json` test case inline doc comments.
This commit is contained in:
Brennan Kinney 2023-01-03 18:58:09 +13:00
parent 306592fcad
commit 2ec6c4abc0
17 changed files with 623 additions and 591 deletions

View file

@ -30,10 +30,10 @@ function _default_teardown() {
# @param ${1} program name [REQUIRED]
# @param ${2} container name [IF UNSET: ${CONTAINER_NAME}]
function _check_if_process_is_running() {
function check_if_process_is_running() {
local PROGRAM_NAME=${1:?Program name must be provided explicitly}
local CONTAINER_NAME=${2:-${CONTAINER_NAME}}
_run_in_container_explicit "${CONTAINER_NAME}" pgrep "${PROGRAM_NAME}"
docker exec "${CONTAINER_NAME}" pgrep "${PROGRAM_NAME}"
}
# -------------------------------------------------------------------

View file

@ -1,15 +1,15 @@
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
export TEST_NAME_PREFIX='default relay host:'
export CONTAINER_NAME='dms-test-default_relay_host'
TEST_NAME_PREFIX='[Relay] ENV:'
CONTAINER_NAME='dms-test_default-relay-host'
function setup_file() {
init_with_defaults
local CUSTOM_SETUP_ARGUMENTS=(
--env DEFAULT_RELAY_HOST=default.relay.host.invalid:25 \
--env PERMIT_DOCKER=host \
--env DEFAULT_RELAY_HOST=default.relay.host.invalid:25
--env PERMIT_DOCKER=host
)
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
@ -17,7 +17,7 @@ function setup_file() {
function teardown_file() { _default_teardown ; }
@test "${TEST_NAME_PREFIX} default relay host is added to main.cf" {
@test "${TEST_NAME_PREFIX} 'DEFAULT_RELAY_HOST' should configure 'main.cf:relayhost'" {
_run_in_container bash -c 'grep -e "^relayhost =" /etc/postfix/main.cf'
assert_output 'relayhost = default.relay.host.invalid:25'
}

View file

@ -2,7 +2,7 @@ load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
TEST_NAME_PREFIX='ClamAV:'
CONTAINER_NAME='dms-test-clamav'
CONTAINER_NAME='dms-test_clamav'
function setup_file() {
init_with_defaults
@ -35,7 +35,7 @@ function setup_file() {
function teardown_file() { _default_teardown ; }
@test "${TEST_NAME_PREFIX} process clamd is running" {
_run_in_container bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
run check_if_process_is_running 'clamd'
assert_success
}
@ -66,6 +66,8 @@ function teardown_file() { _default_teardown ; }
}
@test "${TEST_NAME_PREFIX} process clamd restarts when killed" {
_run_in_container bash -c "pkill clamd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
_run_in_container pkill 'clamd'
assert_success
run_until_success_or_timeout 10 check_if_process_is_running 'clamd'
}

View file

@ -1,48 +1,50 @@
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
TEST_NAME_PREFIX='[ClamAV + SA] (disabled):'
CONTAINER_NAME='dms-test_clamav-spamassasin_disabled'
docker run --rm -d --name mail_disabled_clamav_spamassassin \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_CLAMAV=0 \
-e ENABLE_SPAMASSASSIN=0 \
-e AMAVIS_LOGLEVEL=2 \
-h mail.my-domain.com -t "${NAME}"
function setup_file() {
init_with_defaults
# TODO: find a better way to know when we have waited long enough
# for ClamAV to should have come up, if it were enabled
wait_for_smtp_port_in_container mail_disabled_clamav_spamassassin
docker exec mail_disabled_clamav_spamassassin /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_AMAVIS=1
--env ENABLE_CLAMAV=0
--env ENABLE_SPAMASSASSIN=0
--env AMAVIS_LOGLEVEL=2
)
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
_run_in_container bash -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
assert_success
wait_for_empty_mail_queue_in_container "${CONTAINER_NAME}"
}
teardown_file() {
docker rm -f mail_disabled_clamav_spamassassin
}
function teardown_file() { _default_teardown ; }
@test "checking process: ClamAV (ClamAV disabled by ENABLED_CLAMAV=0)" {
run docker exec mail_disabled_clamav_spamassassin /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
@test "${TEST_NAME_PREFIX} ClamAV - should be disabled by ENV 'ENABLED_CLAMAV=0'" {
run check_if_process_is_running 'clamd'
assert_failure
}
@test "checking spamassassin: should not be listed in amavis when disabled" {
run docker exec mail_disabled_clamav_spamassassin /bin/sh -c "grep -i 'ANTI-SPAM-SA code' /var/log/mail/mail.log | grep 'NOT loaded'"
@test "${TEST_NAME_PREFIX} SA - Amavis integration should not be active" {
_run_in_container /bin/sh -c "grep -i 'ANTI-SPAM-SA code' /var/log/mail/mail.log | grep 'NOT loaded'"
assert_success
}
@test "checking ClamAV: should not be listed in amavis when disabled" {
run docker exec mail_disabled_clamav_spamassassin grep -i 'Found secondary av scanner ClamAV-clamscan' /var/log/mail/mail.log
@test "${TEST_NAME_PREFIX} ClamAV - Amavis integration should not be active" {
_run_in_container grep -i 'Found secondary av scanner ClamAV-clamscan' /var/log/mail/mail.log
assert_failure
}
@test "checking ClamAV: should not be called when disabled" {
run docker exec mail_disabled_clamav_spamassassin grep -i 'connect to /var/run/clamav/clamd.ctl failed' /var/log/mail/mail.log
@test "${TEST_NAME_PREFIX} SA should not be called" {
_run_in_container grep -i 'connect to /var/run/clamav/clamd.ctl failed' /var/log/mail/mail.log
assert_failure
}
@test "checking restart of process: ClamAV (ClamAV disabled by ENABLED_CLAMAV=0)" {
run docker exec mail_disabled_clamav_spamassassin /bin/bash -c "pkill -f clamd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
@test "${TEST_NAME_PREFIX} ClamAV process should not be restarted when killed" {
_run_in_container /bin/bash -c "pkill -f clamd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/clamd'"
assert_failure
}

View file

@ -1,61 +1,61 @@
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
CONTAINER="mail_dnsbl_enabled"
CONTAINER2="mail_dnsbl_disabled"
TEST_NAME_PREFIX='DNSBLs:'
CONTAINER1_NAME='dms-test_dnsbl_enabled'
CONTAINER2_NAME='dms-test_dnsbl_disabled'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . "${CONTAINER}")
local CONTAINER_NAME=${CONTAINER1_NAME}
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_DNSBL=1
)
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
docker run --rm -d --name "${CONTAINER}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-e ENABLE_DNSBL=1 \
-h mail.my-domain.com \
-t "${NAME}"
local CONTAINER_NAME=${CONTAINER2_NAME}
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_DNSBL=0
)
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
}
docker run --rm -d --name "${CONTAINER2}" \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-e ENABLE_DNSBL=0 \
-h mail.my-domain.com \
-t "${NAME}"
wait_for_smtp_port_in_container "${CONTAINER}"
wait_for_smtp_port_in_container "${CONTAINER2}"
function teardown_file() {
docker rm -f "${CONTAINER1_NAME}" "${CONTAINER2_NAME}"
}
# ENABLE_DNSBL=1
@test "checking enabled postfix DNS block list zen.spamhaus.org" {
run docker exec "${CONTAINER}" postconf smtpd_recipient_restrictions
@test "${TEST_NAME_PREFIX} (enabled) Postfix DNS block list zen.spamhaus.org" {
run docker exec "${CONTAINER1_NAME}" postconf smtpd_recipient_restrictions
assert_output --partial 'reject_rbl_client zen.spamhaus.org'
}
@test "checking enabled postscreen DNS block lists --> postscreen_dnsbl_action" {
run docker exec "${CONTAINER}" postconf postscreen_dnsbl_action
@test "${TEST_NAME_PREFIX} (enabled) Postscreen DNS block lists -> postscreen_dnsbl_action" {
run docker exec "${CONTAINER1_NAME}" postconf postscreen_dnsbl_action
assert_output 'postscreen_dnsbl_action = enforce'
}
@test "checking enabled postscreen DNS block lists --> postscreen_dnsbl_sites" {
run docker exec "${CONTAINER}" postconf postscreen_dnsbl_sites
@test "${TEST_NAME_PREFIX} (enabled) Postscreen DNS block lists -> postscreen_dnsbl_sites" {
run docker exec "${CONTAINER1_NAME}" postconf postscreen_dnsbl_sites
assert_output 'postscreen_dnsbl_sites = zen.spamhaus.org=127.0.0.[2..11]*3 bl.mailspike.net=127.0.0.[2;14;13;12;11;10] b.barracudacentral.org*2 bl.spameatingmonkey.net=127.0.0.2 dnsbl.sorbs.net psbl.surriel.com list.dnswl.org=127.0.[0..255].0*-2 list.dnswl.org=127.0.[0..255].1*-3 list.dnswl.org=127.0.[0..255].[2..3]*-4'
}
# ENABLE_DNSBL=0
@test "checking disabled postfix DNS block list zen.spamhaus.org" {
run docker exec "${CONTAINER2}" postconf smtpd_recipient_restrictions
@test "${TEST_NAME_PREFIX} (disabled) Postfix DNS block list zen.spamhaus.org" {
run docker exec "${CONTAINER2_NAME}" postconf smtpd_recipient_restrictions
refute_output --partial 'reject_rbl_client zen.spamhaus.org'
}
@test "checking disabled postscreen DNS block lists --> postscreen_dnsbl_action" {
run docker exec "${CONTAINER2}" postconf postscreen_dnsbl_action
@test "${TEST_NAME_PREFIX} (disabled) Postscreen DNS block lists -> postscreen_dnsbl_action" {
run docker exec "${CONTAINER2_NAME}" postconf postscreen_dnsbl_action
assert_output 'postscreen_dnsbl_action = ignore'
}
@test "checking disabled postscreen DNS block lists --> postscreen_dnsbl_sites" {
run docker exec "${CONTAINER2}" postconf postscreen_dnsbl_sites
@test "${TEST_NAME_PREFIX} (disabled) Postscreen DNS block lists -> postscreen_dnsbl_sites" {
run docker exec "${CONTAINER2_NAME}" postconf postscreen_dnsbl_sites
assert_output 'postscreen_dnsbl_sites ='
}
# cleanup
function teardown_file() {
docker rm -f "${CONTAINER}" "${CONTAINER2}"
}

View file

@ -1,88 +1,90 @@
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
TEST_NAME_PREFIX='Fail2Ban:'
CONTAINER1_NAME='dms-test_fail2ban'
CONTAINER2_NAME='dms-test_fail2ban_fail-auth-mailer'
function get_container2_ip() {
docker inspect --format '{{ .NetworkSettings.IPAddress }}' "${CONTAINER2_NAME}"
}
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run --rm -d --name mail_fail2ban \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_FAIL2BAN=1 \
-e POSTSCREEN_ACTION=ignore \
--cap-add=NET_ADMIN \
--hostname mail.my-domain.com \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
"${NAME}"
export CONTAINER_NAME
CONTAINER_NAME=${CONTAINER1_NAME}
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_FAIL2BAN=1
--env POSTSCREEN_ACTION=ignore
--cap-add=NET_ADMIN
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)"
)
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
# Create a container which will send wrong authentications and should get banned
docker run --name fail-auth-mailer \
-e MAIL_FAIL2BAN_IP="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' mail_fail2ban)" \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test \
-d "${NAME}" \
tail -f /var/log/faillog
CONTAINER_NAME=${CONTAINER2_NAME}
local CUSTOM_SETUP_ARGUMENTS=(--env MAIL_FAIL2BAN_IP="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${CONTAINER1_NAME})")
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_finished_setup_in_container mail_fail2ban
# Set default implicit container fallback for helpers:
CONTAINER_NAME=${CONTAINER1_NAME}
}
function teardown_file() {
docker rm -f mail_fail2ban fail-auth-mailer
docker rm -f "${CONTAINER1_NAME}" "${CONTAINER2_NAME}"
}
#
# processes
#
@test "checking process: fail2ban (fail2ban server enabled)" {
run docker exec mail_fail2ban /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/bin/python3 /usr/bin/fail2ban-server'"
@test "${TEST_NAME_PREFIX} Fail2Ban is running" {
run check_if_process_is_running 'fail2ban-server'
assert_success
}
#
# fail2ban
#
@test "${TEST_NAME_PREFIX} localhost is not banned because ignored" {
_run_in_container fail2ban-client status postfix-sasl
assert_success
refute_output --regexp '.*IP list:.*127\.0\.0\.1.*'
@test "checking fail2ban: localhost is not banned because ignored" {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep 'IP list:.*127.0.0.1'"
assert_failure
run docker exec mail_fail2ban /bin/sh -c "grep 'ignoreip = 127.0.0.1/8' /etc/fail2ban/jail.conf"
_run_in_container grep 'ignoreip = 127.0.0.1/8' /etc/fail2ban/jail.conf
assert_success
}
@test "checking fail2ban: fail2ban-fail2ban.cf overrides" {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get loglevel | grep DEBUG"
@test "${TEST_NAME_PREFIX} fail2ban-fail2ban.cf overrides" {
_run_in_container fail2ban-client get loglevel
assert_success
assert_output --partial 'DEBUG'
}
@test "checking fail2ban: fail2ban-jail.cf overrides" {
FILTERS=(dovecot postfix postfix-sasl)
for FILTER in "${FILTERS[@]}"; do
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get ${FILTER} bantime"
@test "${TEST_NAME_PREFIX} fail2ban-jail.cf overrides" {
for FILTER in 'dovecot' 'postfix' 'postfix-sasl'
do
_run_in_container fail2ban-client get "${FILTER}" bantime
assert_output 1234
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get ${FILTER} findtime"
_run_in_container fail2ban-client get "${FILTER}" findtime
assert_output 321
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get ${FILTER} maxretry"
_run_in_container fail2ban-client get "${FILTER}" maxretry
assert_output 2
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'dovecot', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'dovecot', 'addaction', 'nftables-multiport']"
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'postfix', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'postfix', 'addaction', 'nftables-multiport']"
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'postfix-sasl', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'postfix-sasl', 'addaction', 'nftables-multiport']"
_run_in_container fail2ban-client -d
assert_output --partial "['set', 'dovecot', 'addaction', 'nftables-multiport']"
assert_output --partial "['set', 'postfix', 'addaction', 'nftables-multiport']"
assert_output --partial "['set', 'postfix-sasl', 'addaction', 'nftables-multiport']"
done
}
@test "checking fail2ban: ban ip on multiple failed login" {
# can't pipe the file as usual due to postscreen. (respecting postscreen_greet_wait time and talking in turn):
# NOTE: This test case is fragile if other test cases were to be run concurrently
@test "${TEST_NAME_PREFIX} ban ip on multiple failed login" {
# can't pipe the file as usual due to postscreen
# respecting postscreen_greet_wait time and talking in turn):
# shellcheck disable=SC1004
for _ in {1,2}
do
docker exec fail-auth-mailer /bin/bash -c \
docker exec "${CONTAINER2_NAME}" /bin/bash -c \
'exec 3<>/dev/tcp/${MAIL_FAIL2BAN_IP}/25 && \
while IFS= read -r cmd; do \
head -1 <&3; \
@ -93,108 +95,123 @@ function teardown_file() {
sleep 5
FAIL_AUTH_MAILER_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' fail-auth-mailer)
# Checking that FAIL_AUTH_MAILER_IP is banned in mail_fail2ban
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep '${FAIL_AUTH_MAILER_IP}'"
# Checking that CONTAINER2_IP is banned in "${CONTAINER1_NAME}"
CONTAINER2_IP=$(get_container2_ip)
_run_in_container fail2ban-client status postfix-sasl
assert_success
assert_output --partial "${CONTAINER2_IP}"
# Checking that FAIL_AUTH_MAILER_IP is banned by nftables
run docker exec mail_fail2ban /bin/sh -c "nft list set inet f2b-table addr-set-postfix-sasl"
assert_output --partial "elements = { ${FAIL_AUTH_MAILER_IP} }"
# Checking that CONTAINER2_IP is banned by nftables
_run_in_container bash -c 'nft list set inet f2b-table addr-set-postfix-sasl'
assert_success
assert_output --partial "elements = { ${CONTAINER2_IP} }"
}
@test "checking fail2ban: unban ip works" {
FAIL_AUTH_MAILER_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' fail-auth-mailer)
docker exec mail_fail2ban fail2ban-client set postfix-sasl unbanip "${FAIL_AUTH_MAILER_IP}"
@test "${TEST_NAME_PREFIX} unban ip works" {
CONTAINER2_IP=$(get_container2_ip)
_run_in_container fail2ban-client set postfix-sasl unbanip "${CONTAINER2_IP}"
assert_success
sleep 5
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep 'IP list:.*${FAIL_AUTH_MAILER_IP}'"
assert_failure
# Checking that CONTAINER2_IP is unbanned in "${CONTAINER1_NAME}"
_run_in_container fail2ban-client status postfix-sasl
assert_success
refute_output --partial "${CONTAINER2_IP}"
# Checking that FAIL_AUTH_MAILER_IP is unbanned by nftables
run docker exec mail_fail2ban /bin/sh -c "nft list set inet f2b-table addr-set-postfix-sasl"
refute_output --partial "${FAIL_AUTH_MAILER_IP}"
# Checking that CONTAINER2_IP is unbanned by nftables
_run_in_container bash -c 'nft list set inet f2b-table addr-set-postfix-sasl'
refute_output --partial "${CONTAINER2_IP}"
}
@test "checking fail2ban ban" {
# Ban single IP address
run docker exec mail_fail2ban fail2ban ban 192.0.66.7
@test "${TEST_NAME_PREFIX} bans work properly (single IP)" {
_run_in_container fail2ban ban 192.0.66.7
assert_success
assert_output "Banned custom IP: 1"
assert_output 'Banned custom IP: 1'
run docker exec mail_fail2ban fail2ban
_run_in_container fail2ban
assert_success
assert_output --regexp "Banned in custom:.*192\.0\.66\.7"
assert_output --regexp 'Banned in custom:.*192\.0\.66\.7'
run docker exec mail_fail2ban nft list set inet f2b-table addr-set-custom
_run_in_container nft list set inet f2b-table addr-set-custom
assert_success
assert_output --partial "elements = { 192.0.66.7 }"
assert_output --partial 'elements = { 192.0.66.7 }'
run docker exec mail_fail2ban fail2ban unban 192.0.66.7
_run_in_container fail2ban unban 192.0.66.7
assert_success
assert_output --partial "Unbanned IP from custom: 1"
assert_output --partial 'Unbanned IP from custom: 1'
run docker exec mail_fail2ban nft list set inet f2b-table addr-set-custom
refute_output --partial "192.0.66.7"
# Ban IP network
run docker exec mail_fail2ban fail2ban ban 192.0.66.0/24
assert_success
assert_output "Banned custom IP: 1"
run docker exec mail_fail2ban fail2ban
assert_success
assert_output --regexp "Banned in custom:.*192\.0\.66\.0/24"
run docker exec mail_fail2ban nft list set inet f2b-table addr-set-custom
assert_success
assert_output --partial "elements = { 192.0.66.0/24 }"
run docker exec mail_fail2ban fail2ban unban 192.0.66.0/24
assert_success
assert_output --partial "Unbanned IP from custom: 1"
run docker exec mail_fail2ban nft list set inet f2b-table addr-set-custom
refute_output --partial "192.0.66.0/24"
_run_in_container nft list set inet f2b-table addr-set-custom
refute_output --partial '192.0.66.7'
}
@test "checking FAIL2BAN_BLOCKTYPE is really set to drop" {
run docker exec mail_fail2ban bash -c 'nft list table inet f2b-table'
@test "${TEST_NAME_PREFIX} bans work properly (subnet)" {
_run_in_container fail2ban ban 192.0.66.0/24
assert_success
assert_output 'Banned custom IP: 1'
_run_in_container fail2ban
assert_success
assert_output --regexp 'Banned in custom:.*192\.0\.66\.0/24'
_run_in_container nft list set inet f2b-table addr-set-custom
assert_success
assert_output --partial 'elements = { 192.0.66.0/24 }'
_run_in_container fail2ban unban 192.0.66.0/24
assert_success
assert_output --partial 'Unbanned IP from custom: 1'
_run_in_container nft list set inet f2b-table addr-set-custom
refute_output --partial '192.0.66.0/24'
}
@test "${TEST_NAME_PREFIX} FAIL2BAN_BLOCKTYPE is really set to drop" {
# ban IPs here manually so we can be sure something is inside the jails
for JAIL in dovecot postfix-sasl custom; do
_run_in_container fail2ban-client set "${JAIL}" banip 192.33.44.55
assert_success
done
_run_in_container nft list table inet f2b-table
assert_success
assert_output --partial 'tcp dport { 110, 143, 465, 587, 993, 995, 4190 } ip saddr @addr-set-dovecot drop'
assert_output --partial 'tcp dport { 25, 110, 143, 465, 587, 993, 995 } ip saddr @addr-set-postfix-sasl drop'
assert_output --partial 'tcp dport { 25, 110, 143, 465, 587, 993, 995, 4190 } ip saddr @addr-set-custom drop'
# unban the IPs previously banned to get a clean state again
for JAIL in dovecot postfix-sasl custom; do
_run_in_container fail2ban-client set "${JAIL}" unbanip 192.33.44.55
assert_success
done
}
@test "checking setup.sh: setup.sh fail2ban" {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client set dovecot banip 192.0.66.4"
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client set dovecot banip 192.0.66.5"
@test "${TEST_NAME_PREFIX} setup.sh fail2ban" {
_run_in_container fail2ban-client set dovecot banip 192.0.66.4
_run_in_container fail2ban-client set dovecot banip 192.0.66.5
sleep 10
run ./setup.sh -c mail_fail2ban fail2ban
# Originally: run ./setup.sh -c "${CONTAINER1_NAME}" fail2ban
_run_in_container setup fail2ban
assert_output --regexp '^Banned in dovecot:.*192\.0\.66\.4'
assert_output --regexp '^Banned in dovecot:.*192\.0\.66\.5'
run ./setup.sh -c mail_fail2ban fail2ban unban 192.0.66.4
_run_in_container setup fail2ban unban 192.0.66.4
assert_output --partial "Unbanned IP from dovecot: 1"
run ./setup.sh -c mail_fail2ban fail2ban
assert_output --regexp "^Banned in dovecot:.*192\.0\.66\.5"
_run_in_container setup fail2ban
assert_output --regexp '^Banned in dovecot:.*192\.0\.66\.5'
run ./setup.sh -c mail_fail2ban fail2ban unban 192.0.66.5
assert_output --partial "Unbanned IP from dovecot: 1"
_run_in_container setup fail2ban unban 192.0.66.5
assert_output --partial 'Unbanned IP from dovecot: 1'
run ./setup.sh -c mail_fail2ban fail2ban unban
assert_output --partial "You need to specify an IP address: Run"
_run_in_container setup fail2ban unban
assert_output --partial 'You need to specify an IP address: Run'
}
#
# supervisor
#
@test "checking restart of process: fail2ban (fail2ban server enabled)" {
run docker exec mail_fail2ban /bin/bash -c "pkill fail2ban && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/bin/python3 /usr/bin/fail2ban-server'"
@test "${TEST_NAME_PREFIX} restart of Fail2Ban" {
_run_in_container pkill fail2ban
assert_success
run_until_success_or_timeout 10 check_if_process_is_running 'fail2ban-server'
}

View file

@ -1,92 +1,92 @@
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
TEST_NAME_PREFIX='Postgrey (enabled):'
CONTAINER_NAME='dms-test_postgrey_enabled'
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_DNSBL=1
--env ENABLE_POSTGREY=1
--env PERMIT_DOCKER=container
--env POSTGREY_AUTO_WHITELIST_CLIENTS=5
--env POSTGREY_DELAY=15
--env POSTGREY_MAX_AGE=35
--env POSTGREY_TEXT="Delayed by Postgrey"
)
docker run -d --name mail_with_postgrey \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_DNSBL=1 \
-e ENABLE_POSTGREY=1 \
-e PERMIT_DOCKER=container \
-e POSTGREY_AUTO_WHITELIST_CLIENTS=5 \
-e POSTGREY_DELAY=15 \
-e POSTGREY_MAX_AGE=35 \
-e POSTGREY_TEXT="Delayed by Postgrey" \
-h mail.my-domain.com -t "${NAME}"
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
# using postfix availability as start indicator, this might be insufficient for postgrey
wait_for_smtp_port_in_container mail_with_postgrey
# Postfix needs to be ready on port 25 for nc usage below:
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
}
function teardown_file() {
docker rm -f mail_with_postgrey
}
function teardown_file() { _default_teardown ; }
@test "checking postgrey: /etc/postfix/main.cf correctly edited" {
run docker exec mail_with_postgrey /bin/bash -c "grep -F 'zen.spamhaus.org=127.0.0.[2..11], check_policy_service inet:127.0.0.1:10023' /etc/postfix/main.cf | wc -l"
@test "${TEST_NAME_PREFIX} /etc/postfix/main.cf correctly edited" {
_run_in_container bash -c "grep -F 'zen.spamhaus.org=127.0.0.[2..11], check_policy_service inet:127.0.0.1:10023' /etc/postfix/main.cf | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: /etc/default/postgrey correctly edited and has the default values" {
run docker exec mail_with_postgrey /bin/bash -c "grep '^POSTGREY_OPTS=\"--inet=127.0.0.1:10023 --delay=15 --max-age=35 --auto-whitelist-clients=5\"$' /etc/default/postgrey | wc -l"
@test "${TEST_NAME_PREFIX} /etc/default/postgrey correctly edited and has the default values" {
_run_in_container bash -c "grep '^POSTGREY_OPTS=\"--inet=127.0.0.1:10023 --delay=15 --max-age=35 --auto-whitelist-clients=5\"$' /etc/default/postgrey | wc -l"
assert_success
assert_output 1
run docker exec mail_with_postgrey /bin/bash -c "grep '^POSTGREY_TEXT=\"Delayed by Postgrey\"$' /etc/default/postgrey | wc -l"
_run_in_container bash -c "grep '^POSTGREY_TEXT=\"Delayed by Postgrey\"$' /etc/default/postgrey | wc -l"
assert_success
assert_output 1
}
@test "checking process: postgrey (postgrey server enabled)" {
run docker exec mail_with_postgrey /bin/bash -c "ps aux --forest | grep -v grep | grep 'postgrey'"
@test "${TEST_NAME_PREFIX} Postgrey is running" {
run check_if_process_is_running 'postgrey'
assert_success
}
@test "checking postgrey: there should be a log entry about a new greylisted e-mail user@external.tld in /var/log/mail/mail.log" {
@test "${TEST_NAME_PREFIX} there should be a log entry about a new greylisted e-mail user@external.tld in /var/log/mail/mail.log" {
#editing the postfix config in order to ensure that postgrey handles the test e-mail. The other spam checks at smtpd_recipient_restrictions would interfere with it.
run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/permit_sasl_authenticated.*policyd-spf,$//g' /etc/postfix/main.cf"
run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/reject_unauth_pipelining.*reject_unknown_recipient_domain,$//g' /etc/postfix/main.cf"
run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/reject_rbl_client.*inet:127\.0\.0\.1:10023$//g' /etc/postfix/main.cf"
run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/smtpd_recipient_restrictions =/smtpd_recipient_restrictions = check_policy_service inet:127.0.0.1:10023/g' /etc/postfix/main.cf"
_run_in_container bash -c "sed -ie 's/permit_sasl_authenticated.*policyd-spf,$//g' /etc/postfix/main.cf"
_run_in_container bash -c "sed -ie 's/reject_unauth_pipelining.*reject_unknown_recipient_domain,$//g' /etc/postfix/main.cf"
_run_in_container bash -c "sed -ie 's/reject_rbl_client.*inet:127\.0\.0\.1:10023$//g' /etc/postfix/main.cf"
_run_in_container bash -c "sed -ie 's/smtpd_recipient_restrictions =/smtpd_recipient_restrictions = check_policy_service inet:127.0.0.1:10023/g' /etc/postfix/main.cf"
_run_in_container postfix reload
run docker exec mail_with_postgrey /bin/sh -c "/etc/init.d/postfix reload"
run docker exec mail_with_postgrey /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/postgrey.txt"
_run_in_container bash -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/postgrey.txt"
sleep 5 #ensure that the information has been written into the log
run docker exec mail_with_postgrey /bin/bash -c "grep -i 'action=greylist.*user@external\.tld' /var/log/mail/mail.log | wc -l"
_run_in_container bash -c "grep -i 'action=greylist.*user@external\.tld' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: there should be a log entry about the retried and passed e-mail user@external.tld in /var/log/mail/mail.log" {
@test "${TEST_NAME_PREFIX} there should be a log entry about the retried and passed e-mail user@external.tld in /var/log/mail/mail.log" {
sleep 20 #wait 20 seconds so that postgrey would accept the message
run docker exec mail_with_postgrey /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/postgrey.txt"
_run_in_container bash -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/postgrey.txt"
sleep 8
run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=triplet found.*user@external\.tld' /var/log/mail/mail.log | wc -l"
_run_in_container bash -c "grep -i 'action=pass, reason=triplet found.*user@external\.tld' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: there should be a log entry about the whitelisted and passed e-mail user@whitelist.tld in /var/log/mail/mail.log" {
run docker exec mail_with_postgrey /bin/sh -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist.txt"
run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=client whitelist' /var/log/mail/mail.log | wc -l"
@test "${TEST_NAME_PREFIX} there should be a log entry about the whitelisted and passed e-mail user@whitelist.tld in /var/log/mail/mail.log" {
_run_in_container bash -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist.txt"
_run_in_container bash -c "grep -i 'action=pass, reason=client whitelist' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: there should be a log entry about the whitelisted local and passed e-mail user@whitelistlocal.tld in /var/log/mail/mail.log" {
run docker exec mail_with_postgrey /bin/sh -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist_local.txt"
run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=client whitelist' /var/log/mail/mail.log | wc -l"
@test "${TEST_NAME_PREFIX} there should be a log entry about the whitelisted local and passed e-mail user@whitelistlocal.tld in /var/log/mail/mail.log" {
_run_in_container bash -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist_local.txt"
_run_in_container bash -c "grep -i 'action=pass, reason=client whitelist' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}
@test "checking postgrey: there should be a log entry about the whitelisted recipient user2@otherdomain.tld in /var/log/mail/mail.log" {
run docker exec mail_with_postgrey /bin/sh -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist_recipients.txt"
run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=recipient whitelist' /var/log/mail/mail.log | wc -l"
@test "${TEST_NAME_PREFIX} there should be a log entry about the whitelisted recipient user2@otherdomain.tld in /var/log/mail/mail.log" {
_run_in_container bash -c "nc -w 8 0.0.0.0 10023 < /tmp/docker-mailserver-test/nc_templates/postgrey_whitelist_recipients.txt"
_run_in_container bash -c "grep -i 'action=pass, reason=recipient whitelist' /var/log/mail/mail.log | wc -l"
assert_success
assert_output 1
}

View file

@ -1,44 +1,46 @@
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
setup() {
# Getting mail container IP
MAIL_POSTSCREEN_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' mail_postscreen)
TEST_NAME_PREFIX='Postscreen:'
CONTAINER1_NAME='dms-test_postscreen_enforce'
CONTAINER2_NAME='dms-test_postscreen_sender'
function setup() {
MAIL_POSTSCREEN_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "${CONTAINER1_NAME}")
}
setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
function setup_file() {
local CONTAINER_NAME=${CONTAINER1_NAME}
local CUSTOM_SETUP_ARGUMENTS=(
--env POSTSCREEN_ACTION=enforce
--cap-add=NET_ADMIN
)
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
docker run -d --name mail_postscreen \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e POSTSCREEN_ACTION=enforce \
--cap-add=NET_ADMIN \
-h mail.my-domain.com -t "${NAME}"
docker run --name mail_postscreen_sender \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-d "${NAME}" \
tail -f /var/log/faillog
wait_for_smtp_port_in_container mail_postscreen
local CONTAINER_NAME=${CONTAINER2_NAME}
init_with_defaults
common_container_setup
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
}
teardown_file() {
docker rm -f mail_postscreen mail_postscreen_sender
function teardown_file() {
docker rm -f "${CONTAINER1_NAME}" "${CONTAINER2_NAME}"
}
@test "checking postscreen: talk too fast" {
docker exec mail_postscreen_sender /bin/sh -c "nc ${MAIL_POSTSCREEN_IP} 25 < /tmp/docker-mailserver-test/auth/smtp-auth-login.txt"
@test "${TEST_NAME_PREFIX} talk too fast" {
run docker exec "${CONTAINER2_NAME}" /bin/sh -c "nc ${MAIL_POSTSCREEN_IP} 25 < /tmp/docker-mailserver-test/auth/smtp-auth-login.txt"
assert_success
repeat_until_success_or_timeout 10 run docker exec mail_postscreen grep 'COMMAND PIPELINING' /var/log/mail/mail.log
repeat_until_success_or_timeout 10 run docker exec "${CONTAINER1_NAME}" grep 'COMMAND PIPELINING' /var/log/mail/mail.log
assert_success
}
@test "checking postscreen: positive test (respecting postscreen_greet_wait time and talking in turn)" {
@test "${TEST_NAME_PREFIX} positive test (respecting postscreen_greet_wait time and talking in turn)" {
for _ in {1,2}; do
# shellcheck disable=SC1004
docker exec mail_postscreen_sender /bin/bash -c \
docker exec "${CONTAINER2_NAME}" /bin/bash -c \
'exec 3<>/dev/tcp/'"${MAIL_POSTSCREEN_IP}"'/25 && \
while IFS= read -r cmd; do \
head -1 <&3; \
@ -47,6 +49,6 @@ teardown_file() {
done < "/tmp/docker-mailserver-test/auth/smtp-auth-login.txt"'
done
repeat_until_success_or_timeout 10 run docker exec mail_postscreen grep 'PASS NEW ' /var/log/mail/mail.log
repeat_until_success_or_timeout 10 run docker exec "${CONTAINER1_NAME}" grep 'PASS NEW ' /var/log/mail/mail.log
assert_success
}

View file

@ -1,8 +1,8 @@
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
TEST_NAME_PREFIX='spam (Amavis):'
CONTAINER_NAME='dms-test-spam_bounced'
TEST_NAME_PREFIX='Spam bounced:'
CONTAINER_NAME='dms-test_spam-bounced'
function setup_file() {
init_with_defaults

View file

@ -1,67 +1,67 @@
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
TEST_NAME_PREFIX='Spam junk folder:'
CONTAINER1_NAME='dms-test_spam-junk-folder_1'
CONTAINER2_NAME='dms-test_spam-junk-folder_2'
function teardown() { _default_teardown ; }
# Test case
# ---------
# When SPAMASSASSIN_SPAM_TO_INBOX=1, spam messages must be delivered and eventually (MOVE_SPAM_TO_JUNK=1) moved to the Junk folder.
# When SPAMASSASSIN_SPAM_TO_INBOX=1, spam messages must be delivered
# and eventually (MOVE_SPAM_TO_JUNK=1) moved to the Junk folder.
@test "checking amavis: spam message is delivered and moved to the Junk folder (MOVE_SPAM_TO_JUNK=1)" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_spam_moved_junk)
docker run -d --name mail_spam_moved_junk \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_SPAMASSASSIN=1 \
-e MOVE_SPAM_TO_JUNK=1 \
-e PERMIT_DOCKER=container \
-e SA_SPAM_SUBJECT="SPAM: " \
-e SPAMASSASSIN_SPAM_TO_INBOX=1 \
-h mail.my-domain.com -t "${NAME}"
teardown() { docker rm -f mail_spam_moved_junk; }
wait_for_smtp_port_in_container mail_spam_moved_junk
@test "${TEST_NAME_PREFIX} (Amavis) spam message delivered & moved to Junk folder" {
export CONTAINER_NAME=${CONTAINER1_NAME}
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_AMAVIS=1
--env ENABLE_SPAMASSASSIN=1
--env MOVE_SPAM_TO_JUNK=1
--env PERMIT_DOCKER=container
--env SA_SPAM_SUBJECT="SPAM: "
--env SPAMASSASSIN_SPAM_TO_INBOX=1
)
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
# send a spam message
run docker exec mail_spam_moved_junk /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
_run_in_container bash -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
assert_success
# message will be added to a queue with varying delay until amavis receives it
run repeat_until_success_or_timeout 60 sh -c "docker logs mail_spam_moved_junk | grep 'Passed SPAM {RelayedTaggedInbound,Quarantined}'"
run repeat_until_success_or_timeout 60 bash -c "docker logs ${CONTAINER_NAME} | grep 'Passed SPAM {RelayedTaggedInbound,Quarantined}'"
assert_success
# spam moved to Junk folder
run repeat_until_success_or_timeout 20 sh -c "docker exec mail_spam_moved_junk sh -c 'grep \"Subject: SPAM: \" /var/mail/localhost.localdomain/user1/.Junk/new/ -R'"
run repeat_until_success_or_timeout 20 bash -c "docker exec ${CONTAINER_NAME} sh -c 'grep \"Subject: SPAM: \" /var/mail/localhost.localdomain/user1/.Junk/new/ -R'"
assert_success
}
@test "checking amavis: spam message is delivered to INBOX (MOVE_SPAM_TO_JUNK=0)" {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container . mail_spam_moved_new)
docker run -d --name mail_spam_moved_new \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_SPAMASSASSIN=1 \
-e MOVE_SPAM_TO_JUNK=0 \
-e PERMIT_DOCKER=container \
-e SA_SPAM_SUBJECT="SPAM: " \
-e SPAMASSASSIN_SPAM_TO_INBOX=1 \
-h mail.my-domain.com -t "${NAME}"
teardown() { docker rm -f mail_spam_moved_new; }
wait_for_smtp_port_in_container mail_spam_moved_new
@test "${TEST_NAME_PREFIX} (Amavis) spam message delivered to INBOX" {
export CONTAINER_NAME=${CONTAINER2_NAME}
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_AMAVIS=1
--env ENABLE_SPAMASSASSIN=1
--env MOVE_SPAM_TO_JUNK=0
--env PERMIT_DOCKER=container
--env SA_SPAM_SUBJECT="SPAM: "
--env SPAMASSASSIN_SPAM_TO_INBOX=1
)
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
# send a spam message
run docker exec mail_spam_moved_new /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
_run_in_container /bin/bash -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
assert_success
# message will be added to a queue with varying delay until amavis receives it
run repeat_until_success_or_timeout 60 sh -c "docker logs mail_spam_moved_new | grep 'Passed SPAM {RelayedTaggedInbound,Quarantined}'"
run repeat_until_success_or_timeout 60 bash -c "docker logs ${CONTAINER_NAME} | grep 'Passed SPAM {RelayedTaggedInbound,Quarantined}'"
assert_success
# spam moved to INBOX
run repeat_until_success_or_timeout 20 sh -c "docker exec mail_spam_moved_new sh -c 'grep \"Subject: SPAM: \" /var/mail/localhost.localdomain/user1/new/ -R'"
run repeat_until_success_or_timeout 20 bash -c "docker exec ${CONTAINER_NAME} sh -c 'grep \"Subject: SPAM: \" /var/mail/localhost.localdomain/user1/new/ -R'"
assert_success
}

View file

@ -1,64 +1,61 @@
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
function setup() {
local PRIVATE_CONFIG
TEST_NAME_PREFIX='Undefined spam subject:'
PRIVATE_CONFIG=$(duplicate_config_for_container .)
docker run -d --name mail_undef_spam_subject \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e ENABLE_SPAMASSASSIN=1 \
-e SA_SPAM_SUBJECT="undef" \
--hostname mail.my-domain.com \
--tty \
"${NAME}"
CONTAINER1_NAME='dms-test_spam-undef-subject_1'
CONTAINER2_NAME='dms-test_spam-undef-subject_2'
CONTAINER_NAME=${CONTAINER2_NAME}
CONTAINER='mail_undef_spam_subject_2'
PRIVATE_CONFIG=$(duplicate_config_for_container . "${CONTAINER}")
docker run -d \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-v "$(pwd)/test/onedir":/var/mail-state \
-e ENABLE_CLAMAV=1 \
-e SPOOF_PROTECTION=1 \
-e ENABLE_SPAMASSASSIN=1 \
-e REPORT_RECIPIENT=user1@localhost.localdomain \
-e REPORT_SENDER=report1@mail.my-domain.com \
-e SA_TAG=-5.0 \
-e SA_TAG2=2.0 \
-e SA_KILL=3.0 \
-e SA_SPAM_SUBJECT="SPAM: " \
-e VIRUSMAILS_DELETE_DELAY=7 \
-e ENABLE_SRS=1 \
-e ENABLE_MANAGESIEVE=1 \
-e PERMIT_DOCKER=host \
--name "${CONTAINER}" \
--hostname mail.my-domain.com \
--tty \
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)" \
"${NAME}"
function setup_file() {
local CONTAINER_NAME=${CONTAINER1_NAME}
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_AMAVIS=1
--env ENABLE_SPAMASSASSIN=1
--env SA_SPAM_SUBJECT='undef'
)
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_finished_setup_in_container mail_undef_spam_subject
wait_for_finished_setup_in_container "${CONTAINER}"
# ulimit required for `ENABLE_SRS=1`
local CONTAINER_NAME=${CONTAINER2_NAME}
local CUSTOM_SETUP_ARGUMENTS=(
--env ENABLE_CLAMAV=1
--env SPOOF_PROTECTION=1
--env ENABLE_SPAMASSASSIN=1
--env REPORT_RECIPIENT=user1@localhost.localdomain
--env REPORT_SENDER=report1@mail.my-domain.com
--env SA_TAG=-5.0
--env SA_TAG2=2.0
--env SA_KILL=3.0
--env SA_SPAM_SUBJECT="SPAM: "
--env VIRUSMAILS_DELETE_DELAY=7
--env ENABLE_SRS=1
--env ENABLE_MANAGESIEVE=1
--env PERMIT_DOCKER=host
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)"
)
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
}
function teardown() {
docker rm -f mail_undef_spam_subject "${CONTAINER}"
function teardown_file() {
docker rm -f "${CONTAINER1_NAME}" "${CONTAINER2_NAME}"
}
@test "checking spamassassin: docker env variables are set correctly (custom)" {
run docker exec "${CONTAINER}" /bin/sh -c "grep '\$sa_tag_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= -5.0'"
@test "${TEST_NAME_PREFIX} Docker env variables are set correctly (custom)" {
_run_in_container bash -c "grep '\$sa_tag_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= -5.0'"
assert_success
run docker exec "${CONTAINER}" /bin/sh -c "grep '\$sa_tag2_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 2.0'"
_run_in_container bash -c "grep '\$sa_tag2_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 2.0'"
assert_success
run docker exec "${CONTAINER}" /bin/sh -c "grep '\$sa_kill_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 3.0'"
_run_in_container bash -c "grep '\$sa_kill_level_deflt' /etc/amavis/conf.d/20-debian_defaults | grep '= 3.0'"
assert_success
run docker exec "${CONTAINER}" /bin/sh -c "grep '\$sa_spam_subject_tag' /etc/amavis/conf.d/20-debian_defaults | grep '= .SPAM: .'"
_run_in_container bash -c "grep '\$sa_spam_subject_tag' /etc/amavis/conf.d/20-debian_defaults | grep '= .SPAM: .'"
assert_success
run docker exec mail_undef_spam_subject /bin/sh -c "grep '\$sa_spam_subject_tag' /etc/amavis/conf.d/20-debian_defaults | grep '= undef'"
run docker exec "${CONTAINER1_NAME}" bash -c "grep '\$sa_spam_subject_tag' /etc/amavis/conf.d/20-debian_defaults | grep '= undef'"
assert_success
}

View file

@ -6,7 +6,7 @@ load "${REPOSITORY_ROOT}/test/helper/common"
# ? to identify the test easily
TEST_NAME_PREFIX='template:'
# ? must be unique
CONTAINER_NAME='dms-test-template'
CONTAINER_NAME='dms-test_template'
# ? test setup

View file

@ -1,81 +1,128 @@
#!/usr/bin/env bats
load "${REPOSITORY_ROOT}/test/test_helper/common"
# Globals ${BATS_TMPDIR} and ${NAME}
# `${NAME}` defaults to `mailserver-testing:ci`
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
function teardown() {
docker rm -f tls_test_cipherlists
}
TEST_NAME_PREFIX='[Security] TLS (cipher lists):'
CONTAINER_PREFIX='dms-test_tls-cipherlists'
# NOTE: Tests cases here cannot be run concurrently:
# - The `testssl.txt` file configures `testssl.sh` to connect to `example.test` (TEST_DOMAIN)
# and this is set as a network alias to the DMS container being tested.
# - If multiple containers are active with this alias, the connection is not deterministic and will result
# in comparing the wrong results for a given variant.
function setup_file() {
export DOMAIN="example.test"
export NETWORK="test-network"
export TEST_DOMAIN='example.test'
export TEST_FQDN="mail.${TEST_DOMAIN}"
export TEST_NETWORK='test-network'
# Shared config for TLS testing (read-only)
# Contains various certs for testing TLS support (read-only):
export TLS_CONFIG_VOLUME
TLS_CONFIG_VOLUME="$(pwd)/test/test-files/ssl/${DOMAIN}/:/config/ssl/:ro"
# `${BATS_TMPDIR}` maps to `/tmp`
export TLS_RESULTS_DIR="${BATS_TMPDIR}/results"
TLS_CONFIG_VOLUME="${PWD}/test/test-files/ssl/${TEST_DOMAIN}/:/config/ssl/:ro"
# NOTE: If the network already exists, test will fail to start.
docker network create "${NETWORK}"
# Copies all of `./test/config/` to specific directory for testing
# `${PRIVATE_CONFIG}` becomes `$(pwd)/test/duplicate_configs/<bats test filename>`
export PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# Used for connecting testssl and DMS containers via network name `TEST_DOMAIN`:
# NOTE: If the network already exists, the test will fail to start
docker network create "${TEST_NETWORK}"
# Pull `testssl.sh` image in advance to avoid it interfering with the `run` captured output.
# Only interferes (potential test failure) with `assert_output` not `assert_success`?
docker pull drwetter/testssl.sh:3.1dev
# Only used in `should_support_expected_cipherlists()` to set a storage location for `testssl.sh` JSON output:
# `${BATS_TMPDIR}` maps to `/tmp`: https://bats-core.readthedocs.io/en/v1.8.2/writing-tests.html#special-variables
export TLS_RESULTS_DIR="${BATS_TMPDIR}/results"
}
function teardown_file() {
docker network rm "${NETWORK}"
docker network rm "${TEST_NETWORK}"
}
@test "checking tls: cipher list - rsa intermediate" {
check_ports 'rsa' 'intermediate'
function teardown() { _default_teardown ; }
@test "${TEST_NAME_PREFIX} 'TLS_LEVEL=intermediate' + RSA" {
configure_and_run_dms_container 'intermediate' 'rsa'
should_support_expected_cipherlists
}
@test "checking tls: cipher list - rsa modern" {
check_ports 'rsa' 'modern'
@test "${TEST_NAME_PREFIX} 'TLS_LEVEL=intermediate' + ECDSA" {
configure_and_run_dms_container 'intermediate' 'ecdsa'
should_support_expected_cipherlists
}
@test "checking tls: cipher list - ecdsa intermediate" {
check_ports 'ecdsa' 'intermediate'
# Only ECDSA with an RSA fallback is tested.
# There isn't a situation where RSA with an ECDSA fallback would make sense.
@test "${TEST_NAME_PREFIX} 'TLS_LEVEL=intermediate' + ECDSA with RSA fallback" {
configure_and_run_dms_container 'intermediate' 'ecdsa' 'rsa'
should_support_expected_cipherlists
}
@test "checking tls: cipher list - ecdsa modern" {
check_ports 'ecdsa' 'modern'
@test "${TEST_NAME_PREFIX} 'TLS_LEVEL=modern' + RSA" {
configure_and_run_dms_container 'modern' 'rsa'
should_support_expected_cipherlists
}
# Only ECDSA with RSA fallback is tested.
# There isn't a situation where RSA with ECDSA fallback would make sense.
@test "checking tls: cipher list - ecdsa intermediate, with rsa fallback" {
check_ports 'ecdsa' 'intermediate' 'rsa'
@test "${TEST_NAME_PREFIX} 'TLS_LEVEL=modern' + ECDSA" {
configure_and_run_dms_container 'modern' 'ecdsa'
should_support_expected_cipherlists
}
@test "checking tls: cipher list - ecdsa modern, with rsa fallback" {
check_ports 'ecdsa' 'modern' 'rsa'
@test "${TEST_NAME_PREFIX} 'TLS_LEVEL=modern' + ECDSA with RSA fallback" {
configure_and_run_dms_container 'modern' 'ecdsa' 'rsa'
should_support_expected_cipherlists
}
function check_ports() {
local KEY_TYPE=$1
local TLS_LEVEL=$2
function configure_and_run_dms_container() {
local TLS_LEVEL=$1
local KEY_TYPE=$2
local ALT_KEY_TYPE=$3 # Optional parameter
local KEY_TYPE_LABEL="${KEY_TYPE}"
# This is just to add a `_` delimiter between the two key types for readability
export TEST_VARIANT="${TLS_LEVEL}-${KEY_TYPE}"
if [[ -n ${ALT_KEY_TYPE} ]]
then
KEY_TYPE_LABEL="${KEY_TYPE}_${ALT_KEY_TYPE}"
TEST_VARIANT+="-${ALT_KEY_TYPE}"
fi
local RESULTS_PATH="${KEY_TYPE_LABEL}/${TLS_LEVEL}"
collect_cipherlist_data
export CONTAINER_NAME="${CONTAINER_PREFIX}_${TEST_VARIANT}"
# The initial set of args is static across test cases:
local CUSTOM_SETUP_ARGUMENTS=(
--volume "${TLS_CONFIG_VOLUME}"
--network "${TEST_NETWORK}"
--network-alias "${TEST_DOMAIN}"
--env ENABLE_POP3=1
--env SSL_TYPE="manual"
)
# The remaining args are dependent upon test case vars:
CUSTOM_SETUP_ARGUMENTS+=(
--env TLS_LEVEL="${TLS_LEVEL}"
--env SSL_CERT_PATH="/config/ssl/cert.${KEY_TYPE}.pem"
--env SSL_KEY_PATH="/config/ssl/key.${KEY_TYPE}.pem"
)
if [[ -n ${ALT_KEY_TYPE} ]]
then
CUSTOM_SETUP_ARGUMENTS+=(
--env SSL_ALT_CERT_PATH="/config/ssl/cert.${ALT_KEY_TYPE}.pem"
--env SSL_ALT_KEY_PATH="/config/ssl/key.${ALT_KEY_TYPE}.pem"
)
fi
init_with_defaults
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_smtp_port_in_container "${CONTAINER_NAME}"
}
function should_support_expected_cipherlists() {
# Make a directory with test user ownership. Avoids Docker creating this with root ownership.
# TODO: Can switch to filename prefix for JSON output when this is resolved: https://github.com/drwetter/testssl.sh/issues/1845
local RESULTS_PATH="${TLS_RESULTS_DIR}/${TEST_VARIANT}"
mkdir -p "${RESULTS_PATH}"
collect_cipherlists
verify_cipherlists
}
# Verify that the collected results match our expected cipherlists:
function verify_cipherlists() {
# SMTP: Opportunistic STARTTLS Explicit(25)
# Needs to test against cipher lists specific to Port 25 ('_p25' parameter)
check_cipherlists "${RESULTS_PATH}/port_25.json" '_p25'
@ -93,82 +140,56 @@ function check_ports() {
check_cipherlists "${RESULTS_PATH}/port_995.json"
}
function collect_cipherlist_data() {
local ALT_CERT=()
local ALT_KEY=()
if [[ -n ${ALT_KEY_TYPE} ]]
then
ALT_CERT=(--env SSL_ALT_CERT_PATH="/config/ssl/cert.${ALT_KEY_TYPE}.pem")
ALT_KEY=(--env SSL_ALT_KEY_PATH="/config/ssl/key.${ALT_KEY_TYPE}.pem")
fi
run docker run -d --name tls_test_cipherlists \
--volume "${PRIVATE_CONFIG}/:/tmp/docker-mailserver/" \
--volume "${TLS_CONFIG_VOLUME}" \
--env ENABLE_POP3=1 \
--env SSL_TYPE="manual" \
--env SSL_CERT_PATH="/config/ssl/cert.${KEY_TYPE}.pem" \
--env SSL_KEY_PATH="/config/ssl/key.${KEY_TYPE}.pem" \
"${ALT_CERT[@]}" \
"${ALT_KEY[@]}" \
--env TLS_LEVEL="${TLS_LEVEL}" \
--network "${NETWORK}" \
--network-alias "${DOMAIN}" \
--hostname "mail.${DOMAIN}" \
--tty \
"${NAME}" # Image name
assert_success
wait_for_tcp_port_in_container 25 tls_test_cipherlists
# Using `testssl.sh` we can test each port to collect a list of supported cipher suites (ordered):
function collect_cipherlists() {
# NOTE: An rDNS query for the container IP will resolve to `<container name>.<network name>.`
# Make directory with test user ownership. Avoids Docker creating with root ownership.
# TODO: Can switch to filename prefix for JSON output when this is resolved: https://github.com/drwetter/testssl.sh/issues/1845
mkdir -p "${TLS_RESULTS_DIR}/${RESULTS_PATH}"
# For non-CI test runs, instead of removing prior test files after this test suite completes,
# they're retained and overwritten by future test runs instead. Useful for inspection.
# `--preference` reduces the test scope to the cipher suites reported as supported by the server. Completes in ~35% of the time.
local TESTSSL_CMD=(--quiet --file "/config/ssl/testssl.txt" --mode parallel --overwrite --preference)
local TESTSSL_CMD=(
--quiet
--file "/config/ssl/testssl.txt"
--mode parallel
--overwrite
--preference
)
# NOTE: Batch testing ports via `--file` doesn't properly bubble up failure.
# If the failure for a test is misleading consider testing a single port with:
# local TESTSSL_CMD=(--quiet --jsonfile-pretty "${RESULTS_PATH}/port_${PORT}.json" --starttls smtp "${DOMAIN}:${PORT}")
# local TESTSSL_CMD=(--quiet --jsonfile-pretty "/output/port_${PORT}.json" --starttls smtp "${TEST_DOMAIN}:${PORT}")
# TODO: Can use `jq` to check for failure when this is resolved: https://github.com/drwetter/testssl.sh/issues/1844
# `--user "<uid>:<gid>"` is a workaround: Avoids `permission denied` write errors for json output, uses `id` to match user uid & gid.
run docker run --rm \
--user "$(id -u):$(id -g)" \
--network "${NETWORK}" \
--network "${TEST_NETWORK}" \
--volume "${TLS_CONFIG_VOLUME}" \
--volume "${TLS_RESULTS_DIR}/${RESULTS_PATH}/:/output" \
--volume "${RESULTS_PATH}:/output" \
--workdir "/output" \
drwetter/testssl.sh:3.1dev "${TESTSSL_CMD[@]}"
assert_success
}
# Compares the expected cipher lists against logged test results from `testssl.sh`
function check_cipherlists() {
local RESULTS_FILEPATH=$1
local p25=$2 # optional suffix
compare_cipherlist "cipherorder_TLSv1_2" "$(get_cipherlist "TLSv1_2${p25}")"
compare_cipherlist "cipherorder_TLSv1_3" "$(get_cipherlist 'TLSv1_3')"
}
# Use `jq` to extract a specific cipher list from the target`testssl.sh` results json output file
function compare_cipherlist() {
local TARGET_CIPHERLIST=$1
local RESULTS_FILE=$2
local EXPECTED_CIPHERLIST=$3
local EXPECTED_CIPHERLIST=$2
run jq '.scanResult[0].serverPreferences[] | select(.id=="'"${TARGET_CIPHERLIST}"'") | .finding' "${TLS_RESULTS_DIR}/${RESULTS_FILE}"
run jq '.scanResult[0].serverPreferences[] | select(.id=="'"${TARGET_CIPHERLIST}"'") | .finding' "${RESULTS_FILEPATH}"
assert_success
assert_output "${EXPECTED_CIPHERLIST}"
}
# Compares the expected cipher lists against logged test results from `testssl.sh`
function check_cipherlists() {
local RESULTS_FILE=$1
local p25=$2 # optional suffix
compare_cipherlist "cipherorder_TLSv1_2" "${RESULTS_FILE}" "$(get_cipherlist "TLSv1_2${p25}")"
compare_cipherlist "cipherorder_TLSv1_3" "${RESULTS_FILE}" "$(get_cipherlist 'TLSv1_3')"
}
# Expected cipher lists. Should match `TLS_LEVEL` cipher lists set in `scripts/helpers/ssl.sh`.
# Excluding Port 25 which uses defaults from Postfix after applying `smtpd_tls_exclude_ciphers` rules.
# NOTE: If a test fails, look at the `check_ports` params, then update the corresponding associative key's value
@ -185,34 +206,33 @@ function get_cipherlist() {
# Associative array for easy querying of required cipher list
declare -A CIPHER_LIST
CIPHER_LIST["rsa_intermediate_TLSv1_2"]='"ECDHE-RSA-CHACHA20-POLY1305 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256"'
CIPHER_LIST["rsa_modern_TLSv1_2"]='"ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384"'
# RSA:
CIPHER_LIST["intermediate-rsa_TLSv1_2"]='"ECDHE-RSA-CHACHA20-POLY1305 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256"'
CIPHER_LIST["modern-rsa_TLSv1_2"]='"ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384"'
# ECDSA:
CIPHER_LIST["ecdsa_intermediate_TLSv1_2"]='"ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384"'
CIPHER_LIST["ecdsa_modern_TLSv1_2"]='"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305"'
CIPHER_LIST["intermediate-ecdsa_TLSv1_2"]='"ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384"'
CIPHER_LIST["modern-ecdsa_TLSv1_2"]='"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305"'
# ECDSA + RSA fallback, dual cert support:
CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_2"]='"ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA384 DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256"'
CIPHER_LIST["ecdsa_rsa_modern_TLSv1_2"]='"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384"'
CIPHER_LIST["intermediate-ecdsa-rsa_TLSv1_2"]='"ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA384 DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256"'
CIPHER_LIST["modern-ecdsa-rsa_TLSv1_2"]='"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384"'
# Port 25
# TLSv1_2 has different server order and also includes ARIA, CCM, DHE+CHACHA20-POLY1305 cipher suites:
CIPHER_LIST["rsa_intermediate_TLSv1_2_p25"]='"ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM ECDHE-ARIA256-GCM-SHA384 DHE-RSA-ARIA256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ARIA256-GCM-SHA384 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM ECDHE-ARIA128-GCM-SHA256 DHE-RSA-ARIA128-GCM-SHA256 ECDHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA256 ARIA128-GCM-SHA256"'
# Port 25 is unaffected by `TLS_LEVEL` profiles, it has the same TLS v1.2 cipher list under both:
CIPHER_LIST["rsa_modern_TLSv1_2_p25"]=${CIPHER_LIST["rsa_intermediate_TLSv1_2_p25"]}
# Port 25 has a different server order, and also includes ARIA, CCM, DHE+CHACHA20-POLY1305 cipher suites:
# RSA (Port 25):
CIPHER_LIST["intermediate-rsa_TLSv1_2_p25"]='"ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM ECDHE-ARIA256-GCM-SHA384 DHE-RSA-ARIA256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ARIA256-GCM-SHA384 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM ECDHE-ARIA128-GCM-SHA256 DHE-RSA-ARIA128-GCM-SHA256 ECDHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA256 ARIA128-GCM-SHA256"'
# ECDSA (Port 25):
CIPHER_LIST["ecdsa_intermediate_TLSv1_2_p25"]='"ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM8 ECDHE-ECDSA-AES256-CCM ECDHE-ECDSA-ARIA256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-CCM8 ECDHE-ECDSA-AES128-CCM ECDHE-ECDSA-ARIA128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256"'
CIPHER_LIST["ecdsa_modern_TLSv1_2_p25"]=${CIPHER_LIST["ecdsa_intermediate_TLSv1_2_p25"]}
CIPHER_LIST["intermediate-ecdsa_TLSv1_2_p25"]='"ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM8 ECDHE-ECDSA-AES256-CCM ECDHE-ECDSA-ARIA256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-CCM8 ECDHE-ECDSA-AES128-CCM ECDHE-ECDSA-ARIA128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256"'
# ECDSA + RSA fallback, dual cert support (Port 25):
CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_2_p25"]='"ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM8 ECDHE-ECDSA-AES256-CCM DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM ECDHE-ECDSA-ARIA256-GCM-SHA384 ECDHE-ARIA256-GCM-SHA384 DHE-RSA-ARIA256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ARIA256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-CCM8 ECDHE-ECDSA-AES128-CCM DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM ECDHE-ECDSA-ARIA128-GCM-SHA256 ECDHE-ARIA128-GCM-SHA256 DHE-RSA-ARIA128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA256 ARIA128-GCM-SHA256"'
CIPHER_LIST["ecdsa_rsa_modern_TLSv1_2_p25"]=${CIPHER_LIST["ecdsa_rsa_intermediate_TLSv1_2_p25"]}
CIPHER_LIST["intermediate-ecdsa-rsa_TLSv1_2_p25"]='"ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM8 ECDHE-ECDSA-AES256-CCM DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM ECDHE-ECDSA-ARIA256-GCM-SHA384 ECDHE-ARIA256-GCM-SHA384 DHE-RSA-ARIA256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ARIA256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-CCM8 ECDHE-ECDSA-AES128-CCM DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM ECDHE-ECDSA-ARIA128-GCM-SHA256 ECDHE-ARIA128-GCM-SHA256 DHE-RSA-ARIA128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA256 ARIA128-GCM-SHA256"'
# Port 25 is unaffected by `TLS_LEVEL` profiles, thus no difference for modern:
CIPHER_LIST["modern-rsa_TLSv1_2_p25"]=${CIPHER_LIST["intermediate-rsa_TLSv1_2_p25"]}
CIPHER_LIST["modern-ecdsa_TLSv1_2_p25"]=${CIPHER_LIST["intermediate-ecdsa_TLSv1_2_p25"]}
CIPHER_LIST["modern-ecdsa-rsa_TLSv1_2_p25"]=${CIPHER_LIST["intermediate-ecdsa-rsa_TLSv1_2_p25"]}
local TARGET_QUERY="${KEY_TYPE_LABEL}_${TLS_LEVEL}_${TLS_VERSION}"
local TARGET_QUERY="${TEST_VARIANT}_${TLS_VERSION}"
echo "${CIPHER_LIST[${TARGET_QUERY}]}"
fi
}

View file

@ -1,29 +1,22 @@
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/test_helper/tls"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
load "${REPOSITORY_ROOT}/test/helper/tls"
# Globals referenced from `test_helper/common`:
# TEST_NAME TEST_FQDN TEST_TMP_CONFIG
TEST_NAME_PREFIX='[Security] TLS (SSL_TYPE=letsencrypt):'
CONTAINER1_NAME='dms-test_tls-letsencrypt_default-hostname'
CONTAINER2_NAME='dms-test_tls-letsencrypt_fallback-domainname'
CONTAINER3_NAME='dms-test_tls-letsencrypt_support-acme-json'
export TEST_FQDN='mail.example.test'
# Requires maintenance (TODO): Yes
# Can run tests in parallel?: No
function teardown() { _default_teardown ; }
# Not parallelize friendly when TEST_NAME is static,
# presently name of test file: `mail_ssl_letsencrypt`.
#
# Also shares a common TEST_TMP_CONFIG local folder,
# Instead of individual PRIVATE_CONFIG copies.
# For this test that is a non-issue, unless run in parallel.
# Applies to all tests:
function setup_file() {
# Similar to BATS `setup()` method, but invoked manually after
# CONTAINER_NAME has been adjusted for the running testcase.
function _initial_setup() {
init_with_defaults
# Override default to match the hostname we want to test against instead:
export TEST_FQDN='mail.example.test'
# Prepare certificates in the letsencrypt supported file structure:
# Note Certbot uses `privkey.pem`.
# NOTE: Certbot uses `privkey.pem`.
# `fullchain.pem` is currently what's detected, but we're actually providing the equivalent of `cert.pem` here.
# TODO: Verify format/structure is supported for nginx-proxy + acme-companion (uses `acme.sh` to provision).
@ -36,45 +29,38 @@ function setup_file() {
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/key.rsa.pem' 'example.test/privkey.pem'
}
# Not used
# function teardown_file() {
# }
function teardown() {
docker rm -f "${TEST_NAME}"
}
# Should detect and choose the cert for FQDN `mail.example.test` (HOSTNAME):
@test "ssl(letsencrypt): Should default to HOSTNAME (mail.example.test)" {
local TARGET_DOMAIN='mail.example.test'
@test "${TEST_NAME_PREFIX} Should default to HOSTNAME (${TEST_FQDN})" {
export CONTAINER_NAME=${CONTAINER1_NAME}
_initial_setup
local TEST_DOCKER_ARGS=(
local TARGET_DOMAIN=${TEST_FQDN}
local CUSTOM_SETUP_ARGUMENTS=(
--volume "${TEST_TMP_CONFIG}/letsencrypt/${TARGET_DOMAIN}/:/etc/letsencrypt/live/${TARGET_DOMAIN}/:ro"
--env PERMIT_DOCKER='container'
--env SSL_TYPE='letsencrypt'
)
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
common_container_setup 'TEST_DOCKER_ARGS'
#test hostname has certificate files
# Test that certificate files exist for the configured `hostname`:
_should_have_valid_config "${TARGET_DOMAIN}" 'privkey.pem' 'fullchain.pem'
_should_succesfully_negotiate_tls "${TARGET_DOMAIN}"
_should_not_support_fqdn_in_cert 'example.test'
}
# Should detect and choose cert for FQDN `example.test` (DOMAINNAME),
# as fallback when no cert for FQDN `mail.example.test` (HOSTNAME) exists:
@test "ssl(letsencrypt): Should fallback to DOMAINNAME (example.test)" {
local TARGET_DOMAIN='example.test'
@test "${TEST_NAME_PREFIX} Should fallback to DOMAINNAME (example.test)" {
export CONTAINER_NAME=${CONTAINER2_NAME}
_initial_setup
local TEST_DOCKER_ARGS=(
local TARGET_DOMAIN='example.test'
local CUSTOM_SETUP_ARGUMENTS=(
--volume "${TEST_TMP_CONFIG}/letsencrypt/${TARGET_DOMAIN}/:/etc/letsencrypt/live/${TARGET_DOMAIN}/:ro"
--env PERMIT_DOCKER='container'
--env SSL_TYPE='letsencrypt'
)
common_container_setup 'TEST_DOCKER_ARGS'
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
#test domain has certificate files
_should_have_valid_config "${TARGET_DOMAIN}" 'privkey.pem' 'fullchain.pem'
@ -87,34 +73,36 @@ function teardown() {
#
# NOTE: Currently all of the `acme.json` configs have the FQDN match a SAN value,
# all Subject CN (`main` in acme.json) are `Smallstep Leaf` which is not an FQDN.
# While valid for that field, it does mean there is no test coverage against `main`.
@test "ssl(letsencrypt): Traefik 'acme.json' (*.example.test)" {
# This test group changes to certs signed with an RSA Root CA key,
# These certs all support both FQDNs: `mail.example.test` and `example.test`,
# Except for the wildcard cert `*.example.test`, which intentionally excluded `example.test` when created.
# We want to maintain the same FQDN (mail.example.test) between the _acme_ecdsa and _acme_rsa tests.
local LOCAL_BASE_PATH="${PWD}/test/test-files/ssl/example.test/with_ca/rsa"
# While not using a FQDN is valid for that field,
# it does mean there is no test coverage against the `acme.json` field `main`.
@test "${TEST_NAME_PREFIX} Traefik 'acme.json' (*.example.test)" {
export CONTAINER_NAME=${CONTAINER3_NAME}
_initial_setup
# Change default Root CA cert used for verifying chain of trust with openssl:
# Override the `_initial_setup()` default Root CA cert (used for verifying the chain of trust via `openssl`):
# shellcheck disable=SC2034
local TEST_CA_CERT="${TEST_FILES_CONTAINER_PATH}/ssl/example.test/with_ca/rsa/ca-cert.rsa.pem"
# This test group switches to certs that are signed with an RSA Root CA key instead.
# All of these certs support both FQDNs (`mail.example.test` and `example.test`),
# Except for the wildcard cert (`*.example.test`), that was created with `example.test` intentionally excluded from SAN.
# We want to maintain the same FQDN (`mail.example.test`) between the _acme_ecdsa and _acme_rsa tests.
local LOCAL_BASE_PATH="${PWD}/test/test-files/ssl/example.test/with_ca/rsa"
function _prepare() {
# Default `acme.json` for _acme_ecdsa test:
cp "${LOCAL_BASE_PATH}/ecdsa.acme.json" "${TEST_TMP_CONFIG}/letsencrypt/acme.json"
# TODO: Provision wildcard certs via Traefik to inspect if `example.test` non-wildcard is also added to the cert.
# shellcheck disable=SC2034
local TEST_DOCKER_ARGS=(
local CUSTOM_SETUP_ARGUMENTS=(
--volume "${TEST_TMP_CONFIG}/letsencrypt/acme.json:/etc/letsencrypt/acme.json:ro"
--env LOG_LEVEL='trace'
--env PERMIT_DOCKER='container'
--env SSL_DOMAIN='*.example.test'
--env SSL_TYPE='letsencrypt'
)
common_container_setup 'TEST_DOCKER_ARGS'
wait_for_service "${TEST_NAME}" 'changedetector'
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
wait_for_service "${CONTAINER_NAME}" 'changedetector'
# Wait until the changedetector service startup delay is over:
repeat_until_success_or_timeout 20 sh -c "$(_get_service_logs 'changedetector') | grep 'Changedetector is ready'"
@ -184,7 +172,6 @@ function teardown() {
# Test Methods
#
# Check that Dovecot and Postfix are configured to use a cert for the expected FQDN:
function _should_have_valid_config() {
local EXPECTED_FQDN=${1}
@ -199,16 +186,14 @@ function _should_have_valid_config() {
# CMD ${1} run in container with output checked to match value of ${2}:
function _has_matching_line() {
run docker exec "${TEST_NAME}" sh -c "${1} | grep '${2}'"
_run_in_container bash -c "${1} | grep '${2}'"
assert_output "${2}"
}
#
# Traefik `acme.json` specific
#
# It should log success of extraction for the expected domain and restart Postfix.
function _should_have_succeeded_at_extraction() {
local EXPECTED_DOMAIN=${1}
@ -249,7 +234,7 @@ function _should_have_service_reload_count() {
local NUM_RELOADS=${1}
# Count how many times processes (like Postfix and Dovecot) have been reloaded by the `changedetector` service:
run docker exec "${TEST_NAME}" sh -c "grep -c 'Completed handling of detected change' /var/log/supervisor/changedetector.log"
_run_in_container grep --count 'Completed handling of detected change' '/var/log/supervisor/changedetector.log'
assert_output "${NUM_RELOADS}"
}
@ -265,12 +250,10 @@ function _should_have_expected_files() {
_should_be_equal_in_content "${LE_CERT_PATH}" "${EXPECTED_CERT_PATH}"
}
#
# Misc
#
# Rename test certificate files to match the expected file structure for letsencrypt:
function _copy_to_letsencrypt_storage() {
local SRC=${1}
@ -280,14 +263,18 @@ function _copy_to_letsencrypt_storage() {
FQDN_DIR=$(echo "${DEST}" | cut -d '/' -f1)
mkdir -p "${TEST_TMP_CONFIG}/letsencrypt/${FQDN_DIR}"
cp "${PWD}/test/test-files/ssl/${SRC}" "${TEST_TMP_CONFIG}/letsencrypt/${DEST}"
if ! cp "${PWD}/test/test-files/ssl/${SRC}" "${TEST_TMP_CONFIG}/letsencrypt/${DEST}"
then
echo "Could not copy cert file '${SRC}'' to '${DEST}'" >&2
exit 1
fi
}
function _should_be_equal_in_content() {
local CONTAINER_PATH=${1}
local LOCAL_PATH=${2}
run docker exec "${TEST_NAME}" sh -c "cat ${CONTAINER_PATH}"
_run_in_container /bin/bash -c "cat ${CONTAINER_PATH}"
assert_output "$(cat "${LOCAL_PATH}")"
assert_success
}
@ -295,13 +282,13 @@ function _should_be_equal_in_content() {
function _get_service_logs() {
local SERVICE=${1:-'mailserver'}
local CMD_LOGS=(docker exec "${TEST_NAME}" "supervisorctl tail -2200 ${SERVICE}")
local CMD_LOGS=(docker exec "${CONTAINER_NAME}" "supervisorctl tail -2200 ${SERVICE}")
# As the `mailserver` service logs are not stored in a file but output to stdout/stderr,
# The `supervisorctl tail` command won't work; we must instead query via `docker logs`:
if [[ ${SERVICE} == 'mailserver' ]]
then
CMD_LOGS=(docker logs "${TEST_NAME}")
CMD_LOGS=(docker logs "${CONTAINER_NAME}")
fi
echo "${CMD_LOGS[@]}"

View file

@ -1,8 +1,11 @@
#!/usr/bin/env bats
load "${REPOSITORY_ROOT}/test/test_helper/common"
load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
TEST_NAME_PREFIX='[Security] TLS (SSL_TYPE=manual):'
CONTAINER_NAME='dms-test_tls-manual'
function setup_file() {
# Internal copies made by `start-mailserver.sh`:
# Internal copies made by `scripts/helpers/ssl.sh`:
export PRIMARY_KEY='/etc/dms/tls/key'
export PRIMARY_CERT='/etc/dms/tls/cert'
export FALLBACK_KEY='/etc/dms/tls/fallback_key'
@ -14,98 +17,101 @@ function setup_file() {
export SSL_ALT_KEY_PATH='/config/ssl/key.rsa.pem'
export SSL_ALT_CERT_PATH='/config/ssl/cert.rsa.pem'
local PRIVATE_CONFIG
export DOMAIN_SSL_MANUAL='example.test'
PRIVATE_CONFIG=$(duplicate_config_for_container .)
export TEST_DOMAIN='example.test'
docker run -d --name mail_manual_ssl \
--volume "${PRIVATE_CONFIG}/:/tmp/docker-mailserver/" \
--volume "$(pwd)/test/test-files/ssl/${DOMAIN_SSL_MANUAL}/with_ca/ecdsa/:/config/ssl/:ro" \
--env LOG_LEVEL='trace' \
--env SSL_TYPE='manual' \
--env TLS_LEVEL='modern' \
--env SSL_KEY_PATH="${SSL_KEY_PATH}" \
--env SSL_CERT_PATH="${SSL_CERT_PATH}" \
--env SSL_ALT_KEY_PATH="${SSL_ALT_KEY_PATH}" \
--env SSL_ALT_CERT_PATH="${SSL_ALT_CERT_PATH}" \
--hostname "mail.${DOMAIN_SSL_MANUAL}" \
--tty \
"${NAME}" # Image name
local CUSTOM_SETUP_ARGUMENTS=(
--volume "${PWD}/test/test-files/ssl/${TEST_DOMAIN}/with_ca/ecdsa/:/config/ssl/:ro"
--env LOG_LEVEL='trace'
--env SSL_TYPE='manual'
--env TLS_LEVEL='modern'
--env SSL_KEY_PATH="${SSL_KEY_PATH}"
--env SSL_CERT_PATH="${SSL_CERT_PATH}"
--env SSL_ALT_KEY_PATH="${SSL_ALT_KEY_PATH}"
--env SSL_ALT_CERT_PATH="${SSL_ALT_CERT_PATH}"
)
wait_for_finished_setup_in_container mail_manual_ssl
init_with_defaults
# Override the default set in `common_container_setup`:
export TEST_FQDN="mail.${TEST_DOMAIN}"
common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
}
function teardown_file() {
docker rm -f mail_manual_ssl
function teardown_file() { _default_teardown ; }
@test "${TEST_NAME_PREFIX} ENV vars provided are valid files" {
_run_in_container [ -f "${SSL_CERT_PATH}" ]
assert_success
_run_in_container [ -f "${SSL_KEY_PATH}" ]
assert_success
_run_in_container [ -f "${SSL_ALT_CERT_PATH}" ]
assert_success
_run_in_container [ -f "${SSL_ALT_KEY_PATH}" ]
assert_success
}
@test "checking ssl: ENV vars provided are valid files" {
assert docker exec mail_manual_ssl [ -f "${SSL_CERT_PATH}" ]
assert docker exec mail_manual_ssl [ -f "${SSL_KEY_PATH}" ]
assert docker exec mail_manual_ssl [ -f "${SSL_ALT_CERT_PATH}" ]
assert docker exec mail_manual_ssl [ -f "${SSL_ALT_KEY_PATH}" ]
}
@test "checking ssl: manual configuration is correct" {
@test "${TEST_NAME_PREFIX} manual configuration is correct" {
local DOVECOT_CONFIG_SSL='/etc/dovecot/conf.d/10-ssl.conf'
run docker exec mail_manual_ssl grep '^smtpd_tls_chain_files =' '/etc/postfix/main.cf'
_run_in_container grep '^smtpd_tls_chain_files =' '/etc/postfix/main.cf'
assert_success
assert_output "smtpd_tls_chain_files = ${PRIMARY_KEY} ${PRIMARY_CERT} ${FALLBACK_KEY} ${FALLBACK_CERT}"
run docker exec mail_manual_ssl grep '^ssl_key =' "${DOVECOT_CONFIG_SSL}"
_run_in_container grep '^ssl_key =' "${DOVECOT_CONFIG_SSL}"
assert_success
assert_output "ssl_key = <${PRIMARY_KEY}"
run docker exec mail_manual_ssl grep '^ssl_cert =' "${DOVECOT_CONFIG_SSL}"
_run_in_container grep '^ssl_cert =' "${DOVECOT_CONFIG_SSL}"
assert_success
assert_output "ssl_cert = <${PRIMARY_CERT}"
run docker exec mail_manual_ssl grep '^ssl_alt_key =' "${DOVECOT_CONFIG_SSL}"
_run_in_container grep '^ssl_alt_key =' "${DOVECOT_CONFIG_SSL}"
assert_success
assert_output "ssl_alt_key = <${FALLBACK_KEY}"
run docker exec mail_manual_ssl grep '^ssl_alt_cert =' "${DOVECOT_CONFIG_SSL}"
_run_in_container grep '^ssl_alt_cert =' "${DOVECOT_CONFIG_SSL}"
assert_success
assert_output "ssl_alt_cert = <${FALLBACK_CERT}"
}
@test "checking ssl: manual configuration copied files correctly " {
run docker exec mail_manual_ssl cmp -s "${PRIMARY_KEY}" "${SSL_KEY_PATH}"
@test "${TEST_NAME_PREFIX} manual configuration copied files correctly " {
_run_in_container cmp -s "${PRIMARY_KEY}" "${SSL_KEY_PATH}"
assert_success
run docker exec mail_manual_ssl cmp -s "${PRIMARY_CERT}" "${SSL_CERT_PATH}"
_run_in_container cmp -s "${PRIMARY_CERT}" "${SSL_CERT_PATH}"
assert_success
# Fallback cert
run docker exec mail_manual_ssl cmp -s "${FALLBACK_KEY}" "${SSL_ALT_KEY_PATH}"
_run_in_container cmp -s "${FALLBACK_KEY}" "${SSL_ALT_KEY_PATH}"
assert_success
run docker exec mail_manual_ssl cmp -s "${FALLBACK_CERT}" "${SSL_ALT_CERT_PATH}"
_run_in_container cmp -s "${FALLBACK_CERT}" "${SSL_ALT_CERT_PATH}"
assert_success
}
@test "checking ssl: manual cert works correctly" {
wait_for_tcp_port_in_container 587 mail_manual_ssl
@test "${TEST_NAME_PREFIX} manual cert works correctly" {
wait_for_tcp_port_in_container 587 "${CONTAINER_NAME}"
local TEST_COMMAND=(timeout 1 openssl s_client -connect mail.example.test:587 -starttls smtp)
local RESULT
# Should fail as a chain of trust is required to verify successfully:
RESULT=$(docker exec mail_manual_ssl "${TEST_COMMAND[@]}" | grep 'Verification error:')
RESULT=$(docker exec "${CONTAINER_NAME}" "${TEST_COMMAND[@]}" | grep 'Verification error:')
assert_equal "${RESULT}" 'Verification error: unable to verify the first certificate'
# Provide the Root CA cert for successful verification:
local CA_CERT='/config/ssl/ca-cert.ecdsa.pem'
assert docker exec mail_manual_ssl [ -f "${CA_CERT}" ]
RESULT=$(docker exec mail_manual_ssl "${TEST_COMMAND[@]}" -CAfile "${CA_CERT}" | grep 'Verification: OK')
assert docker exec "${CONTAINER_NAME}" [ -f "${CA_CERT}" ]
RESULT=$(docker exec "${CONTAINER_NAME}" "${TEST_COMMAND[@]}" -CAfile "${CA_CERT}" | grep 'Verification: OK')
assert_equal "${RESULT}" 'Verification: OK'
}
@test "checking ssl: manual cert changes are picked up by check-for-changes" {
@test "${TEST_NAME_PREFIX} manual cert changes are picked up by check-for-changes" {
printf '%s' 'someThingsChangedHere' \
>>"$(pwd)/test/test-files/ssl/${DOMAIN_SSL_MANUAL}/with_ca/ecdsa/key.ecdsa.pem"
>>"$(pwd)/test/test-files/ssl/${TEST_DOMAIN}/with_ca/ecdsa/key.ecdsa.pem"
run timeout 15 docker exec mail_manual_ssl bash -c "tail -F /var/log/supervisor/changedetector.log | sed '/Manual certificates have changed/ q'"
run timeout 15 docker exec "${CONTAINER_NAME}" bash -c "tail -F /var/log/supervisor/changedetector.log | sed '/Manual certificates have changed/ q'"
assert_success
sed -i '/someThingsChangedHere/d' "$(pwd)/test/test-files/ssl/${DOMAIN_SSL_MANUAL}/with_ca/ecdsa/key.ecdsa.pem"
sed -i '/someThingsChangedHere/d' "$(pwd)/test/test-files/ssl/${TEST_DOMAIN}/with_ca/ecdsa/key.ecdsa.pem"
}

View file

@ -2,9 +2,14 @@ load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
TEST_NAME_PREFIX='Dovecot protocols:'
CONTAINER1_NAME='dms-test_dovecot_protocols_all'
CONTAINER2_NAME='dms-test_dovecot_protocols_ipv4'
CONTAINER3_NAME='dms-test_dovecot_protocols_ipv6'
function teardown() { _default_teardown ; }
@test "${TEST_NAME_PREFIX} dual-stack IP configuration" {
local CONTAINER_NAME='dms-test-dovecot_protocols_all'
export CONTAINER_NAME=${CONTAINER1_NAME}
local CUSTOM_SETUP_ARGUMENTS=(--env DOVECOT_INET_PROTOCOLS=)
init_with_defaults
@ -13,12 +18,10 @@ TEST_NAME_PREFIX='Dovecot protocols:'
_run_in_container grep '^#listen = \*, ::' /etc/dovecot/dovecot.conf
assert_success
assert_output '#listen = *, ::'
docker rm -f "${CONTAINER_NAME}"
}
@test "${TEST_NAME_PREFIX} IPv4 configuration" {
local CONTAINER_NAME='dms-test-dovecot_protocols_ipv4'
export CONTAINER_NAME=${CONTAINER2_NAME}
local CUSTOM_SETUP_ARGUMENTS=(--env DOVECOT_INET_PROTOCOLS=ipv4)
init_with_defaults
@ -27,12 +30,10 @@ TEST_NAME_PREFIX='Dovecot protocols:'
_run_in_container grep '^listen = \*$' /etc/dovecot/dovecot.conf
assert_success
assert_output 'listen = *'
docker rm -f "${CONTAINER_NAME}"
}
@test "${TEST_NAME_PREFIX} IPv6 configuration" {
local CONTAINER_NAME='dms-test-dovecot_protocols_ipv6'
export CONTAINER_NAME=${CONTAINER3_NAME}
local CUSTOM_SETUP_ARGUMENTS=(--env DOVECOT_INET_PROTOCOLS=ipv6)
init_with_defaults
@ -41,6 +42,4 @@ TEST_NAME_PREFIX='Dovecot protocols:'
_run_in_container grep '^listen = \[::\]$' /etc/dovecot/dovecot.conf
assert_success
assert_output 'listen = [::]'
docker rm -f "${CONTAINER_NAME}"
}

View file

@ -2,7 +2,7 @@ load "${REPOSITORY_ROOT}/test/helper/setup"
load "${REPOSITORY_ROOT}/test/helper/common"
TEST_NAME_PREFIX='helper functions inside container:'
CONTAINER_NAME='dms-test-helper_functions'
CONTAINER_NAME='dms-test_helper_functions'
function setup_file() {
init_with_defaults