Merge branch 'master' into update-base-image
This commit is contained in:
commit
bb64ead29d
17
CHANGELOG.md
17
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:**
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 <<EOF
|
||||
{
|
||||
"email": "user1@localhost.localdomain",
|
||||
"email_verified": true
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Failed to authorize, close connection and send a 401 status (unauthorized):
|
||||
respond 401 {
|
||||
close
|
||||
}
|
||||
}
|
||||
|
||||
# /imap/xoauth2
|
||||
# Generate IMAP commands for authentication testing
|
||||
# Base64 encoded credentials can alternative be done via CLI with:
|
||||
# echo -en 'user=${USERNAME}\001auth=Bearer ${ACCESS_TOKEN}\001\001' | base64 -w0; echo
|
||||
#
|
||||
# Provide `user` and `access_token` values via query string parameters:
|
||||
# curl 'http://auth.example.test/imap/xoauth2?user=user1@localhost.localdomain&access_token=DMS_YWNjZXNzX3Rva2Vu'
|
||||
# curl 'http://auth.example.test/imap/oauthbearer?user=user1@localhost.localdomain&access_token=DMS_YWNjZXNzX3Rva2Vu'
|
||||
#
|
||||
# Example Response:
|
||||
# a0 AUTHENTICATE XOAUTH2 dXNlcj11c2VyMUBsb2NhbGhvc3QubG9jYWxkb21haW4BYXV0aD1CZWFyZXIgRE1TX1lXTmpaWE56WDNSdmEyVnUBAQ==
|
||||
# a1 EXAMINE INBOX
|
||||
# a2 LOGOUT
|
||||
#
|
||||
# When Dovecot queries /userinfo endpoint, it will be after base64 decoding the IMAP `AUTHENTICATE` value,
|
||||
# and sending the `auth` value from the `credentials` variable as an HTTP Authorization header.
|
||||
(route-imap) {
|
||||
# The login username + OAuth2 access token prior to Base64 encoding, as per the XOAUTH2 spec:
|
||||
# https://developers.google.com/gmail/imap/xoauth2-protocol#the_sasl_xoauth2_mechanism
|
||||
# For OAUTHBEARER `host` and `port` do not appear to affect authentication with Dovecot
|
||||
map {path} {sasl_mechanism} {credentials} {
|
||||
/xoauth2 XOAUTH2 "user={query.user}\001auth=Bearer {query.access_token}\001\001"
|
||||
/oauthbearer OAUTHBEARER "n,a={query.user},\001host=localhost\001port=143\001auth=Bearer {query.access_token}\001\001"
|
||||
}
|
||||
|
||||
# Responds with the raw IMAP commands for testing XOAUTH2 authentication.
|
||||
# Uses the `b64enc` template function to encode credentials as required for `IMAP AUTHENTICATE`:
|
||||
templates
|
||||
respond <<EOF
|
||||
a0 AUTHENTICATE {sasl_mechanism} {{b64enc "{credentials}"}}
|
||||
a1 EXAMINE INBOX
|
||||
a2 LOGOUT
|
||||
EOF
|
||||
}
|
||||
|
||||
# Routes the endpoints to the logical blocks extracted out as snippets above
|
||||
:80 {
|
||||
# This is the `/userinfo` endpoint that Dovecot connects to with the OAuth2 setting (default: `introspection_mode = auth`).
|
||||
# Example: curl http://auth.example.test/userinfo -H 'Authorization: Bearer DMS_YWNjZXNzX3Rva2Vu'
|
||||
handle_path /userinfo {
|
||||
import route-userinfo
|
||||
}
|
||||
|
||||
# An additional endpoint for maintainers to generate `test/files/auth/imap-oauth2-auth.txt`
|
||||
handle_path /imap/* {
|
||||
import route-imap
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# Provides support for running this container outside of tests
|
||||
# Run this with `docker compose up`
|
||||
services:
|
||||
caddy-oauth2:
|
||||
image: caddy:2.7
|
||||
container_name: dms-oauth2
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
|
||||
# Examples:
|
||||
# curl http://localhost:80/userinfo -H 'Authorization: Bearer DMS_YWNjZXNzX3Rva2Vu'
|
||||
# curl 'http://localhost:80/imap/xoauth2?user=user1@localhost.localdomain&access_token=DMS_YWNjZXNzX3Rva2Vu'
|
||||
# curl 'http://localhost:80/imap/oauthbearer?user=user1@localhost.localdomain&access_token=DMS_YWNjZXNzX3Rva2Vu'
|
|
@ -1,56 +0,0 @@
|
|||
# OAuth2 mock service
|
||||
#
|
||||
# Dovecot will query this service with the token it was provided.
|
||||
# If the session for the token is valid, a response provides an attribute to perform a UserDB lookup on (default: email).
|
||||
|
||||
import json
|
||||
import base64
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
# OAuth2.0 Bearer token (paste into https://jwt.io/ to check it's contents).
|
||||
# You should never need to edit this unless you REALLY need to change the issuer.
|
||||
token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vcHJvdmlkZXIuZXhhbXBsZS50ZXN0OjgwMDAvIiwic3ViIjoiODJjMWMzMzRkY2M2ZTMxMWFlNGFhZWJmZTk0NmM1ZTg1OGYwNTVhZmYxY2U1YTM3YWE3Y2M5MWFhYjE3ZTM1YyIsImF1ZCI6Im1haWxzZXJ2ZXIiLCJ1aWQiOiI4OU4zR0NuN1M1Y090WkZNRTVBeVhNbmxURFdVcnEzRmd4YWlyWWhFIn0.zuCytArbphhJn9XT_y9cBdGqDCNo68tBrtOwPIsuKNyF340SaOuZa0xarZofygytdDpLtYr56QlPTKImi-n1ZWrHkRZkwrQi5jQ-j_n2hEAL0vUToLbDnXYfc5q2w7z7X0aoCmiK8-fV7Kx4CVTM7riBgpElf6F3wNAIcX6R1ijUh6ISCL0XYsdogf8WUNZipXY-O4R7YHXdOENuOp3G48hWhxuUh9PsUqE5yxDwLsOVzCTqg9S5gxPQzF2eCN9J0I2XiIlLKvLQPIZ2Y_K7iYvVwjpNdgb4xhm9wuKoIVinYkF_6CwIzAawBWIDJAbix1IslkUPQMGbupTDtOgTiQ"
|
||||
|
||||
# This is the string the user-facing client (e.g. Roundcube) should send via IMAP to Dovecot.
|
||||
# We include the user and the above token separated by '\1' chars as per the XOAUTH2 spec.
|
||||
xoauth2 = base64.b64encode(f"user=user1@localhost.localdomain\1auth=Bearer {token}\1\1".encode("utf-8"))
|
||||
# If changing the user above, use the new output from the below line with the contents of the AUTHENTICATE command in test/test-files/auth/imap-oauth2-auth.txt
|
||||
print("XOAUTH2 string: " + str(xoauth2))
|
||||
|
||||
|
||||
class HTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
auth = self.headers.get("Authorization")
|
||||
if auth is None:
|
||||
self.send_response(401)
|
||||
self.end_headers()
|
||||
return
|
||||
if len(auth.split()) != 2:
|
||||
self.send_response(401)
|
||||
self.end_headers()
|
||||
return
|
||||
auth = auth.split()[1]
|
||||
# Valid session, respond with JSON containing the expected `email` claim to match as Dovecot username:
|
||||
if auth == token:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({
|
||||
"email": "user1@localhost.localdomain",
|
||||
"email_verified": True,
|
||||
"sub": "82c1c334dcc6e311ae4aaebfe946c5e858f055aff1ce5a37aa7cc91aab17e35c"
|
||||
}).encode("utf-8"))
|
||||
else:
|
||||
self.send_response(401)
|
||||
self.end_headers()
|
||||
|
||||
server = HTTPServer(('', 80), HTTPRequestHandler)
|
||||
print("Starting server", flush=True)
|
||||
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
print("Received keyboard interrupt")
|
||||
finally:
|
||||
print("Exiting")
|
|
@ -1,4 +0,0 @@
|
|||
a0 NOOP See test/config/oauth2/provider.py to generate the below XOAUTH2 string
|
||||
a1 AUTHENTICATE XOAUTH2 dXNlcj11c2VyMUBsb2NhbGhvc3QubG9jYWxkb21haW4BYXV0aD1CZWFyZXIgZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBjM01pT2lKb2RIUndPaTh2Y0hKdmRtbGtaWEl1WlhoaGJYQnNaUzUwWlhOME9qZ3dNREF2SWl3aWMzVmlJam9pT0RKak1XTXpNelJrWTJNMlpUTXhNV0ZsTkdGaFpXSm1aVGswTm1NMVpUZzFPR1l3TlRWaFptWXhZMlUxWVRNM1lXRTNZMk01TVdGaFlqRTNaVE0xWXlJc0ltRjFaQ0k2SW0xaGFXeHpaWEoyWlhJaUxDSjFhV1FpT2lJNE9VNHpSME51TjFNMVkwOTBXa1pOUlRWQmVWaE5ibXhVUkZkVmNuRXpSbWQ0WVdseVdXaEZJbjAuenVDeXRBcmJwaGhKbjlYVF95OWNCZEdxRENObzY4dEJydE93UElzdUtOeUYzNDBTYU91WmEweGFyWm9meWd5dGREcEx0WXI1NlFsUFRLSW1pLW4xWldySGtSWmt3clFpNWpRLWpfbjJoRUFMMHZVVG9MYkRuWFlmYzVxMnc3ejdYMGFvQ21pSzgtZlY3S3g0Q1ZUTTdyaUJncEVsZjZGM3dOQUljWDZSMWlqVWg2SVNDTDBYWXNkb2dmOFdVTlppcFhZLU80UjdZSFhkT0VOdU9wM0c0OGhXaHh1VWg5UHNVcUU1eXhEd0xzT1Z6Q1RxZzlTNWd4UFF6RjJlQ045SjBJMlhpSWxMS3ZMUVBJWjJZX0s3aVl2VndqcE5kZ2I0eGhtOXd1S29JVmluWWtGXzZDd0l6QWF3QldJREpBYml4MUlzbGtVUFFNR2J1cFREdE9nVGlRAQE=
|
||||
a2 EXAMINE INBOX
|
||||
a3 LOGOUT
|
|
@ -0,0 +1,4 @@
|
|||
a0 NOOP See test/config/oauth2/Caddyfile to generate the below OAUTHBEARER string
|
||||
a1 AUTHENTICATE OAUTHBEARER bixhPXVzZXIxQGxvY2FsaG9zdC5sb2NhbGRvbWFpbiwBaG9zdD1sb2NhbGhvc3QBcG9ydD0xNDMBYXV0aD1CZWFyZXIgRE1TX1lXTmpaWE56WDNSdmEyVnUBAQ==
|
||||
a2 EXAMINE INBOX
|
||||
a3 LOGOUT
|
|
@ -0,0 +1,4 @@
|
|||
a0 NOOP See test/config/oauth2/Caddyfile to generate the below XOAUTH2 string
|
||||
a1 AUTHENTICATE XOAUTH2 dXNlcj11c2VyMUBsb2NhbGhvc3QubG9jYWxkb21haW4BYXV0aD1CZWFyZXIgRE1TX1lXTmpaWE56WDNSdmEyVnUBAQ==
|
||||
a2 EXAMINE INBOX
|
||||
a3 LOGOUT
|
|
@ -11,19 +11,16 @@ function teardown() { _default_teardown ; }
|
|||
# Process matching notes:
|
||||
# opendkim (/usr/sbin/opendkim) - x2 of the same process are found running (1 is the parent)
|
||||
# opendmarc (/usr/sbin/opendmarc)
|
||||
# master (/usr/lib/postfix/sbin/master) - Postfix main process (Can take a few seconds running to be ready)
|
||||
# NOTE: pgrep or pkill used with `--full` would also match `/usr/sbin/amavisd-new (master)`
|
||||
#
|
||||
# amavi (/usr/sbin/amavi) - Matches three processes, the main process is `/usr/sbin/amavisd-new (master)`
|
||||
# NOTE: `amavisd-new` can only be matched with `--full`, regardless pkill would return `/usr/sbin/amavi`
|
||||
# postfix (/usr/lib/postfix/sbin/master) - Postfix main process (two ancestors, launched via pidproxy python3 script)
|
||||
#
|
||||
# amavisd-new (usr/sbin/amavisd-new)
|
||||
# clamd (/usr/sbin/clamd)
|
||||
# dovecot (/usr/sbin/dovecot)
|
||||
# fetchmail (/usr/bin/fetchmail)
|
||||
# fail2ban-server (/usr/bin/python3 /usr/bin/fail2ban-server) - Started by fail2ban-wrapper.sh
|
||||
# fail2ban-server (/usr/bin/python3 /usr/bin/fail2ban-server) - NOTE: python3 is due to the shebang
|
||||
# mta-sts-daemon (/usr/bin/bin/python3 /usr/bin/mta-sts-daemon)
|
||||
# postgrey (postgrey) - NOTE: This process lacks path information to match with `--full` in pgrep / pkill
|
||||
# postsrsd (/usr/sbin/postsrsd) - NOTE: Also matches the wrapper: `/bin/bash /usr/local/bin/postsrsd-wrapper.sh`
|
||||
# postgrey (postgrey) - NOTE: This process command uses perl via shebang, but unlike python3 the context is missing
|
||||
# postsrsd (/usr/sbin/postsrsd)
|
||||
# saslauthd (/usr/sbin/saslauthd) - x5 of the same process are found running (1 is a parent of 4)
|
||||
|
||||
# Delays:
|
||||
|
@ -31,17 +28,16 @@ function teardown() { _default_teardown ; }
|
|||
# dovecot + fail2ban, take approx 1 sec to kill properly
|
||||
# opendkim + opendmarc can take up to 6 sec to kill properly
|
||||
# clamd + postsrsd sometimes take 1-3 sec to restart after old process is killed.
|
||||
# postfix + fail2ban (due to Wrapper scripts) can delay a restart by up to 5 seconds from usage of sleep.
|
||||
|
||||
# These processes should always be running:
|
||||
CORE_PROCESS_LIST=(
|
||||
master
|
||||
postfix
|
||||
)
|
||||
|
||||
# These processes can be toggled via ENV:
|
||||
# NOTE: clamd handled in separate test case
|
||||
ENV_PROCESS_LIST=(
|
||||
amavi
|
||||
amavisd-new
|
||||
dovecot
|
||||
fail2ban-server
|
||||
fetchmail
|
||||
|
@ -89,7 +85,7 @@ ENV_PROCESS_LIST=(
|
|||
done
|
||||
}
|
||||
|
||||
# Average time: 23 seconds (29 with wrapper scripts)
|
||||
# Average time: 23 seconds
|
||||
@test "(enabled ENV) should restart processes when killed" {
|
||||
export CONTAINER_NAME=${CONTAINER2_NAME}
|
||||
local CONTAINER_ARGS_ENV_CUSTOM=(
|
||||
|
@ -153,14 +149,18 @@ function _should_restart_when_killed() {
|
|||
# Wait until process has been running for at least MIN_PROCESS_AGE:
|
||||
# (this allows us to more confidently check the process was restarted)
|
||||
_run_until_success_or_timeout 30 _check_if_process_is_running "${PROCESS}" "${MIN_PROCESS_AGE}"
|
||||
# NOTE: refute_output doesn't have output to compare to when a run failure is due to a timeout
|
||||
# NOTE: refute_output will not have any output to compare against if a `run` failure is caused by a timeout
|
||||
assert_success
|
||||
assert_output --partial "${PROCESS}"
|
||||
|
||||
# Should kill the process successfully:
|
||||
# (which should then get restarted by supervisord)
|
||||
_run_in_container pkill --echo "${PROCESS}"
|
||||
assert_output --partial "${PROCESS}"
|
||||
# NOTE: The process name from `pkill --echo` does not always match the equivalent processs name from `pgrep --list-full`.
|
||||
# The oldest process returned (if multiple) should be the top-level process launched by supervisord,
|
||||
# the PID will verify the target process was killed correctly:
|
||||
local PID=$(_exec_in_container pgrep --full --oldest "${PROCESS}")
|
||||
_run_in_container pkill --echo --full "${PROCESS}"
|
||||
assert_output --partial "killed (pid ${PID})"
|
||||
assert_success
|
||||
|
||||
# Wait until original process is not running:
|
||||
|
@ -176,13 +176,16 @@ function _should_restart_when_killed() {
|
|||
assert_output --partial "${PROCESS}"
|
||||
}
|
||||
|
||||
# NOTE: CONTAINER_NAME is implicit; it should have be set prior to calling.
|
||||
# NOTE: CONTAINER_NAME is implicit; it should have been set prior to calling.
|
||||
function _check_if_process_is_running() {
|
||||
local PROCESS=${1}
|
||||
local MIN_SECS_RUNNING
|
||||
[[ -n ${2:-} ]] && MIN_SECS_RUNNING=('--older' "${2}")
|
||||
|
||||
local IS_RUNNING=$(docker exec "${CONTAINER_NAME}" pgrep --list-full "${MIN_SECS_RUNNING[@]}" "${PROCESS}")
|
||||
# `--list-full` provides information for matching against (full process path)
|
||||
# `--full` allows matching the process against the full path (required if a process is not the exec command, such as started by python3 command without a shebang)
|
||||
# `--oldest` should select the parent process when there are multiple results, typically the command defined in `supervisor-app.conf`
|
||||
local IS_RUNNING=$(_exec_in_container pgrep --full --list-full "${MIN_SECS_RUNNING[@]}" --oldest "${PROCESS}")
|
||||
|
||||
# When no matches are found, nothing is returned. Provide something we can assert on (helpful for debugging):
|
||||
if [[ ! ${IS_RUNNING} =~ ${PROCESS} ]]; then
|
||||
|
|
|
@ -9,7 +9,7 @@ function setup_file() {
|
|||
export DMS_TEST_NETWORK='test-network-oauth2'
|
||||
export DMS_DOMAIN='example.test'
|
||||
export FQDN_MAIL="mail.${DMS_DOMAIN}"
|
||||
export FQDN_OAUTH2="oauth2.${DMS_DOMAIN}"
|
||||
export FQDN_OAUTH2="auth.${DMS_DOMAIN}"
|
||||
|
||||
# Link the test containers to separate network:
|
||||
# NOTE: If the network already exists, test will fail to start.
|
||||
|
@ -19,20 +19,19 @@ function setup_file() {
|
|||
docker run --rm -d --name "${CONTAINER2_NAME}" \
|
||||
--hostname "${FQDN_OAUTH2}" \
|
||||
--network "${DMS_TEST_NETWORK}" \
|
||||
--volume "${REPOSITORY_ROOT}/test/config/oauth2/:/app/" \
|
||||
docker.io/library/python:latest \
|
||||
python /app/provider.py
|
||||
--volume "${REPOSITORY_ROOT}/test/config/oauth2/Caddyfile:/etc/caddy/Caddyfile:ro" \
|
||||
caddy:2.7
|
||||
|
||||
_run_until_success_or_timeout 20 sh -c "docker logs ${CONTAINER2_NAME} 2>&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=<user1@localhost.localdomain>, method=${AUTH_METHOD}"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue