fix: Enable DH parameters (ffdhe4096) by default (#2192)

This feature was originally introduced by the PR: https://github.com/docker-mailserver/docker-mailserver/pull/1463

- Assign default DH params to use via Dockerfile build instead of copy and update at runtime.
- Parameterized service names and paths.
- Refactor postfix and dovecot dh methods to wrap shared dh logic
- I don't see any value in checking the alternative service for dh params file to copy over, so that's now dropped too.
- Another conditional check is dropped and the default fallback message for existing DH params file is no longer relevant.
- Improved the remaining `_notify` messages. Collapsing the warning into a single logged message also seemed relevant.
- There is no apparent need for special handling with `ONE_DIR=1`. Dropped it.

- Refactor DH params  tests
- Combine custom and default DH param tests into single test file
- docs: Add instructions to use custom DH params

There is no official documented support for custom DH parameters. As no guarantee is provided, this is considered an internal change, not a breaking one.
This commit is contained in:
Brennan Kinney 2021-09-15 20:28:04 +12:00 committed by GitHub
parent 54ee1e7567
commit 08cd4d3371
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 176 additions and 286 deletions

View file

@ -225,7 +225,13 @@ RUN \
COPY target/fetchmail/fetchmailrc /etc/fetchmailrc_general
COPY target/postfix/main.cf target/postfix/master.cf /etc/postfix/
COPY target/shared/ffdhe4096.pem /etc/postfix/shared/ffdhe4096.pem
# DH parameters for DHE cipher suites, ffdhe4096 is the official standard 4096-bit DH params now part of TLS 1.3
# This file is for TLS <1.3 handshakes that rely on DHE cipher suites
# Handled at build to avoid failures by doveadm validating ssl_dh filepath in 10-ssl.auth (eg generate-accounts)
COPY target/shared/ffdhe4096.pem /etc/postfix/dhparams.pem
COPY target/shared/ffdhe4096.pem /etc/dovecot/dh.pem
COPY \
target/postfix/header_checks.pcre \
target/postfix/sender_header_filter.pcre \

View file

@ -654,6 +654,12 @@ if [ "$certcheck_2weeks" = "Certificate will not expire" ]; then
fi
```
## Custom DH Parameters
By default `docker-mailserver` uses [`ffdhe4096`][ffdhe4096-src] from [IETF RFC 7919][ietf::rfc::ffdhe]. These are standardized pre-defined DH groups and the only available DH groups for TLS 1.3. It is [discouraged to generate your own DH parameters][dh-avoid-selfgenerated] as it is often less secure.
Despite this, if you must use non-standard DH parameters or you would like to swap `ffdhe4096` for a different group (eg `ffdhe2048`); Add your own PEM encoded DH params file via a volume to `/tmp/docker-mailserver/dhparams.pem`. This will replace DH params for both Dovecot and Postfix services during container startup.
[docs-optional-config]: ../advanced/optional-config.md
[github-file-compose]: https://github.com/docker-mailserver/docker-mailserver/blob/master/docker-compose.yml
@ -661,4 +667,8 @@ fi
[hanscees-renewcerts]: https://github.com/hanscees/dockerscripts/blob/master/scripts/tomav-renew-certs
[traefik::github]: https://github.com/containous/traefik
[ietf::rfc::acme]: https://tools.ietf.org/html/rfc8555
[ietf::rfc::acme]: https://datatracker.ietf.org/doc/html/rfc8555
[ietf::rfc::ffdhe]: https://datatracker.ietf.org/doc/html/rfc7919
[ffdhe4096-src]: https://github.com/internetstandards/dhe_groups
[dh-avoid-selfgenerated]: https://crypto.stackexchange.com/questions/29926/what-diffie-hellman-parameters-should-i-use

View file

@ -1301,92 +1301,30 @@ function _setup_postfix_relay_hosts
function _setup_postfix_dhparam
{
_notify 'task' 'Setting up Postfix dhparam'
if [[ ${ONE_DIR} -eq 1 ]]
then
DHPARAMS_FILE=/var/mail-state/lib-shared/dhparams.pem
if [[ ! -f ${DHPARAMS_FILE} ]]
then
_notify 'inf' "Use ffdhe4096 for dhparams (postfix)"
cp -f /etc/postfix/shared/ffdhe4096.pem /etc/postfix/dhparams.pem
else
_notify 'inf' "Use postfix dhparams that was generated previously"
_notify 'warn' "Using self-generated dhparams is considered as insecure."
_notify 'warn' "Unless you known what you are doing, please remove /var/mail-state/lib-shared/dhparams.pem."
# Copy from the state directory to the working location
cp -f "${DHPARAMS_FILE}" /etc/postfix/dhparams.pem
fi
else
if [[ ! -f /etc/postfix/dhparams.pem ]]
then
if [[ -f /etc/dovecot/dh.pem ]]
then
_notify 'inf' "Copy dovecot dhparams to postfix"
cp /etc/dovecot/dh.pem /etc/postfix/dhparams.pem
elif [[ -f /tmp/docker-mailserver/dhparams.pem ]]
then
_notify 'inf' "Copy pre-generated dhparams to postfix"
_notify 'warn' "Using self-generated dhparams is considered as insecure."
_notify 'warn' "Unless you known what you are doing, please remove /var/mail-state/lib-shared/dhparams.pem."
cp /tmp/docker-mailserver/dhparams.pem /etc/postfix/dhparams.pem
else
_notify 'inf' "Use ffdhe4096 for dhparams (postfix)"
cp /etc/postfix/shared/ffdhe4096.pem /etc/postfix/dhparams.pem
fi
else
_notify 'inf' "Use existing postfix dhparams"
_notify 'warn' "Using self-generated dhparams is considered insecure."
_notify 'warn' "Unless you known what you are doing, please remove /etc/postfix/dhparams.pem."
fi
fi
_setup_dhparam 'postfix' '/etc/postfix/dhparams.pem'
}
function _setup_dovecot_dhparam
{
_notify 'task' 'Setting up Dovecot dhparam'
_setup_dhparam 'dovecot' '/etc/dovecot/dh.pem'
}
if [[ ${ONE_DIR} -eq 1 ]]
then
DHPARAMS_FILE=/var/mail-state/lib-shared/dhparams.pem
function _setup_dhparam
{
local DH_SERVICE=$1
local DH_DEST=$2
local DH_CUSTOM=/tmp/docker-mailserver/dhparams.pem
if [[ ! -f ${DHPARAMS_FILE} ]]
then
_notify 'inf' "Use ffdhe4096 for dhparams (dovecot)"
cp -f /etc/postfix/shared/ffdhe4096.pem /etc/dovecot/dh.pem
else
_notify 'inf' "Use dovecot dhparams that was generated previously"
_notify 'warn' "Using self-generated dhparams is considered as insecure."
_notify 'warn' "Unless you known what you are doing, please remove /var/mail-state/lib-shared/dhparams.pem."
_notify 'task' "Setting up ${DH_SERVICE} dhparam"
# Copy from the state directory to the working location
cp -f "${DHPARAMS_FILE}" /etc/dovecot/dh.pem
fi
else
if [[ ! -f /etc/dovecot/dh.pem ]]
then
if [[ -f /etc/postfix/dhparams.pem ]]
then
_notify 'inf' "Copy postfix dhparams to dovecot"
cp /etc/postfix/dhparams.pem /etc/dovecot/dh.pem
elif [[ -f /tmp/docker-mailserver/dhparams.pem ]]
then
_notify 'inf' "Copy pre-generated dhparams to dovecot"
_notify 'warn' "Using self-generated dhparams is considered as insecure."
_notify 'warn' "Unless you known what you are doing, please remove /tmp/docker-mailserver/dhparams.pem."
if [[ -f ${DH_CUSTOM} ]]
then # use custom supplied dh params (assumes they're probably insecure)
_notify 'inf' "${DH_SERVICE} will use custom provided DH paramters."
_notify 'warn' "Using self-generated dhparams is considered insecure. Unless you know what you are doing, please remove ${DH_CUSTOM}."
cp /tmp/docker-mailserver/dhparams.pem /etc/dovecot/dh.pem
else
_notify 'inf' "Use ffdhe4096 for dhparams (dovecot)"
cp /etc/postfix/shared/ffdhe4096.pem /etc/dovecot/dh.pem
fi
else
_notify 'inf' "Use existing dovecot dhparams"
_notify 'warn' "Using self-generated dhparams is considered as insecure."
_notify 'warn' "Unless you known what you are doing, please remove /etc/dovecot/dh.pem."
fi
cp -f "${DH_CUSTOM}" "${DH_DEST}"
else # use official standardized dh params (provided via Dockerfile)
_notify 'inf' "${DH_SERVICE} will use official standardized DH parameters (ffdhe4096)."
fi
}

View file

@ -1,81 +0,0 @@
load 'test_helper/common'
# Test case
# ---------
# By default, this image is using audited FFDHE groups (https://github.com/docker-mailserver/docker-mailserver/pull/1463)
#
# This test case covers the described case against both boolean states for `ONE_DIR`.
#
# Description:
# - When no DHE parameters are supplied by the user:
# ~ The file `ffdhe4096.pem` has not been modified (checksum verification).
# ~ `ffdhe4096.pem` is copied to the configuration directories for postfix and dovecot.
function setup() {
run_setup_file_if_necessary
}
function teardown() {
run_teardown_file_if_necessary
}
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_default_dhparams_both_one_dir)"
docker run -d --name mail_default_dhparams_one_dir \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e DMS_DEBUG=0 \
-e ONE_DIR=1 \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_default_dhparams_one_dir
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_default_dhparams_both_not_one_dir)"
docker run -d --name mail_default_dhparams_not_one_dir \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e DMS_DEBUG=0 \
-e ONE_DIR=0 \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_default_dhparams_not_one_dir
}
function teardown_file() {
docker rm -f mail_default_dhparams_one_dir
docker rm -f mail_default_dhparams_not_one_dir
}
@test "first" {
skip 'this test must come first to reliably identify when to run setup_file'
}
@test "checking ssl: checking dhe params are sufficient" {
# reference used: (22/04/2020) https://english.ncsc.nl/publications/publications/2019/juni/01/it-security-guidelines-for-transport-layer-security-tls
# check ffdhe params are inchanged
REPO_CHECKSUM=$(sha512sum "$(pwd)/target/shared/ffdhe4096.pem" | awk '{print $1}')
MOZILLA_CHECKSUM=$(curl https://ssl-config.mozilla.org/ffdhe4096.txt -s | sha512sum | awk '{print $1}')
assert_equal "${REPO_CHECKSUM}" "${MOZILLA_CHECKSUM}"
run echo "${REPO_CHECKSUM}"
refute_output '' # checksum must not be empty
# by default, ffdhe4096 should be used
# ONE_DIR=1
DOCKER_DOVECOT_CHECKSUM_ONE_DIR=$(docker exec mail_default_dhparams_one_dir sha512sum /etc/dovecot/dh.pem | awk '{print $1}')
DOCKER_POSTFIX_CHECKSUM_ONE_DIR=$(docker exec mail_default_dhparams_one_dir sha512sum /etc/postfix/dhparams.pem | awk '{print $1}')
assert_equal "${DOCKER_DOVECOT_CHECKSUM_ONE_DIR}" "${REPO_CHECKSUM}"
assert_equal "${DOCKER_POSTFIX_CHECKSUM_ONE_DIR}" "${REPO_CHECKSUM}"
# ONE_DIR=0
DOCKER_DOVECOT_CHECKSUM_NOT_ONE_DIR=$(docker exec mail_default_dhparams_not_one_dir sha512sum /etc/dovecot/dh.pem | awk '{print $1}')
DOCKER_POSTFIX_CHECKSUM_NOT_ONE_DIR=$(docker exec mail_default_dhparams_not_one_dir sha512sum /etc/postfix/dhparams.pem | awk '{print $1}')
assert_equal "${DOCKER_DOVECOT_CHECKSUM_NOT_ONE_DIR}" "${REPO_CHECKSUM}"
assert_equal "${DOCKER_POSTFIX_CHECKSUM_NOT_ONE_DIR}" "${REPO_CHECKSUM}"
}
@test "last" {
skip 'this test is only there to reliably mark the end for the teardown_file'
}

View file

@ -1,64 +0,0 @@
load 'test_helper/common'
# Test case
# ---------
# By default, this image is using audited FFDHE groups (https://github.com/docker-mailserver/docker-mailserver/pull/1463)
#
# This test case covers the described case when `ONE_DIR=0`.
#
# Description:
# - When custom DHE parameters are supplied by the user:
# ~ User supplied DHE parameters are copied to the configuration directories for postfix and dovecot.
# ~ A warning is raised about usage of insecure parameters.
function setup() {
run_setup_file_if_necessary
}
function teardown() {
run_teardown_file_if_necessary
}
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG=$(duplicate_config_for_container .)
# copy the custom DHE params in local config
cp "$(pwd)/test/test-files/ssl/custom-dhe-params.pem" "${PRIVATE_CONFIG}/dhparams.pem"
docker run -d --name mail_manual_dhparams_not_one_dir \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-e DMS_DEBUG=0 \
-e ONE_DIR=0 \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_manual_dhparams_not_one_dir
}
function teardown_file() {
docker rm -f mail_manual_dhparams_not_one_dir
}
@test "first" {
skip 'this test must come first to reliably identify when to run setup_file'
}
@test "checking dhparams: ONE_DIR=0 check manual dhparams is used" {
test_checksum=$(sha512sum "$(pwd)/test/test-files/ssl/custom-dhe-params.pem" | awk '{print $1}')
run echo "${test_checksum}"
refute_output '' # checksum must not be empty
docker_dovecot_checksum=$(docker exec mail_manual_dhparams_not_one_dir sha512sum /etc/dovecot/dh.pem | awk '{print $1}')
docker_postfix_checksum=$(docker exec mail_manual_dhparams_not_one_dir sha512sum /etc/postfix/dhparams.pem | awk '{print $1}')
assert_equal "${docker_dovecot_checksum}" "${test_checksum}"
assert_equal "${docker_postfix_checksum}" "${test_checksum}"
}
@test "checking dhparams: ONE_DIR=0 check warning output when using manual dhparams" {
run sh -c "docker logs mail_manual_dhparams_not_one_dir | grep 'Using self-generated dhparams is considered as insecure'"
assert_success
}
@test "last" {
skip 'this test is only there to reliably mark the end for the teardown_file'
}

View file

@ -1,61 +0,0 @@
load 'test_helper/common'
# Test case
# ---------
# By default, this image is using audited FFDHE groups (https://github.com/docker-mailserver/docker-mailserver/pull/1463)
#
# This test case covers the described case when `ONE_DIR=1`.
#
# Description:
# - When custom DHE parameters are supplied by the user:
# ~ User supplied DHE parameters are copied to the configuration directories for postfix and dovecot.
# ~ A warning is raised about usage of insecure parameters.
function setup() {
run_setup_file_if_necessary
}
function teardown() {
run_teardown_file_if_necessary
}
function setup_file() {
local PRIVATE_CONFIG
PRIVATE_CONFIG="$(duplicate_config_for_container .)"
docker run -d --name mail_manual_dhparams_one_dir \
-v "${PRIVATE_CONFIG}":/tmp/docker-mailserver \
-v "$(pwd)/test/test-files":/tmp/docker-mailserver-test:ro \
-v "$(pwd)/test/test-files/ssl/custom-dhe-params.pem":/var/mail-state/lib-shared/dhparams.pem:ro \
-e DMS_DEBUG=0 \
-e ONE_DIR=1 \
-h mail.my-domain.com -t "${NAME}"
wait_for_finished_setup_in_container mail_manual_dhparams_one_dir
}
function teardown_file() {
docker rm -f mail_manual_dhparams_one_dir
}
@test "first" {
skip 'this test must come first to reliably identify when to run setup_file'
}
@test "checking dhparams: ONE_DIR=1 check manual dhparams is used" {
test_checksum=$(sha512sum "$(pwd)/test/test-files/ssl/custom-dhe-params.pem" | awk '{print $1}')
run echo "${test_checksum}"
refute_output '' # checksum must not be empty
docker_dovecot_checksum=$(docker exec mail_manual_dhparams_one_dir sha512sum /etc/dovecot/dh.pem | awk '{print $1}')
docker_postfix_checksum=$(docker exec mail_manual_dhparams_one_dir sha512sum /etc/postfix/dhparams.pem | awk '{print $1}')
assert_equal "${docker_dovecot_checksum}" "${test_checksum}"
assert_equal "${docker_postfix_checksum}" "${test_checksum}"
}
@test "checking dhparams: ONE_DIR=1 check warning output when using manual dhparams" {
run sh -c "docker logs mail_manual_dhparams_one_dir | grep 'Using self-generated dhparams is considered as insecure'"
assert_success
}
@test "last" {
skip 'this test is only there to reliably mark the end for the teardown_file'
}

142
test/mail_tls_dhparams.bats Normal file
View file

@ -0,0 +1,142 @@
load 'test_helper/common'
# Test case
# ---------
# By default, this image is using audited FFDHE groups (https://github.com/docker-mailserver/docker-mailserver/pull/1463)
#
# This test case covers the described case against both boolean states for `ONE_DIR`.
#
# Description:
# 1. Verify that the file `ffdhe4096.pem` has not been modified (checksum verification).
# 2. Verify Postfix and Dovecot are using the default `ffdhe4096.pem` from Dockerfile build.
# 3. When custom DHE parameters are supplied by the user as `/tmp/docker-mailserver/dhparams.pem`:
# - Verify Postfix and Dovecot use the custom `custom-dhe-params.pem` (contents is actually `ffdhe2048.pem`).
# - A warning is raised about usage of potentially insecure parameters.
function setup() {
run_setup_file_if_necessary
}
function teardown() {
docker rm -f mail_dhparams
run_teardown_file_if_necessary
}
function setup_file() {
# Delegated container setup to common_container_setup
# DRY - Explicit config changes between tests are more apparent this way.
# Global scope
# Copies all of `./test/config/` to specific directory for testing
# `${PRIVATE_CONFIG}` becomes `$(pwd)/test/duplicate_configs/<bats test filename>`
export PRIVATE_CONFIG
export DMS_ONE_DIR=1 # default
local DH_DEFAULT_PARAMS
export DH_DEFAULT_CHECKSUM
export DH_CUSTOM_PARAMS
export DH_CUSTOM_CHECKSUM
DH_DEFAULT_PARAMS="$(pwd)/target/shared/ffdhe4096.pem"
DH_DEFAULT_CHECKSUM="$(sha512sum "${DH_DEFAULT_PARAMS}" | awk '{print $1}')"
DH_CUSTOM_PARAMS="$(pwd)/test/test-files/ssl/custom-dhe-params.pem"
DH_CUSTOM_CHECKSUM="$(sha512sum "${DH_CUSTOM_PARAMS}" | awk '{print $1}')"
}
# Not used
# function teardown_file() {
# }
@test "first" {
skip 'this test must come first to reliably identify when to run setup_file'
}
@test "testing tls: DH Parameters - Verify integrity of Default (ffdhe4096)" {
# Reference used (22/04/2020):
# https://english.ncsc.nl/publications/publications/2019/juni/01/it-security-guidelines-for-transport-layer-security-tls
run echo "${DH_DEFAULT_CHECKSUM}"
refute_output '' # checksum must not be empty
# Verify the FFDHE params file has not been modified (equivalent to `target/shared/ffdhe4096.pem.sha512sum`):
local DH_MOZILLA_CHECKSUM
DH_MOZILLA_CHECKSUM="$(curl https://ssl-config.mozilla.org/ffdhe4096.txt -s | sha512sum | awk '{print $1}')"
assert_equal "${DH_DEFAULT_CHECKSUM}" "${DH_MOZILLA_CHECKSUM}"
}
@test "testing tls: DH Parameters - Default [ONE_DIR=0]" {
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_dhparams_default_0)"
DMS_ONE_DIR=0
common_container_setup
should_have_valid_checksum "${DH_DEFAULT_CHECKSUM}"
}
@test "testing tls: DH Parameters - Default [ONE_DIR=1]" {
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_dhparams_default_1)"
common_container_setup
should_have_valid_checksum "${DH_DEFAULT_CHECKSUM}"
}
@test "testing tls: DH Parameters - Custom [ONE_DIR=0]" {
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_dhparams_custom_0)"
# shellcheck disable=SC2030
DMS_ONE_DIR=0
cp "${DH_CUSTOM_PARAMS}" "${PRIVATE_CONFIG}/dhparams.pem"
common_container_setup
should_have_valid_checksum "${DH_CUSTOM_CHECKSUM}"
should_emit_warning
}
@test "testing tls: DH Parameters - Custom [ONE_DIR=1]" {
# shellcheck disable=SC2030
PRIVATE_CONFIG="$(duplicate_config_for_container . mail_dhparams_custom_1)"
cp "${DH_CUSTOM_PARAMS}" "${PRIVATE_CONFIG}/dhparams.pem"
common_container_setup
should_have_valid_checksum "${DH_CUSTOM_CHECKSUM}"
should_emit_warning
}
@test "last" {
skip 'this test is only there to reliably mark the end for the teardown_file'
}
function common_container_setup() {
# shellcheck disable=SC2031
docker run -d --name mail_dhparams \
-v "${PRIVATE_CONFIG}:/tmp/docker-mailserver" \
-v "$(pwd)/test/test-files:/tmp/docker-mailserver-test:ro" \
-e DMS_DEBUG=0 \
-e ONE_DIR="${DMS_ONE_DIR}" \
-h mail.my-domain.com \
--tty \
"${NAME}"
wait_for_finished_setup_in_container mail_dhparams
}
# Ensures the docker image services (Postfix and Dovecot) have the intended DH files
function should_have_valid_checksum() {
local DH_CHECKSUM=$1
local DH_CHECKSUM_DOVECOT
DH_CHECKSUM_DOVECOT=$(docker exec mail_dhparams sha512sum /etc/dovecot/dh.pem | awk '{print $1}')
assert_equal "${DH_CHECKSUM_DOVECOT}" "${DH_CHECKSUM}"
local DH_CHECKSUM_POSTFIX
DH_CHECKSUM_POSTFIX=$(docker exec mail_dhparams sha512sum /etc/postfix/dhparams.pem | awk '{print $1}')
assert_equal "${DH_CHECKSUM_POSTFIX}" "${DH_CHECKSUM}"
}
function should_emit_warning() {
run sh -c "docker logs mail_dhparams | grep 'Using self-generated dhparams is considered insecure.'"
assert_success
}