mirror of
https://github.com/docker-mailserver/docker-mailserver.git
synced 2025-08-04 18:15:29 +02:00
tests(feat): Complete rewrite of letsencrypt tests (#2286)
* chore: Normalize container setup Easier to grok what is different between configurations. - Container name usage replaced with variable - Volumes defined earlier and redeclared when relevant (only real difference is `VOLUME_LETSENCRYPT`) - Contextual comment about the `acme.json` copy. - Quoting `SSL_TYPE`, `SSL_DOMAIN` and `-h` values for syntax highlighting. - Moved `-t` and `${NAME}` to separate line. - Consistent indentation. * chore: DRY test logic Extracts out repeated test logic into methods * chore: Scope configs to individual test cases (1/3) - Preparation step for shifting out the container configs to their own scoped test cases. Split into multiple commits to ease reviewing by diffs for this change. - Re-arrange the hostname and domain configs to match the expected order of the new test cases. - Shuffle the hostname and domainname grouped tests into tests per container config scope. - Collapse the `acme.json` test cases into single test case. * chore: Scope configs to individual test cases (2/3) - Shifts the hostname and domainname container configs into their respective scoped test cases. - Moving the `acme.json` container config produces a less favorable diff, so is deferred to a follow-up commit. - Test cases updated to refer to their `${CONTAINER_NAME}` var instead of the hard-coded string name. * chore: Scope configs to individual test cases (3/3) Final commit to shift out the container configs. - Common vars are exported in `setup_file()` for the test cases to use without needing to repeat the declaration in each test case. - `teardown_file()` shifts container removal at end of scoped test case. * chore: Adapt to `common_container_setup` template - `CONTAINER_NAME` becomes `TEST_NAME` (`common.bash` helper via `init_with_defaults`). - `docker run ...` and related configuration is now outsourced to the `common.bash` helper, only extra args that the default template does not cover are defined in the test case. - `TARGET_DOMAIN`establishes the domain folder name for `/etc/letsencrypt/live`. - `_should*` methods no longer manage a `CONTAINER_NAME` arg, instead using the `TEST_NAME` global that should be valid as test is run as a sequence of test cases. - `PRIVATE_CONFIG` and the `private_config_path ...` are now using the global `TEST_TMP_CONFIG` initialized at the start of each test case, slightly different as not locally defined/scoped like `PRIVATE_CONFIG` would be within the test case, hence the explicit choice of a different name for context. * chore: Minor tweaks - Test case comment descriptions. - DRY: `docker rm -f` lines moved to `teardown()` - Use `wait_for_service` helper instead of checking the `changedetector` script itself is running. - There is a startup delay before the `changedetector` begins monitoring, wait until it ready event is logged. - Added a helper to query logs for a service (useful later). - `/bin/sh` commands reduced to `sh`. - Change the config check to match and compare output, not number of lines returned. Provides better failure output by bats to debug against. * chore: Add more test functions for `acme.json` This just extracts out existing logic from the test case to functions to make the test case itself more readable/terse. * chore: Housekeeping No changes, just moving logic around and grouping into inline functions, with some added comments. * chore: Switch to `example.test` certs This also required copying the source files to match the expected letsencrypt file structure expected in the test/container usage. * chore: Delete `test/config/letsencrypt/` No longer necessary, using the `example.test/` certs instead. These letsencrypt certs weren't for the domains they were used for, and of course long expired. * chore: Housekeeping Add more maintainer comments, rename some functions. * tests: Expand `acme.json` extraction coverage Finally able to add more test coverage! :) - Two new methods to validate expected success/failure of extraction for a given FQDN. - Added an RSA test prior to the wildcard to test a renewal simulation (just with different cert type). - Added extra method to make sure we're detecting multiple successful change events, not just a previous logged success (false positive). * tests: Refactor the negotiate_tls functionality Covers all ports (except POP) and correctly tests against expected verification status with new `example.test` certs. The `FQDN` var will be put to use in a follow-up commit. * tests: Verify the certs contain the expected FQDNs * chore: Extract TLS test methods into a separate helper script Can be useful for other TLS tests to utilize. * chore: Housekeeping * chore: Fix test typo There was a mismatch between the output and expected output between these two files "find key for" and "find key & cert for". Changed to "find key and/or cert for" to make the warning more clear that it's issued for either or both failure conditions. Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
This commit is contained in:
parent
584577787a
commit
7ca056852f
16 changed files with 431 additions and 529 deletions
|
@ -1,133 +1,331 @@
|
|||
load 'test_helper/common'
|
||||
load 'test_helper/tls'
|
||||
|
||||
# Globals referenced from `test_helper/common`:
|
||||
# TEST_NAME TEST_FQDN TEST_TMP_CONFIG
|
||||
|
||||
# Requires maintenance (TODO): Yes
|
||||
# Can run tests in parallel?: No
|
||||
|
||||
# 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() {
|
||||
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`.
|
||||
# `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).
|
||||
|
||||
# `mail.example.test` (Only this FQDN is supported by this certificate):
|
||||
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/cert.ecdsa.pem' 'mail.example.test/fullchain.pem'
|
||||
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/key.ecdsa.pem' "mail.example.test/privkey.pem"
|
||||
|
||||
# `example.test` (Only this FQDN is supported by this certificate):
|
||||
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/cert.rsa.pem' 'example.test/fullchain.pem'
|
||||
_copy_to_letsencrypt_storage 'example.test/with_ca/ecdsa/key.rsa.pem' 'example.test/privkey.pem'
|
||||
}
|
||||
|
||||
# Not used
|
||||
# function teardown_file() {
|
||||
# }
|
||||
|
||||
# Applies per test:
|
||||
function setup() {
|
||||
run_setup_file_if_necessary
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
docker rm -f "${TEST_NAME}"
|
||||
run_teardown_file_if_necessary
|
||||
}
|
||||
|
||||
function setup_file() {
|
||||
local PRIVATE_CONFIG
|
||||
|
||||
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_lets_domain)"
|
||||
docker run -d --name mail_lets_domain \
|
||||
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
|
||||
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
|
||||
-v "${PRIVATE_CONFIG}/letsencrypt/my-domain.com":/etc/letsencrypt/live/my-domain.com \
|
||||
-e DMS_DEBUG=0 \
|
||||
-e SSL_TYPE=letsencrypt \
|
||||
-h mail.my-domain.com -t "${NAME}"
|
||||
wait_for_finished_setup_in_container mail_lets_domain
|
||||
|
||||
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_lets_hostname)"
|
||||
docker run -d --name mail_lets_hostname \
|
||||
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
|
||||
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
|
||||
-v "${PRIVATE_CONFIG}/letsencrypt/mail.my-domain.com":/etc/letsencrypt/live/mail.my-domain.com \
|
||||
-e DMS_DEBUG=0 \
|
||||
-e SSL_TYPE=letsencrypt \
|
||||
-h mail.my-domain.com -t "${NAME}"
|
||||
wait_for_finished_setup_in_container mail_lets_hostname
|
||||
|
||||
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_lets_acme_json)"
|
||||
cp "$(private_config_path mail_lets_acme_json)/letsencrypt/acme.json" "$(private_config_path mail_lets_acme_json)/acme.json"
|
||||
docker run -d --name mail_lets_acme_json \
|
||||
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
|
||||
-v "${PRIVATE_CONFIG}/acme.json":/etc/letsencrypt/acme.json:ro \
|
||||
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
|
||||
-e DMS_DEBUG=1 \
|
||||
-e SSL_TYPE=letsencrypt \
|
||||
-e "SSL_DOMAIN=*.example.com" \
|
||||
-h mail.my-domain.com -t "${NAME}"
|
||||
|
||||
wait_for_finished_setup_in_container mail_lets_acme_json
|
||||
}
|
||||
|
||||
function teardown_file() {
|
||||
docker rm -f mail_lets_domain
|
||||
docker rm -f mail_lets_hostname
|
||||
docker rm -f mail_lets_acme_json
|
||||
}
|
||||
|
||||
# this test must come first to reliably identify when to run setup_file
|
||||
@test "first" {
|
||||
skip 'Starting testing of letsencrypt SSL'
|
||||
}
|
||||
|
||||
@test "checking ssl: letsencrypt configuration is correct" {
|
||||
#test domain has certificate files
|
||||
run docker exec mail_lets_domain /bin/sh -c 'postconf | grep "smtpd_tls_chain_files = /etc/letsencrypt/live/my-domain.com/key.pem /etc/letsencrypt/live/my-domain.com/fullchain.pem" | wc -l'
|
||||
assert_success
|
||||
assert_output 1
|
||||
run docker exec mail_lets_domain /bin/sh -c 'doveconf | grep "ssl_cert = </etc/letsencrypt/live/my-domain.com/fullchain.pem" | wc -l'
|
||||
assert_success
|
||||
assert_output 1
|
||||
run docker exec mail_lets_domain /bin/sh -c 'doveconf -P | grep "ssl_key = </etc/letsencrypt/live/my-domain.com/key.pem" | wc -l'
|
||||
assert_success
|
||||
assert_output 1
|
||||
|
||||
# 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'
|
||||
|
||||
local TEST_DOCKER_ARGS=(
|
||||
--volume "${TEST_TMP_CONFIG}/letsencrypt/${TARGET_DOMAIN}/:/etc/letsencrypt/live/${TARGET_DOMAIN}/:ro"
|
||||
--env SSL_TYPE='letsencrypt'
|
||||
)
|
||||
|
||||
common_container_setup 'TEST_DOCKER_ARGS'
|
||||
|
||||
#test hostname has certificate files
|
||||
run docker exec mail_lets_hostname /bin/sh -c 'postconf | grep "smtpd_tls_chain_files = /etc/letsencrypt/live/mail.my-domain.com/privkey.pem /etc/letsencrypt/live/mail.my-domain.com/fullchain.pem" | wc -l'
|
||||
assert_success
|
||||
assert_output 1
|
||||
run docker exec mail_lets_hostname /bin/sh -c 'doveconf | grep "ssl_cert = </etc/letsencrypt/live/mail.my-domain.com/fullchain.pem" | wc -l'
|
||||
assert_success
|
||||
assert_output 1
|
||||
run docker exec mail_lets_hostname /bin/sh -c 'doveconf -P | grep "ssl_key = </etc/letsencrypt/live/mail.my-domain.com/privkey.pem" | wc -l'
|
||||
assert_success
|
||||
assert_output 1
|
||||
_should_have_valid_config "${TARGET_DOMAIN}" 'privkey.pem' 'fullchain.pem'
|
||||
_should_succesfully_negotiate_tls "${TARGET_DOMAIN}"
|
||||
_should_not_have_fqdn_in_cert 'example.test'
|
||||
}
|
||||
|
||||
@test "checking ssl: letsencrypt cert works correctly" {
|
||||
run docker exec mail_lets_domain /bin/sh -c "timeout 1 openssl s_client -connect 0.0.0.0:587 -starttls smtp -CApath /etc/ssl/certs/ | grep 'Verify return code: 10 (certificate has expired)'"
|
||||
assert_success
|
||||
run docker exec mail_lets_domain /bin/sh -c "timeout 1 openssl s_client -connect 0.0.0.0:465 -CApath /etc/ssl/certs/ | grep 'Verify return code: 10 (certificate has expired)'"
|
||||
assert_success
|
||||
run docker exec mail_lets_hostname /bin/sh -c "timeout 1 openssl s_client -connect 0.0.0.0:587 -starttls smtp -CApath /etc/ssl/certs/ | grep 'Verify return code: 10 (certificate has expired)'"
|
||||
assert_success
|
||||
run docker exec mail_lets_hostname /bin/sh -c "timeout 1 openssl s_client -connect 0.0.0.0:465 -CApath /etc/ssl/certs/ | grep 'Verify return code: 10 (certificate has expired)'"
|
||||
assert_success
|
||||
|
||||
# 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'
|
||||
|
||||
local TEST_DOCKER_ARGS=(
|
||||
--volume "${TEST_TMP_CONFIG}/letsencrypt/${TARGET_DOMAIN}/:/etc/letsencrypt/live/${TARGET_DOMAIN}/:ro"
|
||||
--env SSL_TYPE='letsencrypt'
|
||||
)
|
||||
|
||||
common_container_setup 'TEST_DOCKER_ARGS'
|
||||
|
||||
#test domain has certificate files
|
||||
_should_have_valid_config "${TARGET_DOMAIN}" 'privkey.pem' 'fullchain.pem'
|
||||
_should_succesfully_negotiate_tls "${TARGET_DOMAIN}"
|
||||
_should_not_have_fqdn_in_cert 'mail.example.test'
|
||||
}
|
||||
|
||||
|
||||
# When using `acme.json` (Traefik) - a wildcard cert `*.example.test` (SSL_DOMAIN)
|
||||
# should be extracted and be chosen over an existing FQDN `mail.example.test` (HOSTNAME):
|
||||
# _acme_wildcard should verify the FQDN `mail.example.test` is negotiated, not `example.test`.
|
||||
#
|
||||
# acme.json updates
|
||||
#
|
||||
# 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 should not support `example.test`.
|
||||
# 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"
|
||||
|
||||
@test "checking changedetector: server is ready" {
|
||||
run docker exec mail_lets_acme_json /bin/bash -c "ps aux | grep '/bin/bash /usr/local/bin/check-for-changes.sh'"
|
||||
assert_success
|
||||
}
|
||||
# Change default Root CA cert used for verifying chain of trust with openssl:
|
||||
# shellcheck disable=SC2034
|
||||
local TEST_CA_CERT="${TEST_FILES_CONTAINER_PATH}/ssl/example.test/with_ca/rsa/ca-cert.rsa.pem"
|
||||
|
||||
@test "can extract certs from acme.json" {
|
||||
run docker exec mail_lets_acme_json /bin/bash -c "cat /etc/letsencrypt/live/mail.my-domain.com/key.pem"
|
||||
assert_output "$(cat "$(private_config_path mail_lets_acme_json)/letsencrypt/mail.my-domain.com/privkey.pem")"
|
||||
assert_success
|
||||
function _prepare() {
|
||||
# Default `acme.json` for _acme_ecdsa test:
|
||||
cp "${LOCAL_BASE_PATH}/ecdsa.acme.json" "${TEST_TMP_CONFIG}/letsencrypt/acme.json"
|
||||
|
||||
run docker exec mail_lets_acme_json /bin/bash -c "cat /etc/letsencrypt/live/mail.my-domain.com/fullchain.pem"
|
||||
assert_output "$(cat "$(private_config_path mail_lets_acme_json)/letsencrypt/mail.my-domain.com/fullchain.pem")"
|
||||
assert_success
|
||||
}
|
||||
# TODO: Provision wildcard certs via Traefik to inspect if `example.test` non-wildcard is also added to the cert.
|
||||
# `DMS_DEBUG=1` required for catching logged `inf` output.
|
||||
# shellcheck disable=SC2034
|
||||
local TEST_DOCKER_ARGS=(
|
||||
--volume "${TEST_TMP_CONFIG}/letsencrypt/acme.json:/etc/letsencrypt/acme.json:ro"
|
||||
--env SSL_TYPE='letsencrypt'
|
||||
--env SSL_DOMAIN='*.example.test'
|
||||
--env DMS_DEBUG=1
|
||||
)
|
||||
|
||||
@test "can detect changes" {
|
||||
cp "$(private_config_path mail_lets_acme_json)/letsencrypt/acme-changed.json" "$(private_config_path mail_lets_acme_json)/acme.json"
|
||||
sleep 11
|
||||
run docker exec mail_lets_acme_json /bin/bash -c "supervisorctl tail changedetector"
|
||||
assert_output --partial "postfix: stopped"
|
||||
assert_output --partial "postfix: started"
|
||||
assert_output --partial "Change detected"
|
||||
common_container_setup 'TEST_DOCKER_ARGS'
|
||||
wait_for_service "${TEST_NAME}" 'changedetector'
|
||||
|
||||
run docker exec mail_lets_acme_json /bin/bash -c "cat /etc/letsencrypt/live/example.com/key.pem"
|
||||
assert_output "$(cat "$(private_config_path mail_lets_acme_json)/letsencrypt/changed/key.pem")"
|
||||
assert_success
|
||||
# Wait until the changedetector service startup delay is over:
|
||||
repeat_until_success_or_timeout 20 sh -c "$(_get_service_logs 'changedetector') | grep 'check-for-changes is ready'"
|
||||
}
|
||||
|
||||
run docker exec mail_lets_acme_json /bin/bash -c "cat /etc/letsencrypt/live/example.com/fullchain.pem"
|
||||
assert_output "$(cat "$(private_config_path mail_lets_acme_json)/letsencrypt/changed/fullchain.pem")"
|
||||
assert_success
|
||||
# Test `acme.json` extraction works at container startup:
|
||||
# It should have already extracted `mail.example.test` from the original mounted `acme.json`.
|
||||
function _acme_ecdsa() {
|
||||
_should_have_succeeded_at_extraction 'mail.example.test'
|
||||
|
||||
# SSL_DOMAIN set as ENV, but startup should not have match in `acme.json`:
|
||||
_should_have_failed_at_extraction '*.example.test' 'mailserver'
|
||||
_should_have_valid_config 'mail.example.test' 'key.pem' 'fullchain.pem'
|
||||
|
||||
local ECDSA_KEY_PATH="${LOCAL_BASE_PATH}/key.ecdsa.pem"
|
||||
local ECDSA_CERT_PATH="${LOCAL_BASE_PATH}/cert.ecdsa.pem"
|
||||
_should_have_expected_files 'mail.example.test' "${ECDSA_KEY_PATH}" "${ECDSA_CERT_PATH}"
|
||||
}
|
||||
|
||||
# Test `acme.json` extraction is triggered via change detection:
|
||||
# The updated `acme.json` roughly emulates a renewal, but changes from an ECDSA cert to an RSA one.
|
||||
# It should replace the cert files in the existing `letsencrypt/live/mail.example.test/` folder.
|
||||
function _acme_rsa() {
|
||||
_should_extract_on_changes 'mail.example.test' "${LOCAL_BASE_PATH}/rsa.acme.json"
|
||||
_should_have_service_restart_count '1'
|
||||
|
||||
local RSA_KEY_PATH="${LOCAL_BASE_PATH}/key.rsa.pem"
|
||||
local RSA_CERT_PATH="${LOCAL_BASE_PATH}/cert.rsa.pem"
|
||||
_should_have_expected_files 'mail.example.test' "${RSA_KEY_PATH}" "${RSA_CERT_PATH}"
|
||||
}
|
||||
|
||||
# Test that `acme.json` also works with wildcard certificates:
|
||||
# Additionally tests that SSL_DOMAIN is prioritized when `letsencrypt/live/` already has a HOSTNAME dir available.
|
||||
# Wildcard `*.example.test` should extract to `example.test/` in `letsencrypt/live/`:
|
||||
function _acme_wildcard() {
|
||||
_should_extract_on_changes 'example.test' "${LOCAL_BASE_PATH}/wildcard/rsa.acme.json"
|
||||
_should_have_service_restart_count '2'
|
||||
|
||||
# TODO: Make this pass.
|
||||
# As the FQDN has changed since startup, the configs need to be updated accordingly.
|
||||
# This requires the `changedetector` service event to invoke the same function for TLS configuration
|
||||
# that is used during container startup to work correctly. A follow up PR will refactor `setup-stack.sh` for supporting this.
|
||||
# _should_have_valid_config 'example.test' 'key.pem' 'fullchain.pem'
|
||||
|
||||
local WILDCARD_KEY_PATH="${LOCAL_BASE_PATH}/wildcard/key.rsa.pem"
|
||||
local WILDCARD_CERT_PATH="${LOCAL_BASE_PATH}/wildcard/cert.rsa.pem"
|
||||
_should_have_expected_files 'example.test' "${WILDCARD_KEY_PATH}" "${WILDCARD_CERT_PATH}"
|
||||
|
||||
# Verify this works for wildcard certs, it should use `*.example.test` for `mail.example.test` (NOT `example.test`):
|
||||
_should_succesfully_negotiate_tls 'mail.example.test'
|
||||
# WARNING: This should fail...but requires resolving the above TODO.
|
||||
# _should_not_have_fqdn_in_cert 'example.test'
|
||||
}
|
||||
|
||||
_prepare
|
||||
|
||||
# Unleash the `acme.json` tests!
|
||||
# NOTE: Test failures aren't as helpful here as bats will only identify function calls at this top-level,
|
||||
# rather than the actual failing nested function call..
|
||||
# TODO: Extract methods to separate test cases.
|
||||
_acme_ecdsa
|
||||
_acme_rsa
|
||||
_acme_wildcard
|
||||
}
|
||||
|
||||
|
||||
# this test is only there to reliably mark the end for the teardown_file
|
||||
# this test is only there to reliably mark the end for the teardown_file
|
||||
@test "last" {
|
||||
skip 'Finished testing of letsencrypt SSL'
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# 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}
|
||||
local LE_KEY_PATH="/etc/letsencrypt/live/${EXPECTED_FQDN}/${2}"
|
||||
local LE_CERT_PATH="/etc/letsencrypt/live/${EXPECTED_FQDN}/${3}"
|
||||
|
||||
_has_matching_line 'postconf' "smtpd_tls_chain_files = ${LE_KEY_PATH} ${LE_CERT_PATH}"
|
||||
_has_matching_line 'doveconf' "ssl_cert = <${LE_CERT_PATH}"
|
||||
# `-P` is required to prevent redacting secrets
|
||||
_has_matching_line 'doveconf -P' "ssl_key = <${LE_KEY_PATH}"
|
||||
}
|
||||
|
||||
# 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}'"
|
||||
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}
|
||||
local SERVICE=${2}
|
||||
|
||||
run $(_get_service_logs "${SERVICE}")
|
||||
assert_output --partial "_extract_certs_from_acme | Certificate successfully extracted for '${EXPECTED_DOMAIN}'"
|
||||
}
|
||||
|
||||
function _should_have_failed_at_extraction() {
|
||||
local EXPECTED_DOMAIN=${1}
|
||||
local SERVICE=${2}
|
||||
|
||||
run $(_get_service_logs "${SERVICE}")
|
||||
assert_output --partial "_extract_certs_from_acme | Unable to find key and/or cert for '${EXPECTED_DOMAIN}' in '/etc/letsencrypt/acme.json'"
|
||||
}
|
||||
|
||||
# Replace the mounted `acme.json` and wait to see if changes were detected.
|
||||
function _should_extract_on_changes() {
|
||||
local EXPECTED_DOMAIN=${1}
|
||||
local ACME_JSON=${2}
|
||||
|
||||
cp "${ACME_JSON}" "${TEST_TMP_CONFIG}/letsencrypt/acme.json"
|
||||
# Change detection takes a little over 5 seconds to complete (restart services)
|
||||
sleep 10
|
||||
|
||||
# Expected log lines from the changedetector service:
|
||||
run $(_get_service_logs 'changedetector')
|
||||
assert_output --partial 'Change detected'
|
||||
assert_output --partial "'/etc/letsencrypt/acme.json' has changed, extracting certs"
|
||||
assert_output --partial "_extract_certs_from_acme | Certificate successfully extracted for '${EXPECTED_DOMAIN}'"
|
||||
assert_output --partial 'Restarting services due to detected changes'
|
||||
assert_output --partial 'postfix: stopped'
|
||||
assert_output --partial 'postfix: started'
|
||||
assert_output --partial 'dovecot: stopped'
|
||||
assert_output --partial 'dovecot: started'
|
||||
}
|
||||
|
||||
# Ensure change detection is not mistakenly validating against previous change events:
|
||||
function _should_have_service_restart_count() {
|
||||
local NUM_RESTARTS=${1}
|
||||
|
||||
# Count how many times postfix was restarted by the `changedetector` service:
|
||||
run docker exec "${TEST_NAME}" sh -c "supervisorctl tail changedetector | grep -c 'postfix: started'"
|
||||
assert_output "${NUM_RESTARTS}"
|
||||
}
|
||||
|
||||
# Extracted cert files from `acme.json` have content matching the expected reference files:
|
||||
function _should_have_expected_files() {
|
||||
local LE_BASE_PATH="/etc/letsencrypt/live/${1}"
|
||||
local LE_KEY_PATH="${LE_BASE_PATH}/key.pem"
|
||||
local LE_CERT_PATH="${LE_BASE_PATH}/fullchain.pem"
|
||||
local EXPECTED_KEY_PATH=${2}
|
||||
local EXPECTED_CERT_PATH=${3}
|
||||
|
||||
_should_be_equal_in_content "${LE_KEY_PATH}" "${EXPECTED_KEY_PATH}"
|
||||
_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}
|
||||
local DEST=${2}
|
||||
|
||||
local FQDN_DIR
|
||||
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}"
|
||||
}
|
||||
|
||||
function _should_be_equal_in_content() {
|
||||
local CONTAINER_PATH=${1}
|
||||
local LOCAL_PATH=${2}
|
||||
|
||||
run docker exec "${TEST_NAME}" sh -c "cat ${CONTAINER_PATH}"
|
||||
assert_output "$(cat "${LOCAL_PATH}")"
|
||||
assert_success
|
||||
}
|
||||
|
||||
function _get_service_logs() {
|
||||
local SERVICE=${1:-'mailserver'}
|
||||
|
||||
local CMD_LOGS=(docker exec "${TEST_NAME}" "supervisorctl tail ${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}")
|
||||
fi
|
||||
|
||||
echo "${CMD_LOGS[@]}"
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue