diff --git a/.gitignore b/.gitignore index 50d22a22..79a4dc3c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ ################################################# .env +compose.override.yaml docs/site/ docker-data/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 382b9efa..df9c6987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ All notable changes to this project will be documented in this file. The format > **Note**: Changes and additions listed here are contained in the `:edge` image tag. These changes may not be as stable as released changes. +### Features + +- **Authentication with OIDC / OAuth 2.0** 🎉 + - DMS now supports authentication via OAuth2 (_via `XOAUTH2` or `OAUTHBEARER` SASL mechanisms_) from capable services (_like Roundcube_). + - This does not replace the need for an `ACCOUNT_PROVISIONER` (`FILE` / `LDAP`), which is required for an account to receive or send mail. + - Successful authentication (_via Dovecot PassDB_) still requires an existing account (_lookup via Dovecot UserDB_). +- **MTA-STS** (_Optional support for mandatory outgoing TLS encryption_) + - If enabled and the outbound recipient has an MTA-STS policy set, TLS is mandatory for delivering to that recipient. + - Enable via the ENV `ENABLE_MTA_STS=1` + - Supported by major email service providers like Gmail, Yahoo and Outlook. + ### Updates - **Tests**: diff --git a/Dockerfile b/Dockerfile index 65d818ab..e822632a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -108,6 +108,13 @@ EOF COPY target/rspamd/local.d/ /etc/rspamd/local.d/ COPY target/rspamd/scores.d/* /etc/rspamd/scores.d/ +# ----------------------------------------------- +# --- OAUTH2 ------------------------------------ +# ----------------------------------------------- + +COPY target/dovecot/auth-oauth2.conf.ext /etc/dovecot/conf.d +COPY target/dovecot/dovecot-oauth2.conf.ext /etc/dovecot + # ----------------------------------------------- # --- LDAP & SpamAssassin's Cron ---------------- # ----------------------------------------------- @@ -192,6 +199,15 @@ COPY target/opendmarc/opendmarc.conf /etc/opendmarc.conf COPY target/opendmarc/default-opendmarc /etc/default/opendmarc COPY target/opendmarc/ignore.hosts /etc/opendmarc/ignore.hosts +# -------------------------------------------------- +# --- postfix-mta-sts-daemon ----------------------- +# -------------------------------------------------- +COPY target/mta-sts-daemon/mta-sts-daemon.yml /etc/mta-sts-daemon.yml +RUN < OAUTH2 authentication is disabled + # 1 => OAUTH2 authentication is enabled + ENABLE_OAUTH2=1 + + # Specify the user info endpoint URL of the oauth2 provider + OAUTH2_INTROSPECTION_URL=https://authentik.example.com/application/o/userinfo/ + ``` + + === "2. Authentik" + 1. Create a new OAuth2 provider + 2. Note the client id and client secret + 3. Set the allowed redirect url to the equivalent of `https://roundcube.example.com/index.php/login/oauth` for your RoundCube instance. + + === "3. Roundcube" + Add the following to `oauth2.inc.php` ([documentation](https://github.com/roundcube/roundcubemail/wiki/Configuration)): + + ```php + $config['oauth_provider'] = 'generic'; + $config['oauth_provider_name'] = 'Authentik'; + $config['oauth_client_id'] = ''; + $config['oauth_client_secret'] = ''; + $config['oauth_auth_uri'] = 'https://authentik.example.com/application/o/authorize/'; + $config['oauth_token_uri'] = 'https://authentik.example.com/application/o/token/'; + $config['oauth_identity_uri'] = 'https://authentik.example.com/application/o/userinfo/'; + + // Optional: disable SSL certificate check on HTTP requests to OAuth server. For possible values, see: + // http://docs.guzzlephp.org/en/stable/request-options.html#verify + $config['oauth_verify_peer'] = false; + + $config['oauth_scope'] = 'email openid profile'; + $config['oauth_identity_fields'] = ['email']; + + // Boolean: automatically redirect to OAuth login when opening Roundcube without a valid session + $config['oauth_login_redirect'] = false; + ``` diff --git a/docs/content/config/best-practices/mta-sts.md b/docs/content/config/best-practices/mta-sts.md new file mode 100644 index 00000000..1aebbdc5 --- /dev/null +++ b/docs/content/config/best-practices/mta-sts.md @@ -0,0 +1,30 @@ +--- +title: 'Best practices | MTA-STS' +hide: + - toc # Hide Table of Contents for this page +--- + +MTA-STS is an optional mechanism for a domain to signal support for STARTTLS. + +- It can be used to prevent man-in-the-middle-attacks from hiding STARTTLS support that would force DMS to send outbound mail through an insecure connection. +- MTA-STS is an alternative to DANE without the need of DNSSEC. +- MTA-STS is supported by some of the biggest mail providers like Google Mail and Outlook. + +## Supporting MTA-STS for outbound mail + +Enable this feature via the ENV setting [`ENABLE_MTA_STS=1`](../environment.md#enable_mta_sts). + +!!! warning "If you have configured DANE" + + Enabling MTA-STS will by default override DANE if both are configured for a domain. + + This can be partially addressed by configuring a dane-only policy resolver before the MTA-STS entry in `smtp_tls_policy_maps`. See the [`postfix-mta-sts-resolver` documentation][postfix-mta-sts-resolver::dane] for further details. + +[postfix-mta-sts-resolver::dane]: https://github.com/Snawoot/postfix-mta-sts-resolver#warning-mta-sts-policy-overrides-dane-tls-authentication + +## Supporting MTA-STS for inbound mail + +While this feature in DMS supports ensuring STARTTLS is used when mail is sent to another mail server, you may setup similar for mail servers sending mail to DMS. + +This requires configuring your DNS and hosting the MTA-STS policy file via a webserver. A good introduction can be found on [dmarcian.com](https://dmarcian.com/mta-sts/). + diff --git a/docs/content/config/environment.md b/docs/content/config/environment.md index 62639abc..1aa6799d 100644 --- a/docs/content/config/environment.md +++ b/docs/content/config/environment.md @@ -54,7 +54,15 @@ The Group ID assigned to the static vmail group for `/var/mail` (_Mail storage m Configures the provisioning source of user accounts (including aliases) for user queries and authentication by services managed by DMS (_Postfix and Dovecot_). -User provisioning via OIDC is planned for the future, see [this tracking issue](https://github.com/docker-mailserver/docker-mailserver/issues/2713). +!!! tip "OAuth2 Support" + + Presently DMS supports OAuth2 only as an supplementary authentication method. + + - A third-party service must provide a valid token for the user which Dovecot validates with the authentication service provider. To enable this feature reference the [OAuth2 configuration example guide][docs::auth::oauth2-config-guide]. + - User accounts must be provisioned to receive mail via one of the supported `ACCOUNT_PROVISIONER` providers. + - User provisioning via OIDC is planned for the future, see [this tracking issue](https://github.com/docker-mailserver/docker-mailserver/issues/2713). + +[docs::auth::oauth2-config-guide]: ./advanced/auth-oauth2.md - **empty** => use FILE - LDAP => use LDAP authentication @@ -108,6 +116,15 @@ This enables DNS block lists in _Postscreen_. If you want to know which lists we - **0** => DNS block lists are disabled - 1 => DNS block lists are enabled +##### ENABLE_MTA_STS + +Enables MTA-STS support for outbound mail. + +- **0** => Disabled +- 1 => Enabled + +See [MTA-STS](best-practices/mta-sts.md) for further explanation. + ##### ENABLE_OPENDKIM Enables the OpenDKIM service. @@ -716,10 +733,20 @@ Enable or disable `getmail`. - **5** => `getmail` The number of minutes for the interval. Min: 1; Max: 30; Default: 5. + +#### OAUTH2 + +##### ENABLE_OAUTH2 + +- **empty** => OAUTH2 authentication is disabled +- 1 => OAUTH2 authentication is enabled + +##### OAUTH2_INTROSPECTION_URL + +- => Specify the user info endpoint URL of the oauth2 provider (_eg: `https://oauth2.example.com/userinfo/`_) + #### LDAP - - ##### LDAP_START_TLS - **empty** => no diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2dc0214e..8a6a24b0 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -122,8 +122,9 @@ nav: - 'Environment Variables': config/environment.md - 'User Management': config/user-management.md - 'Best Practices': - - 'DKIM, DMARC & SPF': config/best-practices/dkim_dmarc_spf.md - 'Auto-discovery': config/best-practices/autodiscover.md + - 'DKIM, DMARC & SPF': config/best-practices/dkim_dmarc_spf.md + - 'MTA-STS': config/best-practices/mta-sts.md - 'Security': - 'Understanding the Ports': config/security/understanding-the-ports.md - 'SSL/TLS': config/security/ssl.md @@ -142,6 +143,7 @@ nav: - 'Postfix': config/advanced/override-defaults/postfix.md - 'Modifications via Script': config/advanced/override-defaults/user-patches.md - 'LDAP Authentication': config/advanced/auth-ldap.md + - 'OAuth2 Authentication': config/advanced/auth-oauth2.md - 'Email Filtering with Sieve': config/advanced/mail-sieve.md - 'Email Gathering with Fetchmail': config/advanced/mail-fetchmail.md - 'Email Gathering with Getmail': config/advanced/mail-getmail.md diff --git a/mailserver.env b/mailserver.env index 0c2e1e40..49bc2cca 100644 --- a/mailserver.env +++ b/mailserver.env @@ -354,6 +354,12 @@ POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME=0 # Note: More details at http://www.postfix.org/postconf.5.html#inet_protocols POSTFIX_INET_PROTOCOLS=all +# Enables MTA-STS support for outbound mail. +# More details: https://docker-mailserver.github.io/docker-mailserver/latest/config/advanced/mail-mta-sts/ +# - **0** ==> MTA-STS disabled +# - 1 => MTA-STS enabled +ENABLE_MTA_STS=0 + # Choose TCP/IP protocols for dovecot to use # **all** => Listen on all interfaces # ipv4 => Listen only on IPv4 interfaces. Most likely you want this behind Docker. @@ -419,6 +425,18 @@ ENABLE_GETMAIL=0 # The number of minutes for the interval. Min: 1; Max: 30. GETMAIL_POLL=5 +# ----------------------------------------------- +# --- OAUTH2 Section ---------------------------- +# ----------------------------------------------- + +# empty => OAUTH2 authentication is disabled +# 1 => OAUTH2 authentication is enabled +ENABLE_OAUTH2= + +# Specify the user info endpoint URL of the oauth2 provider +# Example: https://oauth2.example.com/userinfo/ +OAUTH2_INTROSPECTION_URL= + # ----------------------------------------------- # --- LDAP Section ------------------------------ # ----------------------------------------------- diff --git a/target/dovecot/10-auth.conf b/target/dovecot/10-auth.conf index f71289e9..260832fb 100644 --- a/target/dovecot/10-auth.conf +++ b/target/dovecot/10-auth.conf @@ -123,6 +123,7 @@ auth_mechanisms = plain login #!include auth-sql.conf.ext #!include auth-ldap.conf.ext !include auth-passwdfile.inc +#!include auth-oauth2.conf.ext #!include auth-checkpassword.conf.ext #!include auth-vpopmail.conf.ext #!include auth-static.conf.ext diff --git a/target/dovecot/auth-oauth2.conf.ext b/target/dovecot/auth-oauth2.conf.ext new file mode 100644 index 00000000..6096d1e4 --- /dev/null +++ b/target/dovecot/auth-oauth2.conf.ext @@ -0,0 +1,7 @@ +auth_mechanisms = $auth_mechanisms oauthbearer xoauth2 + +passdb { + driver = oauth2 + mechanisms = xoauth2 oauthbearer + args = /etc/dovecot/dovecot-oauth2.conf.ext +} diff --git a/target/dovecot/dovecot-oauth2.conf.ext b/target/dovecot/dovecot-oauth2.conf.ext new file mode 100644 index 00000000..6998ed08 --- /dev/null +++ b/target/dovecot/dovecot-oauth2.conf.ext @@ -0,0 +1,4 @@ +introspection_url = +# Dovecot defaults: +introspection_mode = auth +username_attribute = email diff --git a/target/mta-sts-daemon/mta-sts-daemon.yml b/target/mta-sts-daemon/mta-sts-daemon.yml new file mode 100644 index 00000000..4d5d5e55 --- /dev/null +++ b/target/mta-sts-daemon/mta-sts-daemon.yml @@ -0,0 +1,7 @@ +# Docs: https://github.com/Snawoot/postfix-mta-sts-resolver/blob/master/man/mta-sts-daemon.yml.5.adoc +path: /var/run/mta-sts/daemon.sock +mode: 0666 +cache: + type: sqlite + options: + filename: "/var/lib/mta-sts/cache.db" diff --git a/target/scripts/build/packages.sh b/target/scripts/build/packages.sh index e3607f48..e57cfe07 100644 --- a/target/scripts/build/packages.sh +++ b/target/scripts/build/packages.sh @@ -68,7 +68,7 @@ function _install_packages() { ) POSTFIX_PACKAGES=( - pflogsumm postgrey postfix-ldap + pflogsumm postgrey postfix-ldap postfix-mta-sts-resolver postfix-pcre postfix-policyd-spf-python postsrsd ) diff --git a/target/scripts/start-mailserver.sh b/target/scripts/start-mailserver.sh index 2129b74a..56dfa1fb 100755 --- a/target/scripts/start-mailserver.sh +++ b/target/scripts/start-mailserver.sh @@ -71,6 +71,11 @@ function _register_functions() { ;; esac + if [[ ${ENABLE_OAUTH2} -eq 1 ]]; then + _environment_variables_oauth2 + _register_setup_function '_setup_oauth2' + fi + if [[ ${ENABLE_SASLAUTHD} -eq 1 ]]; then _environment_variables_saslauthd _register_setup_function '_setup_saslauthd' @@ -115,6 +120,11 @@ function _register_functions() { _register_setup_function '_setup_apply_fixes_after_configuration' _register_setup_function '_environment_variables_export' + if [[ ${ENABLE_MTA_STS} -eq 1 ]]; then + _register_setup_function '_setup_mta_sts' + _register_start_daemon '_start_daemon_mta_sts_daemon' + fi + # ? >> Daemons _register_start_daemon '_start_daemon_cron' diff --git a/target/scripts/startup/daemons-stack.sh b/target/scripts/startup/daemons-stack.sh index 5476fc9f..a4cecf67 100644 --- a/target/scripts/startup/daemons-stack.sh +++ b/target/scripts/startup/daemons-stack.sh @@ -38,6 +38,7 @@ function _start_daemon_opendkim { _default_start_daemon 'opendkim' ; function _start_daemon_opendmarc { _default_start_daemon 'opendmarc' ; } function _start_daemon_postgrey { _default_start_daemon 'postgrey' ; } function _start_daemon_postsrsd { _default_start_daemon 'postsrsd' ; } +function _start_daemon_mta_sts_daemon { _default_start_daemon 'mta-sts-daemon' ; } function _start_daemon_rspamd { _default_start_daemon 'rspamd' ; } function _start_daemon_rspamd_redis { _default_start_daemon 'rspamd-redis' ; } function _start_daemon_rsyslog { _default_start_daemon 'rsyslog' ; } diff --git a/target/scripts/startup/setup.d/mail_state.sh b/target/scripts/startup/setup.d/mail_state.sh index 73c2515b..9963bbcc 100644 --- a/target/scripts/startup/setup.d/mail_state.sh +++ b/target/scripts/startup/setup.d/mail_state.sh @@ -24,6 +24,7 @@ function _setup_save_states() { [[ ${ENABLE_FAIL2BAN} -eq 1 ]] && SERVICEDIRS+=('lib/fail2ban') [[ ${ENABLE_FETCHMAIL} -eq 1 ]] && SERVICEDIRS+=('lib/fetchmail') [[ ${ENABLE_GETMAIL} -eq 1 ]] && SERVICEDIRS+=('lib/getmail') + [[ ${ENABLE_MTA_STS} -eq 1 ]] && SERVICEDIRS+=('lib/mta-sts') [[ ${ENABLE_POSTGREY} -eq 1 ]] && SERVICEDIRS+=('lib/postgrey') [[ ${ENABLE_RSPAMD} -eq 1 ]] && SERVICEDIRS+=('lib/rspamd') [[ ${ENABLE_RSPAMD_REDIS} -eq 1 ]] && SERVICEDIRS+=('lib/redis') @@ -84,6 +85,7 @@ function _setup_save_states() { [[ ${ENABLE_AMAVIS} -eq 1 ]] && chown -R amavis:amavis "${STATEDIR}/lib-amavis" [[ ${ENABLE_CLAMAV} -eq 1 ]] && chown -R clamav:clamav "${STATEDIR}/lib-clamav" [[ ${ENABLE_FETCHMAIL} -eq 1 ]] && chown -R fetchmail:nogroup "${STATEDIR}/lib-fetchmail" + [[ ${ENABLE_MTA_STS} -eq 1 ]] && chown -R _mta-sts:_mta-sts "${STATEDIR}/lib-mta-sts" [[ ${ENABLE_POSTGREY} -eq 1 ]] && chown -R postgrey:postgrey "${STATEDIR}/lib-postgrey" [[ ${ENABLE_RSPAMD} -eq 1 ]] && chown -R _rspamd:_rspamd "${STATEDIR}/lib-rspamd" [[ ${ENABLE_RSPAMD_REDIS} -eq 1 ]] && chown -R redis:redis "${STATEDIR}/lib-redis" diff --git a/target/scripts/startup/setup.d/mta-sts.sh b/target/scripts/startup/setup.d/mta-sts.sh new file mode 100644 index 00000000..7d1f88ea --- /dev/null +++ b/target/scripts/startup/setup.d/mta-sts.sh @@ -0,0 +1,7 @@ +#!/bin/bash + + +function _setup_mta_sts() { + _log 'trace' 'Adding MTA-STS lookup to the Postfix TLS policy map' + _add_to_or_update_postfix_main smtp_tls_policy_maps 'socketmap:unix:/var/run/mta-sts/daemon.sock:postfix' +} diff --git a/target/scripts/startup/setup.d/oauth2.sh b/target/scripts/startup/setup.d/oauth2.sh new file mode 100644 index 00000000..20e9ffd1 --- /dev/null +++ b/target/scripts/startup/setup.d/oauth2.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +function _setup_oauth2() { + _log 'debug' 'Setting up OAUTH2' + + # Enable OAuth2 PassDB (Authentication): + sedfile -i -e '/\!include auth-oauth2\.conf\.ext/s/^#//' /etc/dovecot/conf.d/10-auth.conf + _replace_by_env_in_file 'OAUTH2_' '/etc/dovecot/dovecot-oauth2.conf.ext' + + return 0 +} diff --git a/target/scripts/startup/variables-stack.sh b/target/scripts/startup/variables-stack.sh index 2660ce89..0b351a9e 100644 --- a/target/scripts/startup/variables-stack.sh +++ b/target/scripts/startup/variables-stack.sh @@ -83,6 +83,7 @@ function __environment_variables_general_setup() { VARS[ENABLE_FETCHMAIL]="${ENABLE_FETCHMAIL:=0}" VARS[ENABLE_GETMAIL]="${ENABLE_GETMAIL:=0}" VARS[ENABLE_MANAGESIEVE]="${ENABLE_MANAGESIEVE:=0}" + VARS[ENABLE_OAUTH2]="${ENABLE_OAUTH2:=0}" VARS[ENABLE_OPENDKIM]="${ENABLE_OPENDKIM:=1}" VARS[ENABLE_OPENDMARC]="${ENABLE_OPENDMARC:=1}" VARS[ENABLE_POLICYD_SPF]="${ENABLE_POLICYD_SPF:=1}" @@ -151,6 +152,12 @@ function __environment_variables_general_setup() { VARS[UPDATE_CHECK_INTERVAL]="${UPDATE_CHECK_INTERVAL:=1d}" } +function _environment_variables_oauth2() { + _log 'debug' 'Setting OAUTH2-related environment variables now' + + VARS[OAUTH2_INTROSPECTION_URL]="${OAUTH2_INTROSPECTION_URL:=}" +} + # This function handles environment variables related to LDAP. # NOTE: SASLAuthd and Dovecot LDAP support inherit these common ENV. function _environment_variables_ldap() { diff --git a/target/supervisor/conf.d/supervisor-app.conf b/target/supervisor/conf.d/supervisor-app.conf index 431357d8..d64d3d72 100644 --- a/target/supervisor/conf.d/supervisor-app.conf +++ b/target/supervisor/conf.d/supervisor-app.conf @@ -157,3 +157,15 @@ autostart=false stdout_logfile=/var/log/supervisor/%(program_name)s.log stderr_logfile=/var/log/supervisor/%(program_name)s.log command=/bin/bash -l -c /usr/local/bin/update-check.sh + +# Docs: https://github.com/Snawoot/postfix-mta-sts-resolver/blob/master/man/mta-sts-daemon.1.adoc +[program:mta-sts-daemon] +startsecs=0 +stopwaitsecs=55 +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +command=/usr/bin/mta-sts-daemon --config /etc/mta-sts-daemon.yml +user=_mta-sts +environment=HOME=/var/lib/mta-sts diff --git a/test/config/oauth2/provider.py b/test/config/oauth2/provider.py new file mode 100644 index 00000000..22fc8129 --- /dev/null +++ b/test/config/oauth2/provider.py @@ -0,0 +1,56 @@ +# 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") diff --git a/test/files/auth/imap-oauth2-auth.txt b/test/files/auth/imap-oauth2-auth.txt new file mode 100644 index 00000000..825fabda --- /dev/null +++ b/test/files/auth/imap-oauth2-auth.txt @@ -0,0 +1,4 @@ +a0 NOOP See test/config/oauth2/provider.py to generate the below XOAUTH2 string +a1 AUTHENTICATE XOAUTH2 dXNlcj11c2VyMUBsb2NhbGhvc3QubG9jYWxkb21haW4BYXV0aD1CZWFyZXIgZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBjM01pT2lKb2RIUndPaTh2Y0hKdmRtbGtaWEl1WlhoaGJYQnNaUzUwWlhOME9qZ3dNREF2SWl3aWMzVmlJam9pT0RKak1XTXpNelJrWTJNMlpUTXhNV0ZsTkdGaFpXSm1aVGswTm1NMVpUZzFPR1l3TlRWaFptWXhZMlUxWVRNM1lXRTNZMk01TVdGaFlqRTNaVE0xWXlJc0ltRjFaQ0k2SW0xaGFXeHpaWEoyWlhJaUxDSjFhV1FpT2lJNE9VNHpSME51TjFNMVkwOTBXa1pOUlRWQmVWaE5ibXhVUkZkVmNuRXpSbWQ0WVdseVdXaEZJbjAuenVDeXRBcmJwaGhKbjlYVF95OWNCZEdxRENObzY4dEJydE93UElzdUtOeUYzNDBTYU91WmEweGFyWm9meWd5dGREcEx0WXI1NlFsUFRLSW1pLW4xWldySGtSWmt3clFpNWpRLWpfbjJoRUFMMHZVVG9MYkRuWFlmYzVxMnc3ejdYMGFvQ21pSzgtZlY3S3g0Q1ZUTTdyaUJncEVsZjZGM3dOQUljWDZSMWlqVWg2SVNDTDBYWXNkb2dmOFdVTlppcFhZLU80UjdZSFhkT0VOdU9wM0c0OGhXaHh1VWg5UHNVcUU1eXhEd0xzT1Z6Q1RxZzlTNWd4UFF6RjJlQ045SjBJMlhpSWxMS3ZMUVBJWjJZX0s3aVl2VndqcE5kZ2I0eGhtOXd1S29JVmluWWtGXzZDd0l6QWF3QldJREpBYml4MUlzbGtVUFFNR2J1cFREdE9nVGlRAQE= +a2 EXAMINE INBOX +a3 LOGOUT diff --git a/test/tests/parallel/set3/container_configuration/process_check_restart.bats b/test/tests/parallel/set3/container_configuration/process_check_restart.bats index b559d21d..4b01454e 100644 --- a/test/tests/parallel/set3/container_configuration/process_check_restart.bats +++ b/test/tests/parallel/set3/container_configuration/process_check_restart.bats @@ -21,6 +21,7 @@ function teardown() { _default_teardown ; } # dovecot (/usr/sbin/dovecot) # fetchmail (/usr/bin/fetchmail) # fail2ban-server (/usr/bin/python3 /usr/bin/fail2ban-server) - Started by fail2ban-wrapper.sh +# 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` # saslauthd (/usr/sbin/saslauthd) - x5 of the same process are found running (1 is a parent of 4) @@ -44,6 +45,7 @@ ENV_PROCESS_LIST=( dovecot fail2ban-server fetchmail + mta-sts-daemon opendkim opendmarc postgrey @@ -58,6 +60,7 @@ ENV_PROCESS_LIST=( --env ENABLE_CLAMAV=0 --env ENABLE_FAIL2BAN=0 --env ENABLE_FETCHMAIL=0 + --env ENABLE_MTA_STS=0 --env ENABLE_OPENDKIM=0 --env ENABLE_OPENDMARC=0 --env ENABLE_POSTGREY=0 @@ -93,6 +96,7 @@ ENV_PROCESS_LIST=( --env ENABLE_AMAVIS=1 --env ENABLE_FAIL2BAN=1 --env ENABLE_FETCHMAIL=1 + --env ENABLE_MTA_STS=1 --env ENABLE_OPENDKIM=1 --env ENABLE_OPENDMARC=1 --env FETCHMAIL_PARALLEL=1 diff --git a/test/tests/serial/mail_with_oauth2.bats b/test/tests/serial/mail_with_oauth2.bats new file mode 100644 index 00000000..0d73bc54 --- /dev/null +++ b/test/tests/serial/mail_with_oauth2.bats @@ -0,0 +1,66 @@ +load "${REPOSITORY_ROOT}/test/helper/setup" +load "${REPOSITORY_ROOT}/test/helper/common" + +BATS_TEST_NAME_PREFIX='[OAuth2] ' +CONTAINER1_NAME='dms-test_oauth2' +CONTAINER2_NAME='dms-test_oauth2_provider' + +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}" + + # Link the test containers to separate network: + # NOTE: If the network already exists, test will fail to start. + docker network create "${DMS_TEST_NETWORK}" + + # Setup local oauth2 provider service: + 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 + + _run_until_success_or_timeout 20 sh -c "docker logs ${CONTAINER2_NAME} 2>&1 | grep 'Starting server'" + + # + # Setup DMS container + # + + # Add OAUTH2 configuration so that Dovecot can reach out to our mock provider (CONTAINER2) + local ENV_OAUTH2_CONFIG=( + --env ENABLE_OAUTH2=1 + --env OAUTH2_INTROSPECTION_URL=http://oauth2.example.test/userinfo/ + ) + + export CONTAINER_NAME=${CONTAINER1_NAME} + local CUSTOM_SETUP_ARGUMENTS=( + "${ENV_OAUTH2_CONFIG[@]}" + + --hostname "${FQDN_MAIL}" + --network "${DMS_TEST_NETWORK}" + ) + + _init_with_defaults + _common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + _wait_for_tcp_port_in_container 143 + + # Set default implicit container fallback for helpers: + export CONTAINER_NAME=${CONTAINER1_NAME} +} + +function teardown_file() { + docker rm -f "${CONTAINER1_NAME}" "${CONTAINER2_NAME}" + 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' +}