diff --git a/Dockerfile b/Dockerfile index d160e323..8b180e6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -212,7 +212,7 @@ RUN curl -s https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > /et COPY ./target/bin /usr/local/bin # Start-mailserver script -COPY ./target/check-for-changes.sh ./target/start-mailserver.sh ./target/fail2ban-wrapper.sh ./target/postfix-wrapper.sh ./target/postsrsd-wrapper.sh ./target/docker-configomat/configomat.sh /usr/local/bin/ +COPY ./target/helper_functions.sh ./target/check-for-changes.sh ./target/start-mailserver.sh ./target/fail2ban-wrapper.sh ./target/postfix-wrapper.sh ./target/postsrsd-wrapper.sh ./target/docker-configomat/configomat.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/* # Configure supervisor diff --git a/Makefile b/Makefile index 4475ff4a..3892edd8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ NAME = tvial/docker-mailserver:testing -NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME=non-default-docker-mail-network all: build-no-cache backup generate-accounts run generate-accounts-after-run fixtures tests clean all-fast: build backup generate-accounts run generate-accounts-after-run fixtures tests clean @@ -27,23 +26,6 @@ generate-accounts: docker run --rm -e MAIL_USER=user2@otherdomain.tld -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf run: - docker network create --driver bridge --subnet 192.168.13.0/24 $(NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME) - docker network create --driver bridge --subnet 192.168.37.0/24 $(NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME)2 - # use two networks (default ("bridge") and our custom network) to recreate problematic test case where PERMIT_DOCKER=host would not help - # currently we cannot use --network in `docker run` multiple times, it will just use the last one - # instead we need to use create, network connect and start (see https://success.docker.com/article/multiple-docker-networks) - docker create --name mail_smtponly_second_network \ - -v "`pwd`/test/config":/tmp/docker-mailserver \ - -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ - -e SMTP_ONLY=1 \ - -e PERMIT_DOCKER=connected-networks \ - -e DMS_DEBUG=0 \ - -e OVERRIDE_HOSTNAME=mail.my-domain.com \ - --network $(NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME) \ - -t $(NAME) - docker network connect $(NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME)2 mail_smtponly_second_network - docker start mail_smtponly_second_network - sleep 15 # Run containers docker run -d --name mail \ -v "`pwd`/test/config":/tmp/docker-mailserver \ @@ -311,7 +293,12 @@ fixtures: tests: # Start tests - ./test/bats/bin/bats test/tests.bats + ./test/bats/bin/bats test/*.bats + +.PHONY: ALWAYS_RUN + +test/%.bats: ALWAYS_RUN + ./test/bats/bin/bats $@ clean: # Remove running test containers @@ -337,10 +324,8 @@ clean: mail_domainname \ mail_srs_domainname \ mail_with_relays \ - mail_with_default_relay \ - mail_smtponly_second_network + mail_with_default_relay - -docker network rm ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME} ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2 @if [ -d config.bak ]; then\ rm -rf config ;\ mv config.bak config ;\ diff --git a/target/helper_functions.sh b/target/helper_functions.sh new file mode 100644 index 00000000..207daef1 --- /dev/null +++ b/target/helper_functions.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# expects mask prefix length and the digit +function _mask_ip_digit() { + if [[ $1 -ge 8 ]]; then + MASK=255 + else + if [[ $1 -le 0 ]]; then + MASK=0 + else + VALUES=('0' '128' '192' '224' '240' '248' '252' '254' '255') + MASK=${VALUES[$1]} + fi + fi + echo $(( $2 & $MASK )) +} + +# transforms a specific ip with CIDR suffix like 1.2.3.4/16 +# to subnet with cidr suffix like 1.2.0.0/16 +function _sanitize_ipv4_to_subnet_cidr() { + IP=${1%%/*} + PREFIX_LENGTH=${1#*/} + + # split IP by . into digits + DIGITS=(${IP//./ }) + + # mask digits according to prefix length + MASKED_DIGITS=() + DIGIT_PREFIX_LENGTH="$PREFIX_LENGTH" + for DIGIT in "${DIGITS[@]}" ; do + MASKED_DIGITS+=( $(_mask_ip_digit $DIGIT_PREFIX_LENGTH $DIGIT) ) + DIGIT_PREFIX_LENGTH=$(( $DIGIT_PREFIX_LENGTH - 8 )) + done + + # output masked ip plus prefix length + echo ${MASKED_DIGITS[0]}.${MASKED_DIGITS[1]}.${MASKED_DIGITS[2]}.${MASKED_DIGITS[3]}/$PREFIX_LENGTH +} \ No newline at end of file diff --git a/target/start-mailserver.sh b/target/start-mailserver.sh index a2651630..784acffc 100644 --- a/target/start-mailserver.sh +++ b/target/start-mailserver.sh @@ -1040,6 +1040,7 @@ function _setup_docker_permit() { ;; "connected-networks" ) for network in $container_networks; do + network=$(_sanitize_ipv4_to_subnet_cidr $network) notify 'inf' "Adding docker network $network in my networks" postconf -e "$(postconf | grep '^mynetworks =') $network" echo $network >> /etc/opendmarc/ignore.hosts @@ -1611,6 +1612,8 @@ function _start_changedetector() { # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # >> +. /usr/local/bin/helper_functions.sh + if [[ ${DEFAULT_VARS["DMS_DEBUG"]} == 1 ]]; then notify 'taskgrp' "" notify 'taskgrp' "#" diff --git a/test/helper_functions.bats b/test/helper_functions.bats new file mode 100644 index 00000000..49f1819f --- /dev/null +++ b/test/helper_functions.bats @@ -0,0 +1,14 @@ +load 'test_helper/bats-support/load' +load 'test_helper/bats-assert/load' + +# load the helper function into current context +. ./target/helper_functions.sh + +@test "check helper function: _sanitize_ipv4_to_subnet_cidr" { + output=$(_sanitize_ipv4_to_subnet_cidr 255.255.255.255/0) + assert_output "0.0.0.0/0" + output=$(_sanitize_ipv4_to_subnet_cidr 192.168.255.14/20) + assert_output "192.168.240.0/20" + output=$(_sanitize_ipv4_to_subnet_cidr 192.168.255.14/32) + assert_output "192.168.255.14/32" +} \ No newline at end of file diff --git a/test/permit_docker.bats b/test/permit_docker.bats new file mode 100644 index 00000000..982ecc37 --- /dev/null +++ b/test/permit_docker.bats @@ -0,0 +1,77 @@ +load 'test_helper/bats-support/load' +load 'test_helper/bats-assert/load' + +NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME=non-default-docker-mail-network +NAME=tvial/docker-mailserver:testing +setup() { + docker network create --driver bridge ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME} + docker network create --driver bridge ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2 + # use two networks (default ("bridge") and our custom network) to recreate problematic test case where PERMIT_DOCKER=host would not help + # currently we cannot use --network in `docker run` multiple times, it will just use the last one + # instead we need to use create, network connect and start (see https://success.docker.com/article/multiple-docker-networks) + docker create --name mail_smtponly_second_network \ + -v "`pwd`/test/config":/tmp/docker-mailserver \ + -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ + -e SMTP_ONLY=1 \ + -e PERMIT_DOCKER=connected-networks \ + -e DMS_DEBUG=0 \ + -e OVERRIDE_HOSTNAME=mail.my-domain.com \ + --network ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME} \ + -t ${NAME} + docker network connect ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2 mail_smtponly_second_network + docker start mail_smtponly_second_network + docker run -d --name mail_smtponly_second_network_sender \ + -v "`pwd`/test/config":/tmp/docker-mailserver \ + -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ + -e SMTP_ONLY=1 \ + -e PERMIT_DOCKER=connected-networks \ + -e DMS_DEBUG=0 \ + -e OVERRIDE_HOSTNAME=mail.my-domain.com \ + --network ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2 \ + -t ${NAME} + + # wait until postfix is up + STARTTIME=$SECONDS + until docker exec mail_smtponly_second_network /bin/sh -c "nc -z 0.0.0.0 25" + do + sleep 5 + if [[ $(($SECONDS - $STARTTIME )) -gt 60 ]]; then + echo "Waiting for server timed out after $(($SECONDS - $STARTTIME )) seconds" + exit 1 + fi + done +} + +teardown() { + docker logs mail_smtponly_second_network + docker rm -f mail_smtponly_second_network \ + mail_smtponly_second_network_sender + docker network rm ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME} ${NON_DEFAULT_DOCKER_MAIL_NETWORK_NAME}2 +} + +function repeat_until_success_or_timeout { + TIMEOUT=$1 + STARTTIME=$SECONDS + shift 1 + until "$@" + do + sleep 5 + if [[ $(($SECONDS - $STARTTIME )) -gt $TIMEOUT ]]; then + echo "Timed out on command: $@" + exit 1 + fi + done +} + +@test "checking PERMIT_DOCKER: connected-networks" { + run docker exec mail_smtponly_second_network /bin/sh -c "postconf -e smtp_host_lookup=no" + assert_success + run docker exec mail_smtponly_second_network /bin/sh -c "/etc/init.d/postfix reload" + assert_success + # we should be able to send from the other container on the second network! + run docker exec mail_smtponly_second_network_sender /bin/sh -c "nc mail_smtponly_second_network 25 < /tmp/docker-mailserver-test/email-templates/smtp-only.txt" + assert_output --partial "250 2.0.0 Ok: queued as " + + repeat_until_success_or_timeout 60 run docker exec mail_smtponly_second_network /bin/sh -c 'grep -cE "to=.*status\=sent" /var/log/mail/mail.log' + [ "$status" -ge 0 ] +} \ No newline at end of file diff --git a/test/tests.bats b/test/tests.bats index 1deed0e8..e65e6693 100644 --- a/test/tests.bats +++ b/test/tests.bats @@ -1232,12 +1232,6 @@ function count_processed_changes() { assert_success } -@test "checking PERMIT_DOCKER: connected-networks" { - run docker exec mail_smtponly_second_network /bin/sh -c "postconf | grep '^mynetworks ='" - assert_output --regexp "192\.168\.13\.[0-9]{1,3}\/24" - assert_output --regexp '192.168.37.[0-9]{1,3}/24' -} - # # amavis #