diff --git a/CHANGELOG.md b/CHANGELOG.md index fcca0cd3..30fa1d54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased](https://github.com/docker-mailserver/docker-mailserver/compare/v14.0.0...HEAD) +## [Unreleased](https://github.com/docker-mailserver/docker-mailserver/compare/v13.3.0...HEAD) > **Note**: Changes and additions listed here are contained in the `:edge` image tag. These changes may not be as stable as released changes. -## [v14.0.0](https://github.com/docker-mailserver/docker-mailserver/releases/tag/v14.0.0) - The most noteworthy change of this release is the update of the container's base image from Debian 11 ("Bullseye") to Debian 12 ("Bookworm"). This update alone involves breaking changes and requires a careful update! ### Breaking @@ -48,9 +46,17 @@ The most noteworthy change of this release is the update of the container's base - Enable via the ENV `ENABLE_MTA_STS=1` - Supported by major email service providers like Gmail, Yahoo and Outlook. +### Added + +- **Docs:** + - An example for how to bind outbound SMTP connections to a specific network interface ([#3465](https://github.com/docker-mailserver/docker-mailserver/pull/3465)) + ### Updates - **Tests**: + - Revised OAuth2 test ([#3795](https://github.com/docker-mailserver/docker-mailserver/pull/3795)) + - Replace `wc -l` with `grep -c` ([#3752](https://github.com/docker-mailserver/docker-mailserver/pull/3752)) + - Revised testing of service process management (supervisord) to be more robust ([#3780](https://github.com/docker-mailserver/docker-mailserver/pull/3780)) - Refactored mail sending ([#3747](https://github.com/docker-mailserver/docker-mailserver/pull/3747) & [#3772](https://github.com/docker-mailserver/docker-mailserver/pull/3772)): - This change is a follow-up to [#3732](https://github.com/docker-mailserver/docker-mailserver/pull/3732) from DMS v13.2. - `swaks` version is now the latest from Github releases instead of the Debian package. @@ -58,16 +64,17 @@ The most noteworthy change of this release is the update of the container's base - `sending.bash` helper methods were refactored to better integrate `swaks` and accommodate different usage contexts. - `test/files/emails/existing/` files were removed similar to previous removal of SMTP auth files as they became redundant with `swaks`. - **Internal:** - - tests: Replace `wc -l` with `grep -c` ([#3752](https://github.com/docker-mailserver/docker-mailserver/pull/3752)) - Postfix is now configured with `smtputf8_enable = no` in our default `main.cf` config (_instead of during container startup_). ([#3750](https://github.com/docker-mailserver/docker-mailserver/pull/3750)) - **Rspamd** ([#3726](https://github.com/docker-mailserver/docker-mailserver/pull/3726)): - - symbol scores for SPF, DKIM & DMARC were updated to more closely align with [RFC7489](https://www.rfc-editor.org/rfc/rfc7489#page-24); please note though that complete alignment is undesirable, because other symbols might be added as well, which changes the overall score calculation again, see [this issue](https://github.com/docker-mailserver/docker-mailserver/issues/3690#issuecomment-1866871996) + - Symbol scores for SPF, DKIM & DMARC were updated to more closely align with [RFC7489](https://www.rfc-editor.org/rfc/rfc7489#page-24). Please note that complete alignment is undesirable as other symbols may be added as well, which changes the overall score calculation again, see [this issue](https://github.com/docker-mailserver/docker-mailserver/issues/3690#issuecomment-1866871996) - **Docs:** - Revised the SpamAssassin ENV docs to better communicate configuration and their relation to other ENV settings. ([#3756](https://github.com/docker-mailserver/docker-mailserver/pull/3756)) - Detailed how mail received is assigned a spam score by Rspamd and processed accordingly ([#3773](https://github.com/docker-mailserver/docker-mailserver/pull/3773)) ### Fixes +- **Setup:** + - `setup` CLI - `setup dkim domain` now creates the keys files with the user owning the key directory ([#3783](https://github.com/docker-mailserver/docker-mailserver/pull/3783)) - **Dovecot:** - During container startup for Dovecot Sieve, `.sievec` source files compiled to `.svbin` now have their `mtime` adjusted post setup to ensure it is always older than the associated `.svbin` file. This avoids superfluous error logs for sieve scripts that don't actually need to be compiled again ([#3779](https://github.com/docker-mailserver/docker-mailserver/pull/3779)) - **Internal:** diff --git a/VERSION b/VERSION index 67aee239..ac565bc1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -13.2.0 +13.3.0 diff --git a/docs/content/examples/use-cases/bind-smtp-network-interface.md b/docs/content/examples/use-cases/bind-smtp-network-interface.md new file mode 100644 index 00000000..b12e21de --- /dev/null +++ b/docs/content/examples/use-cases/bind-smtp-network-interface.md @@ -0,0 +1,68 @@ +--- +title: 'Use Cases | Binding outbound SMTP to a specific network' +hide: + - toc +--- + +!!! warning "Advice not extensively tested" + + This configuration advice is a community contribution which has only been verified as a solution when using `network: host`, where you have direct access to the host interfaces. + + It may be applicable in other network modes if the container has control of the outbound IPs to bind to. This is not the case with bridge networks that typically bind to a private range network for containers which are bridged to a public interface via Docker. + +If your Docker host is running multiple IPv4 and IPv6 IP-addresses, it may be beneficial to bind outgoing SMTP connections to specific IP-address / interface. + +- When a mail is sent outbound from DMS, it greets the MTA it is connecting to with a EHLO (DMS FQDN) which might be verified against the IP resolved, and that a `PTR` record for that IP resolves an address back to the same IP. +- A similar check with SPF can be against the envelope-sender address which may verify a DNS record like MX / A is valid (_or a similar restriction check from an MTA like [Postfix has with `reject_unknown_sender`][gh-pr::3465::comment-restrictions]_). +- If the IP address is inconsistent for those connections from DMS, these DNS checks are likely to fail. + +This can be configured by [overriding the default Postfix configurations][docs::overrides-postfix] DMS provides. Create `postfix-master.cf` and `postfix-main.cf` files for your config volume (`docker-data/dms/config`). + +In `postfix-main.cf` you'll have to set the [`smtp_bind_address`][postfix-docs::smtp-bind-address-ipv4] and [`smtp_bind_address6`][postfix-docs::smtp-bind-address-ipv6] +to the respective IP-address on the server you want to use. + +[docs::overrides-postfix]: ../../config/advanced/override-defaults/postfix.md +[postfix-docs::smtp-bind-address-ipv4]: https://www.postfix.org/postconf.5.html#smtp_bind_address +[postfix-docs::smtp-bind-address-ipv6]: https://www.postfix.org/postconf.5.html#smtp_bind_address6 + +!!! example + + === "Contributed solution" + + ```title="postfix-main.cf" + smtp_bind_address = 198.51.100.42 + smtp_bind_address6 = 2001:DB8::42 + ``` + + !!! bug "Inheriting the bind from `main.cf` can misconfigure services" + + One problem when setting `smtp_bind_address` in `main.cf` is that it will be inherited by any services in `master.cf` that extend the `smtp` transport. One of these is `smtp-amavis`, which is explicitly configured to listen / connect via loopback (localhost / `127.0.0.1`). + + A `postfix-master.cf` override can workaround that issue by ensuring `smtp-amavis` binds to the expected internal IP: + + ```title="postfix-master.cf" + smtp-amavis/unix/smtp_bind_address=127.0.0.1 + smtp-amavis/unix/smtp_bind_address6=::1 + ``` + + === "Alternative (unverified)" + + A potentially better solution might be to instead [explicitly set the `smtp_bind_address` override on the `smtp` transport service][gh-pr::3465::alternative-solution]: + + ```title="postfix-master.cf" + smtp/inet/smtp_bind_address = 198.51.100.42 + smtp/inet/smtp_bind_address6 = 2001:DB8::42 + ``` + + If that avoids the concern with `smtp-amavis`, you may still need to additionally override for the [`relay` transport][gh-src::postfix-master-cf::relay-transport] as well if you have configured DMS to relay mail. + +!!! note "IP addresses for documentation" + + IP addresses shown in above examples are placeholders, they are IP addresses reserved for documentation by IANA (_[RFC-5737 (IPv4)][rfc-5737] and [RFC-3849 (IPv6)][rfc-3849]_). Replace them with the IP addresses you want DMS to send mail through. + +[rfc-5737]: https://datatracker.ietf.org/doc/html/rfc5737 +[rfc-3849]: https://datatracker.ietf.org/doc/html/rfc3849 + +[gh-pr::3465::comment-restrictions]: https://github.com/docker-mailserver/docker-mailserver/pull/3465#discussion_r1458114528 +[gh-pr::3465::alternative-solution]: https://github.com/docker-mailserver/docker-mailserver/pull/3465#issuecomment-1678107233 +[gh-src::postfix-master-cf::relay-transport]: https://github.com/docker-mailserver/docker-mailserver/blob/9cdbef2b369fb4fb0f1b4e534da8703daf92abc9/target/postfix/master.cf#L65 diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8a6a24b0..0d34b407 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -167,6 +167,7 @@ nav: - 'Customize IMAP Folders': examples/use-cases/imap-folders.md - 'iOS Mail Push Support': examples/use-cases/ios-mail-push-support.md - 'Lua Authentication': examples/use-cases/auth-lua.md + - 'Bind outbound SMTP to a specific network': examples/use-cases/bind-smtp-network-interface.md - 'FAQ' : faq.md - 'Contributing': - 'General Information': contributing/general.md diff --git a/target/bin/open-dkim b/target/bin/open-dkim index 86fbfb81..808ef8cc 100755 --- a/target/bin/open-dkim +++ b/target/bin/open-dkim @@ -144,6 +144,9 @@ while read -r DKIM_DOMAIN; do --directory="/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}" fi + # fix permissions to use the same user:group as /tmp/docker-mailserver/opendkim/keys + chown -R "$(stat -c '%U:%G' /tmp/docker-mailserver/opendkim/keys)" "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}" + # write to KeyTable if necessary KEYTABLEENTRY="${SELECTOR}._domainkey.${DKIM_DOMAIN} ${DKIM_DOMAIN}:${SELECTOR}:/etc/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private" if [[ ! -f "/tmp/docker-mailserver/opendkim/KeyTable" ]]; then diff --git a/target/postfix/main.cf b/target/postfix/main.cf index f9b3e913..a0d805cc 100644 --- a/target/postfix/main.cf +++ b/target/postfix/main.cf @@ -68,6 +68,7 @@ smtpd_forbid_bare_newline = yes # smtpd_forbid_bare_newline_exclusions = $mynetworks # Custom defined parameters for DMS: +# reject_unknown_sender_domain: https://github.com/docker-mailserver/docker-mailserver/issues/3716#issuecomment-1868033234 dms_smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unknown_sender_domain # Submission ports 587 and 465 support for SPOOF_PROTECTION=1 mua_sender_restrictions = reject_authenticated_sender_login_mismatch, $dms_smtpd_sender_restrictions diff --git a/target/scripts/startup/setup.d/security/misc.sh b/target/scripts/startup/setup.d/security/misc.sh index 4a06d986..ec32a1e6 100644 --- a/target/scripts/startup/setup.d/security/misc.sh +++ b/target/scripts/startup/setup.d/security/misc.sh @@ -70,6 +70,8 @@ function __setup__security__spamassassin() { if [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]]; then _log 'debug' 'Enabling and configuring SpamAssassin' + # Maintainers should take care in attempting to change these sed commands. Alternatives were already explored: + # https://github.com/docker-mailserver/docker-mailserver/pull/3767#issuecomment-1885989591 # shellcheck disable=SC2016 sed -i -r 's|^\$sa_tag_level_deflt (.*);|\$sa_tag_level_deflt = '"${SA_TAG}"';|g' /etc/amavis/conf.d/20-debian_defaults diff --git a/test/config/oauth2/Caddyfile b/test/config/oauth2/Caddyfile new file mode 100644 index 00000000..e116aa55 --- /dev/null +++ b/test/config/oauth2/Caddyfile @@ -0,0 +1,88 @@ +# Mocked OAuth2 /userinfo endpoint normally provided via an Authorization Server (AS) / Identity Provider (IdP) +# +# Dovecot will query the mocked `/userinfo` endpoint with the OAuth2 bearer token it was provided during login. +# If the session for the token is valid, a response returns an attribute to perform a UserDB lookup on (default: email). + +# `DMS_YWNjZXNzX3Rva2Vu` is the access token our OAuth2 tests expect for an authorization request to be successful. +# - The token was created by base64 encoding the string `access_token`, followed by adding `DMS_` as a prefix. +# - Normally an access token is a short-lived value associated to a login session. The value does not encode any real data. +# It is an opaque token: https://oauth.net/2/bearer-tokens/ + +# NOTE: The main server config is at the end within the `:80 { ... }` block. +# This is because the endpoints are extracted out into Caddy snippets, which must be defined before they're referenced. + +# /userinfo +(route-userinfo) { + vars token "DMS_YWNjZXNzX3Rva2Vu" + + # Expects to match an authorization header with a specific bearer token: + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes + @auth header Authorization "Bearer {vars.token}" + + # If the provided authorization header has the expected value (bearer token), respond with this JSON payload: + handle @auth { + # JSON inlined via HereDoc string feature: + # Dovecot OAuth2 defaults to `username_attribute = email`, which must be returned in the response to match + # with the `user` credentials field that Dovecot received via base64 encoded IMAP `AUTHENTICATE` value. + respond <&1 | grep 'Starting server'" + _run_until_success_or_timeout 20 bash -c "docker logs ${CONTAINER2_NAME} 2>&1 | grep 'serving initial configuration'" # # Setup DMS container # - # Add OAUTH2 configuration so that Dovecot can reach out to our mock provider (CONTAINER2) + # Add OAuth2 configuration so that Dovecot can query our mocked identity provider (CONTAINER2) local ENV_OAUTH2_CONFIG=( --env ENABLE_OAUTH2=1 - --env OAUTH2_INTROSPECTION_URL=http://oauth2.example.test/userinfo/ + --env OAUTH2_INTROSPECTION_URL=http://auth.example.test/userinfo ) export CONTAINER_NAME=${CONTAINER1_NAME} @@ -49,6 +48,9 @@ function setup_file() { # Set default implicit container fallback for helpers: export CONTAINER_NAME=${CONTAINER1_NAME} + + # An initial connection needs to be made first, otherwise the auth attempts fail + _run_in_container_bash 'nc -vz 0.0.0.0 143' } function teardown_file() { @@ -56,11 +58,21 @@ function teardown_file() { docker network rm "${DMS_TEST_NETWORK}" } - -@test "oauth2: imap connect and authentication works" { - # An initial connection needs to be made first, otherwise the auth attempt fails - _run_in_container_bash 'nc -vz 0.0.0.0 143' - - _nc_wrapper 'auth/imap-oauth2-auth.txt' '-w 1 0.0.0.0 143' - assert_output --partial 'Examine completed' +@test "should authenticate with XOAUTH2 over IMAP" { + _nc_wrapper 'auth/imap-oauth2-xoauth2.txt' '-w 1 0.0.0.0 143' + __verify_successful_login 'XOAUTH2' +} + +@test "should authenticate with OAUTHBEARER over IMAP" { + _nc_wrapper 'auth/imap-oauth2-oauthbearer.txt' '-w 1 0.0.0.0 143' + __verify_successful_login 'OAUTHBEARER' +} + +function __verify_successful_login() { + local AUTH_METHOD=${1} + + # Inspect the relevant Dovecot logs to catch failure / success: + _run_in_container grep 'dovecot:' /var/log/mail.log + refute_output --partial 'oauth2 failed: Introspection failed' + assert_output --partial "dovecot: imap-login: Login: user=, method=${AUTH_METHOD}" }