diff --git a/Dockerfile b/Dockerfile index 54f9e99..bd08f53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,13 @@ FROM debian:bullseye-slim +ARG INCLUDE_DMARC=false +ENV INCLUDE_DMARC=${INCLUDE_DMARC} + RUN DEBIAN_FRONTEND=noninteractive apt update \ && DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends curl perl postfix ruby socat \ + && if [ "$INCLUDE_DMARC" = "true" ]; then \ + DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends opendmarc opendkim opendkim-tools postfix-policyd-spf-python; \ + fi \ && DEBIAN_FRONTEND=noninteractive apt -y --purge autoremove \ && DEBIAN_FRONTEND=noninteractive apt clean @@ -19,12 +25,27 @@ RUN >/etc/postfix/main.cf \ && postconf -e alias_maps= \ && postconf -e mynetworks='127.0.0.0/8 [::1]/128 [fe80::]/64' \ && postconf -e transport_maps=hash:/etc/postfix/transport \ - && postconf -e 'smtpd_recipient_restrictions = check_policy_service unix:private/policy' \ + && if [ "$INCLUDE_DMARC" = "true" ]; then \ + postconf -e 'smtpd_recipient_restrictions=check_policy_service unix:private/policy,check_policy_service unix:private/policyd-spf' \ + && postconf -e smtpd_milters=unix:/run/opendkim/opendkim.sock,unix:/run/opendmarc/opendmarc.sock \ + && postconf -e non_smtpd_milters=$smtpd_milters \ + && postconf -e 'milter_default_action=accept'; \ + else \ + postconf -e 'smtpd_recipient_restrictions = check_policy_service unix:private/policy'; \ + fi \ && postconf -M -e 'smtp/inet=smtp inet n - n - - smtpd' \ && postconf -M -e 'discourse/unix=discourse unix - n n - - pipe user=nobody:nogroup argv=/usr/local/bin/receive-mail ${recipient}' \ && postconf -M -e 'policy/unix=policy unix - n n - - spawn user=nobody argv=/usr/local/bin/discourse-smtp-fast-rejection' \ + && if [ "$INCLUDE_DMARC" = "true" ]; then \ + postconf -M -e 'policyd-spf/unix=policyd-spf unix - n n - - spawn user=nobody argv=/usr/bin/policyd-spf'; \ + fi \ && rm -rf /var/spool/postfix/* + +COPY policyd-spf.conf /etc/postfix-policyd-spf-python/policyd-spf.conf +COPY opendkim.conf /etc/opendkim.conf +COPY opendmarc.conf /etc/opendmarc.conf + COPY receive-mail discourse-smtp-fast-rejection /usr/local/bin/ COPY lib/ /usr/local/lib/site_ruby/ COPY boot /sbin/ diff --git a/README.md b/README.md index c314afc..fe25cc7 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,15 @@ For example, if you wanted to add a pre-delivery milter, you might use: -e POSTCONF_smtpd_milters=192.0.2.42:12345 +## SPF, DKIM and DMARC checks +These checks are enabled by default in the latest image `discourse/mail-receiver:release`. + +You may also decide whether or not to set up a Postfix server that has SPF, DKIM and DMARC checks configured by setting the build argument `INCLUDE_DMARC` to `true` or `false` when building the docker container for mail-receiver: + +```bash +docker build --build-arg INCLUDE_DMARC=true -t local_discourse/mail-receiver:latest . +``` +Configurations for these checks are stored in their respective configuration files `policyd-spf.conf`, `opendkim.conf`, `opendmarc.conf` in this repository. ## Blacklisting sender domains diff --git a/boot b/boot index 20ef8a7..d678b48 100755 --- a/boot +++ b/boot @@ -50,6 +50,16 @@ for envvar in $(compgen -v); do fi done +if [ "$INCLUDE_DMARC" = "true" ]; then + echo "Starting OpenDKIM..." >&2 + adduser postfix opendkim #ensure postfix is part of opendkim group so it can access the socket + /usr/sbin/opendkim -x /etc/opendkim.conf + + echo "Starting OpenDMARC..." >&2 + adduser postfix opendmarc #ensure postfix is part of opendmarc group so it can access the socket + /usr/sbin/opendmarc -c /etc/opendmarc.conf +fi + # Now, make sure that the Postfix filesystem environment is sane mkdir -p -m 0755 /var/spool/postfix/pid chown root:root /var/spool/postfix diff --git a/discourse_mail_receiver.gemspec b/discourse_mail_receiver.gemspec index 3cef40f..786992e 100644 --- a/discourse_mail_receiver.gemspec +++ b/discourse_mail_receiver.gemspec @@ -6,7 +6,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) Gem::Specification.new do |spec| spec.name = 'discourse_mail_receiver' - spec.version = '4.0.7' + spec.version = '4.1.0' spec.authors = ['Discourse Team'] spec.email = ['team@discourse.org'] spec.description = %q{A gem used to package the core .rb files of the mail-receiver.} diff --git a/opendkim.conf b/opendkim.conf new file mode 100644 index 0000000..b2728e3 --- /dev/null +++ b/opendkim.conf @@ -0,0 +1,64 @@ +# This is a basic configuration for signing and verifying. It can easily be +# adapted to suit a basic installation. See opendkim.conf(5) and +# /usr/share/doc/opendkim/examples/opendkim.conf.sample for complete +# documentation of available configuration parameters. + +Syslog yes +SyslogSuccess yes +#LogWhy no + +# Common signing and verification parameters. In Debian, the "From" header is +# oversigned, because it is often the identity key used by reputation systems +# and thus somewhat security sensitive. +Canonicalization relaxed/simple +#Mode sv +#SubDomains no +OversignHeaders From + +# Signing domain, selector, and key (required). For example, perform signing +# for domain "example.com" with selector "2020" (2020._domainkey.example.com), +# using the private key stored in /etc/dkimkeys/example.private. More granular +# setup options can be found in /usr/share/doc/opendkim/README.opendkim. +#Domain example.com +#Selector 2020 +#KeyFile /etc/dkimkeys/example.private + +# In Debian, opendkim runs as user "opendkim". A umask of 007 is required when +# using a local socket with MTAs that access the socket as a non-privileged +# user (for example, Postfix). You may need to add user "postfix" to group +# "opendkim" in that case. +UserID opendkim +UMask 007 + +# Socket for the MTA connection (required). If the MTA is inside a chroot jail, +# it must be ensured that the socket is accessible. In Debian, Postfix runs in +# a chroot in /var/spool/postfix, therefore a Unix socket would have to be +# configured as shown on the last line below. +Socket local:/run/opendkim/opendkim.sock +#Socket inet:8892@localhost +#Socket inet:8891 +#Socket local:/var/spool/postfix/opendkim/opendkim.sock + +PidFile /run/opendkim/opendkim.pid + +# Hosts for which to sign rather than verify, default is 127.0.0.1. See the +# OPERATION section of opendkim(8) for more information. +#InternalHosts 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12 + +# The trust anchor enables DNSSEC. In Debian, the trust anchor file is provided +# by the package dns-root-data. +TrustAnchorFile /usr/share/dns/root.key +#Nameservers 127.0.0.1 + +# this server only receives email, so not necessary to have signing mode +Mode v + +# ensures that even if no DKIM signature is present, we add the authentication result header +AlwaysAddARHeader true + +# Selects the action to be taken when any verification or internal error of any kind is encountered. This is processed before +# the other "On-" values so it can be used as a blanket setting followed by specific overrides. +# On-Default accept +# On-KeyNotFound reject +# On-BadSignature reject +# On-DNSError tempfail diff --git a/opendmarc.conf b/opendmarc.conf new file mode 100644 index 0000000..8bbb490 --- /dev/null +++ b/opendmarc.conf @@ -0,0 +1,114 @@ +# This is a basic configuration that can easily be adapted to suit a standard +# installation. For more advanced options, see openmarc.conf(5) and/or +# /usr/share/doc/opendmarc/examples/opendmarc.conf.sample. + +## AuthservID (string) +## defaults to MTA name +## +## Sets the "authserv-id" to use when generating the Authentication-Results: +## header field after verifying a message. If the string "HOSTNAME" is +## provided, the name of the host running the filter (as returned by the +## gethostname(3) function) will be used. +# +# AuthservID name + +## FailureReports { true | false } +## default "false" +## +## Enables generation of failure reports when the DMARC test fails and the +## purported sender of the message has requested such reports. Reports are +## formatted per RFC6591. +# +# FailureReports false + +## PidFile path +## default (none) +## +## Specifies the path to a file that should be created at process start +## containing the process ID. +# +PidFile /run/opendmarc/opendmarc.pid + +## PublicSuffixList path +## default (none) +## +## Specifies the path to a file that contains top-level domains (TLDs) that +## will be used to compute the Organizational Domain for a given domain name, +## as described in the DMARC specification. If not provided, the filter will +## not be able to determine the Organizational Domain and only the presented +## domain will be evaluated. +# +PublicSuffixList /usr/share/publicsuffix/public_suffix_list.dat + +## RejectFailures { true | false } +## default "false" +## +## If set, messages will be rejected if they fail the DMARC evaluation, or +## temp-failed if evaluation could not be completed. By default, no message +## will be rejected or temp-failed regardless of the outcome of the DMARC +## evaluation of the message. Instead, an Authentication-Results header +## field will be added. +# +# RejectFailures false + +## Socket socketspec +## default (none) +## +## Specifies the socket that should be established by the filter to receive +## connections from sendmail(8) in order to provide service. socketspec is +## in one of two forms: local:path, which creates a UNIX domain socket at +## the specified path, or inet:port[@host] or inet6:port[@host] which creates +## a TCP socket on the specified port for the appropriate protocol family. +## If the host is not given as either a hostname or an IP address, the +## socket will be listening on all interfaces. This option is mandatory +## either in the configuration file or on the command line. If an IP +## address is used, it must be enclosed in square brackets. +# +Socket local:/run/opendmarc/opendmarc.sock + +## Syslog { true | false } +## default "false" +## +## Log via calls to syslog(3) any interesting activity. +# +Syslog true + +## SyslogFacility facility-name +## default "mail" +## +## Log via calls to syslog(3) using the named facility. The facility names +## are the same as the ones allowed in syslog.conf(5). +# +# SyslogFacility mail + +## TrustedAuthservIDs string +## default HOSTNAME +## +## Specifies one or more "authserv-id" values to trust as relaying true +## upstream DKIM and SPF results. The default is to use the name of +## the MTA processing the message. To specify a list, separate each entry +## with a comma. The key word "HOSTNAME" will be replaced by the name of +## the host running the filter as reported by the gethostname(3) function. +# +# TrustedAuthservIDs HOSTNAME + +## UMask mask +## default (none) +## +## Requests a specific permissions mask to be used for file creation. This +## only really applies to creation of the socket when Socket specifies a +## UNIX domain socket, and to the HistoryFile and PidFile (if any); temporary +## files are normally created by the mkstemp(3) function that enforces a +## specific file mode on creation regardless of the process umask. See +## umask(2) for more information. +# +UMask 0002 + +## UserID user[:group] +## default (none) +## +## Attempts to become the specified userid before starting operations. +## The process will be assigned all of the groups and primary group ID of +## the named userid unless an alternate group is specified. +# +UserID opendmarc diff --git a/policyd-spf.conf b/policyd-spf.conf new file mode 100644 index 0000000..98c6523 --- /dev/null +++ b/policyd-spf.conf @@ -0,0 +1,15 @@ +# For a fully commented sample config file see policyd-spf.conf.commented + +debugLevel = 1 +TestOnly = 1 + +# Change these options to False if you want to pass SPF failures through to DMARC milter +HELO_reject = Fail +Mail_From_reject = Fail + + +PermError_reject = False +TempError_Defer = False + +skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1 +