diff --git a/Containerfile b/Containerfile index a5a3487..8debbfd 100644 --- a/Containerfile +++ b/Containerfile @@ -1,11 +1,93 @@ -FROM docker.io/thallian/confd-env:3.20-3.1.6.2 +FROM docker.io/alpine:3.19 as builder RUN apk --no-cache add \ + rpcgen \ + g++ \ + make \ + openssl \ + openssl-dev \ + lua5.1-dev \ + libsodium-dev \ + linux-pam-dev \ + zlib-dev \ + bzip2-dev \ + xz-dev \ + lz4-dev \ + icu-dev \ + inotify-tools-dev + +ENV SHA256_SUM_DOVECOT=05b11093a71c237c2ef309ad587510721cc93bbee6828251549fc1586c36502d +ENV DOVECOT_FILENAME=dovecot-2.3.21.tar.gz +RUN wget https://www.dovecot.org/releases/2.3/$DOVECOT_FILENAME +RUN echo "$SHA256_SUM_DOVECOT $DOVECOT_FILENAME" | sha256sum -c - || exit 1 +RUN mkdir /tmp/dovecot +RUN tar xzf $DOVECOT_FILENAME -C /tmp/dovecot --strip 1 + +ENV SHA256_SUM_PIGEONHOLE=1ca71d2659076712058a72030288f150b2b076b0306453471c5261498d3ded27 +ENV PIGEONHOLE_FILENAME=dovecot-2.3-pigeonhole-0.5.21.tar.gz +RUN wget https://pigeonhole.dovecot.org/releases/2.3/$PIGEONHOLE_FILENAME +RUN echo "$SHA256_SUM_PIGEONHOLE $PIGEONHOLE_FILENAME" | sha256sum -c - || exit 1 +RUN mkdir /tmp/pigeonhole +RUN tar xzf $PIGEONHOLE_FILENAME -C /tmp/pigeonhole --strip 1 + +RUN cd /tmp/dovecot && \ + ./configure --prefix '' \ + --with-notify=inotify \ + --with-lua \ + --with-zlib \ + --with-bzlib \ + --with-pam \ + --with-ssl=openssl \ + --with-sodium \ + --without-sql \ + --with-lzma \ + --with-lz4 \ + --with-icu \ + --without-shadow \ + --with-ssldir=/etc/ssl/mail \ + --with-rundir=/run/dovecot \ + --disable-static && \ + make && \ + make install + +RUN cd /tmp/pigeonhole && \ + ./configure --prefix '' \ + --with-dovecot=/lib/dovecot \ + --disable-static && \ + make && make install + +FROM docker.io/thallian/confd-env:3.19-3.1.6.2 + +COPY --from=builder /lib/dovecot/ /lib/dovecot/ +COPY --from=builder /libexec/dovecot/ /libexec/dovecot/ +COPY --from=builder /bin/doveadm /bin/doveadm +COPY --from=builder /bin/doveconf /bin/doveconf +COPY --from=builder /bin/dsync /bin/dsync +COPY --from=builder /sbin/dovecot /sbin/dovecot +COPY --from=builder /bin/sieve* /bin/ + +RUN apk --no-cache add \ + libsodium \ + libbz2 \ + zlib \ + xz-libs \ + lz4-libs \ + lz4 \ + linux-pam \ + openssl \ ssmtp \ - dovecot \ - dovecot-pgsql \ - dovecot-lmtpd \ - dovecot-pigeonhole-plugin + ca-certificates \ + lua5.1-libs \ + lua5.1-rapidjson \ + curl \ + inotify-tools \ + libssl3 + +RUN addgroup -g 150 dovecot +RUN adduser -u 140 -h /dev/null -H -s /sbin/nologin -D -G dovecot dovecot + +RUN addgroup -g 151 dovenull +RUN adduser -u 141 -h /dev/null -H -s /sbin/nologin -D -G dovenull dovenull RUN addgroup -g 2222 access RUN addgroup dovecot access diff --git a/README.md b/README.md index 2b4c0a4..4f4855e 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,75 @@ -[Dovecot](http://www.dovecot.org/) with imap, starttls, oauth2 proxy auth and -sieve rules. +[Dovecot](http://www.dovecot.org/) with imap, starttls, oauth2 proxy auth and sieve rules. -Uses [SSMTP](https://packages.debian.org/stable/mail/ssmtp) to send mails (for -example if you have a redirect sieve rule). +Uses [SSMTP](https://packages.debian.org/stable/mail/ssmtp) to send mails (for example if you have a redirect sieve rule). -Reuses the same database schema as the {postfix container](/container/postfix). # Volumes - - `/var/lib/vmail/mail` # Environment Variables - ## HOSTNAME - Fully qualified name of the mail host. ## GRANT_URL - OAuth2 url for token grants (password grant type). ## INTROSPECTION_URL +OAuth2 url for token information. -OAuth2 url for token information. Must include client id and client secret in -basic auth format. +## USER_URL +OAuth2 url for getting available users, the username will be appended to the end. -## TOKENINFO_URL +## TOKENINFO_URL +OAuth2 url for requestion information about a token. -OAuth2 url for requestion information about a token. Must include client id and -client secret in basic auth format. +## CLIENT_ID +Id of the OAuth2 application. -## DB_HOST +## CLIENT_SECRET +Secret of the OAuth2 application. -Postgre database host. +## OAUTH_ADMIN_USER +User with which to perform user lookups (does not have to be an admin, but needs enough rights for that). -## DB_USER - -User to connect to the database. - -## DB_PW - -Password to use for the database user. - -## DB_NAME - -- default: email - -Name of the postgre database to connect to. +## OAUTH_ADMIN_PASSWORD +Password for the `OAUTH_ADMIN_USER`. ## SSMTP_MAIL_RELAY - Hostname and port for the used smtp relay (for example `mail.example.com:587`). ## SSMTP_USER - User to authenticate agains the smtp relay. ## SSMTP_PASSWORD - Password to authenticate agains the smtp relay. ## SSMTP_AUTH_METHOD - - default: LOGIN Which authentication mechanism to use for the smtp relay. ## SSMTP_USE_STARTTLS - - default: yes Whether to use starttls for the smtp relay. ## ALLOWED_USERNAME_CHARS - -- default: - äöüabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@ +- default: äöüabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@ List of characters allowed in a username. ## AUTH_MECHANISMS - - default: plain -Space seperated list of supported -[authentication mechanisms](http://wiki2.dovecot.org/Authentication/Mechanisms). +Space seperated list of supported [authentication mechanisms](http://wiki2.dovecot.org/Authentication/Mechanisms). ## SSL_MIN_PROTOCOL - - default: TLSv1.2 Ssl minimum protocol version. ## SSL_CIPHERLIST - -- default: - ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 +- default: ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 Colon seperated list of supported ciphers (`!`disables a cipher). @@ -103,17 +77,14 @@ Go [here](https://www.openssl.org/docs/manmaster/man1/ciphers.html) for a list of ciphers. ## IMAP_MAX_USER_CONNECTIONS - - default: 10 Maximum number of connections from the same user + ip. # Ports - - 143 # Capabilities - - CHOWN - DAC_OVERRIDE - FOWNER diff --git a/rootfs/etc/confd/conf.d/dovecot-sql.userdb.ext.toml b/rootfs/etc/confd/conf.d/dovecot-sql.userdb.ext.toml deleted file mode 100644 index ba1646d..0000000 --- a/rootfs/etc/confd/conf.d/dovecot-sql.userdb.ext.toml +++ /dev/null @@ -1,3 +0,0 @@ -[template] -src = "dovecot-sql.userdb.conf.ext.tmpl" -dest = "/etc/dovecot/dovecot-sql.userdb.conf.ext" diff --git a/rootfs/etc/confd/conf.d/oauth2-userdb.lua.toml b/rootfs/etc/confd/conf.d/oauth2-userdb.lua.toml new file mode 100644 index 0000000..cfb53ac --- /dev/null +++ b/rootfs/etc/confd/conf.d/oauth2-userdb.lua.toml @@ -0,0 +1,3 @@ +[template] +src = "oauth2-userdb.lua.tmpl" +dest = "/etc/dovecot/oauth2-userdb.lua" diff --git a/rootfs/etc/confd/templates/10-auth.conf.tmpl b/rootfs/etc/confd/templates/10-auth.conf.tmpl index 8d99a1f..498385f 100644 --- a/rootfs/etc/confd/templates/10-auth.conf.tmpl +++ b/rootfs/etc/confd/templates/10-auth.conf.tmpl @@ -1,5 +1,4 @@ auth_username_chars = {{getenv "ALLOWED_USERNAME_CHARS" "äöüabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@"}} auth_username_format ="%Ln" auth_mechanisms = {{getenv "AUTH_MECHANISMS" "plain login oauthbearer xoauth2"}} -disable_plaintext_auth = no !include auth-oauth2.conf.ext diff --git a/rootfs/etc/confd/templates/10-mail.conf.tmpl b/rootfs/etc/confd/templates/10-mail.conf.tmpl index f15ee9c..3c064c3 100644 --- a/rootfs/etc/confd/templates/10-mail.conf.tmpl +++ b/rootfs/etc/confd/templates/10-mail.conf.tmpl @@ -23,4 +23,4 @@ namespace inbox { } } -mail_plugin_dir = /usr/lib/dovecot +mail_plugin_dir = /lib/dovecot diff --git a/rootfs/etc/confd/templates/10-master.conf.tmpl b/rootfs/etc/confd/templates/10-master.conf.tmpl index 391b1b4..928ebf7 100644 --- a/rootfs/etc/confd/templates/10-master.conf.tmpl +++ b/rootfs/etc/confd/templates/10-master.conf.tmpl @@ -2,11 +2,6 @@ service imap-login { inet_listener imap { port = 143 } - - inet_listener imaps { - port = 993 - ssl = yes - } } service lmtp { diff --git a/rootfs/etc/confd/templates/20-managesieve.conf.tmpl b/rootfs/etc/confd/templates/20-managesieve.conf.tmpl index bfcce1c..ae406c3 100644 --- a/rootfs/etc/confd/templates/20-managesieve.conf.tmpl +++ b/rootfs/etc/confd/templates/20-managesieve.conf.tmpl @@ -5,6 +5,7 @@ protocols = $protocols sieve service managesieve-login { inet_listener sieve { port = 4190 + address = localhost } #inet_listener sieve_deprecated { diff --git a/rootfs/etc/confd/templates/auth-oauth2.conf.ext.tmpl b/rootfs/etc/confd/templates/auth-oauth2.conf.ext.tmpl index 5cfcf25..7d1a33a 100644 --- a/rootfs/etc/confd/templates/auth-oauth2.conf.ext.tmpl +++ b/rootfs/etc/confd/templates/auth-oauth2.conf.ext.tmpl @@ -11,7 +11,6 @@ passdb { } userdb { - driver = sql - args = /etc/dovecot/dovecot-sql.userdb.conf.ext - default_fields = uid=vmail gid=vmail home=/var/lib/vmail/mail/%u + driver = lua + args = file=/etc/dovecot/oauth2-userdb.lua blocking=yes } diff --git a/rootfs/etc/confd/templates/dovecot-oauth2.plain.conf.ext.tmpl b/rootfs/etc/confd/templates/dovecot-oauth2.plain.conf.ext.tmpl index 743a60e..419a169 100644 --- a/rootfs/etc/confd/templates/dovecot-oauth2.plain.conf.ext.tmpl +++ b/rootfs/etc/confd/templates/dovecot-oauth2.plain.conf.ext.tmpl @@ -1,4 +1,6 @@ grant_url = {{ getenv "GRANT_URL" }} +client_id = {{ getenv "CLIENT_ID" }} +client_secret = {{ getenv "CLIENT_SECRET" }} introspection_url = {{ getenv "INTROSPECTION_URL" }} introspection_mode = {{ getenv "INTROSPECTION_MODE" "post" }} username_attribute = username diff --git a/rootfs/etc/confd/templates/dovecot-oauth2.token.conf.ext.tmpl b/rootfs/etc/confd/templates/dovecot-oauth2.token.conf.ext.tmpl index cc5b90e..4940f23 100644 --- a/rootfs/etc/confd/templates/dovecot-oauth2.token.conf.ext.tmpl +++ b/rootfs/etc/confd/templates/dovecot-oauth2.token.conf.ext.tmpl @@ -1,4 +1,6 @@ grant_url = {{ getenv "GRANT_URL" }} +client_id = {{ getenv "CLIENT_ID" }} +client_secret = {{ getenv "CLIENT_SECRET" }} tokeninfo_url = {{ getenv "TOKENINFO_URL" }} introspection_url = {{ getenv "INTROSPECTION_URL" }} introspection_mode = {{ getenv "INTROSPECTION_MODE" "post" }} @@ -6,3 +8,4 @@ username_attribute = username tls_ca_cert_file = /etc/ssl/certs/ca-certificates.crt use_grant_password = no pass_attrs = pass=%{oauth2:access_token} + diff --git a/rootfs/etc/confd/templates/dovecot-sql.userdb.conf.ext.tmpl b/rootfs/etc/confd/templates/dovecot-sql.userdb.conf.ext.tmpl deleted file mode 100644 index e87aed7..0000000 --- a/rootfs/etc/confd/templates/dovecot-sql.userdb.conf.ext.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -driver = pgsql -connect = host={{ getenv "DB_HOST" }} dbname={{ getenv "DB_NAME" }} user={{ getenv "DB_USER" }} password={{ getenv "DB_PW" }} -user_query = SELECT COUNT(email) as count FROM virtual_users WHERE email = '%n' HAVING COUNT(email) > 0; diff --git a/rootfs/etc/confd/templates/oauth2-userdb.lua.tmpl b/rootfs/etc/confd/templates/oauth2-userdb.lua.tmpl new file mode 100644 index 0000000..cc97b81 --- /dev/null +++ b/rootfs/etc/confd/templates/oauth2-userdb.lua.tmpl @@ -0,0 +1,44 @@ +local rapidjson = require('rapidjson') + +local clientId = "{{ getenv "CLIENT_ID" }}" +local clientSecret = "{{ getenv "CLIENT_SECRET" }}" +local username = "{{ getenv "OAUTH_ADMIN_USER" }}" +local password = "{{ getenv "OAUTH_ADMIN_PASSWORD" }}" +local tokenUrl = "{{ getenv "GRANT_URL" }}" +local userUrl = "{{ getenv "USER_URL" }}" + +function os.capture(cmd, raw) + local f = assert(io.popen(cmd, 'r')) + local s = assert(f:read('*a')) + f:close() + + return s +end + +function auth_userdb_lookup(req) + local tokenCmd = "curl -L --silent -X POST -d \"grant_type=password\"" + tokenCmd = tokenCmd .. " -d \"client_id=" .. clientId .. "\"" + tokenCmd = tokenCmd .. " -d \"client_secret=" .. clientSecret .. "\"" + tokenCmd = tokenCmd .. " -d \"username=" .. username .. "\"" + tokenCmd = tokenCmd .. " -d \"password=" .. password .. "\"" + tokenCmd = tokenCmd .. " \"" .. tokenUrl .. "\"" + + local tokenRaw = os.capture(tokenCmd) + local tokenJson = rapidjson.decode(tokenRaw) + local accessToken = tokenJson.access_token + + local userCmd = "curl -L --silent -H \"Authorization: Bearer " .. accessToken .. "\" \"" .. userUrl .. req.username .. "\"" + local userRaw = os.capture(userCmd) + local userJson = rapidjson.decode(userRaw) + + if #userJson == 0 then + return dovecot.auth.USERDB_RESULT_USER_UNKNOWN, "no such user" + end + + if userJson[1].username == req.username then + return dovecot.auth.USERDB_RESULT_OK, "uid=vmail gid=vmail home=/var/lib/vmail/mail/" .. req.username + end + + return dovecot.auth.USERDB_RESULT_USER_UNKNOWN, "no such user" +end +