Skip to content

HOWTO coldsetup 08_mta_container_link

steveoro edited this page May 2, 2021 · 3 revisions

HOW-TO: Cold Deploy Server step-by-step

Part 8: Connecting the app running in Docker to the host's MTA

References:

SSMTP is a program which delivers email from a local computer to a configured mailhost (mailhub). It is not a mail server. Make sure that ssmtp is installed inside the container.

Configuration: from the MTA host's side

From the host running the container, where the MTA (Postfix) is installed:

$> sudo vi /etc/postfix/main.cf

Assuming the gateway for the staging docker network is on 172.XXX.0.1:

#...
# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/master-goggles.org/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/master-goggles.org/privkey.pem
smtpd_tls_security_level=may

smtp_tls_CApath=/etc/ssl/certs
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination

myhostname = master-goggles.org
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydomain = master-goggles.org
mydestination = master-goggles.org

smtp_bind_address=0.0.0.0
smtp_bind_address6=::

relayhost =
relay_domains = master-goggles.org
parent_domain_matches_subdomains = debug_peer_list smtpd_access_maps

mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 172.0.0.0/8

mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
#...
$> sudo vi /etc/postfix/master.cf

Uncomment only the following to enable a PLAIN transport protocol service for incoming connections via smtp:

#...
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd
#...
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_reject_unlisted_recipient=no
#...
#smtps     inet  n       -       y       -       -       smtpd
#...

Restart Postfix and remember to set the port access so that only localhost and the containers running on the Docker bridge network can connect to the smtp service:

$> sudo systemctl restart postfix

$> sudo ufw allow from 172.0.0.0/8 to any port 25
$> sudo ufw allow from 127.0.0.0/16 to any port 25
$> sudo ufw reload

Make sure this set up didn't yield any misconfiguration by sending out another test e-mail.

Send a test email w/ s-nail:

$> echo "Test after Postfix manual reconfiguration" | s-nail -s 'Test from Goggles - ouside container' -T "to: steve.alloro@gmail.com" -r "no-reply@master-goggles.org"

Send a test email w/ sendmail:

If you have already written a mail text.txt file, you can use that as a body like this:

$> echo "Subject: Test w/ sendmail from prompt" | cat - text.txt | sendmail -F goggles@master-goggles.org -t steve.alloro@gmail.com

# ...Or just send the message without a body:
$> echo "Subject: test w/ sendmail from prompt" | sendmail -v steve.alloro@gmail.com

Configuration: from the Container's side

Connect to the running container:

$> docker exec -it goggles-main.staging sh

Inside the running container, make sure SSMTP is present and configured:

(assuming Alpine linux for the Docker image)

# If the package is missing:
$# apk add ssmtp

$# vi /etc/ssmtp.conf

Change defaults to:

#
# /etc/ssmtp.conf -- a config file for sSMTP sendmail.
#
# The person who gets all mail for userids < 1000
# Make this empty to disable rewriting.
root=postmaster

# The place where the mail goes. The actual machine name is required
# no MX records are consulted. Commonly mailhosts are named mail.domain.com
# The example will fit if you are in domain.com and you mailhub is so named.
mailhub=172.29.0.1 # (<= Just an example: THIS WILL CHANGE AFTER EACH RE-RUN)

# Where will the mail seem to come from?
rewriteDomain=master-goggles.org

# The full hostname
hostname="master-goggles.org"

This should be enough to send out plain text emails with no encryption.

Send a test email w/ sendmail:

From inside the container:

$# echo "Subject: test, sendmail from Staging" | sendmail -v steve.alloro@gmail.com

Or a bit more high-level, from the Rails application console:

$# bundle exec rails c

> ApplicationMailer.generic_message( \
    user_email:'steve.alloro@gmail.com', \
    user_name: 'Steve A.', \
    subject_text: "Testing purposes", \
    content_body: "I'm writing from inside the app!" \
  ).deliver_now

=> # goes through, with no SSL

One step further: about S-MIME/SSL encryption + TSL setup for Postfix with Dovecot

Bear in mind:

Regarding Postfix options:

  • smtp |➡ outgoing connections from the mail server
  • smtpd |⬅ incoming connections to the mail server

S-MIME may not work the way you probably think it does: it doesn't allow you to send encrypted emails to unless the recipient has a copy of you public key. So, it's typically a fallback for security message exchange between servers that do not support SSL but have a copy of your public key.

See How To Encrypt Mails With SSL Certificates (S/MIME) for more information.

Encryption level can be check easily by sending a test email on a GMail account:

  • (highest) S-MIME corresponds to a "green lock" icon on GMail
  • (normal) TSL will yield a "grey lock" on GMail
  • (insecure) plain or no encryption will yield a "red lock" on GMail

To enable TSL, you'll need to "publish" the root certificate for the server (smtp_tls_CAfile, see below for actual configuration values for Ubuntu), set a minimum logging level (smtp_tls_loglevel) and enable the handshaking by setting the proper security level (smtp_tls_security_level).

References:

What Does it Mean if Gmail Reports my Email isn't Encrypted (Red Pad Lock)?

If you received the gmail 'red pad lock' on a sent message it's because the handshaking protocol was not encrypted using valid security certificates (and using a TLS handshaking protocol).

To fix this error, the server sending the email must deliver the message using a valid SSL security certificate with TLS handshaking enabled.

How to Fix: Send Encrypted Email to Gmail (Postfix, TLS, SSL Certificates)

References:

In order to enable TLS handshaking on outgoing emails (from the mail server to GMail, Yahoo, etc) the only settings that have to be edited are:

$> sudo vi /etc/postfix/main.cf
smtp_tls_security_level = may
smtp_tls_loglevel = 1
smtp_tls_CAfile = /path/to/system_CA_root_file
# i.e., for Ubuntu/Debian: /etc/ssl/certs/ca-certificates.crt
  • smtp_tls_security_level = may: at the "may" TLS security level, TLS encryption is opportunistic. The SMTP transaction is encrypted if the STARTTLS ESMTP feature is supported by the server. Otherwise, messages are sent in the clear.

  • smtp_tls_CAfile: points to the system file containing the CA root certificates trusted to sign either remote SMTP server certificates or intermediate CA certificates.

Assuming SSL certificates are already set for the web server, the other TLS certificates can be found under /etc/letsencrypt/live/<your.domain>/.

You can add your new certificates to the Postfix configuration using:

$> sudo postconf -e 'smtpd_tls_cert_file = /etc/letsencrypt/live/<your.domain>/fullchain.pem'
$> sudo postconf -e 'smtpd_tls_key_file = /etc/letsencrypt/live/<your.domain>/privkey.pem'

Setting up SMTP authentication with Dovecot

(Overkill for a send-only server)

References:

SMTP-AUTH allows a client to identify itself through the authentication mechanism SASL for connecting to the smtpd daemon.

(Note that this is for incoming connections and may not be needed for a send-only server that blocks traffic from outsite its network.)

Transport Layer Security (TLS) should be used to encrypt the authentication process. Once authenticated, the server will allow the client to relay mail.

Installing Dovecot permits IMAP & POP3 support and adds the actual SASL security layer for client authentication.

$> sudo postconf -e 'smtpd_sasl_type = dovecot'
$> sudo postconf -e 'smtpd_sasl_path = private/auth'
$> sudo postconf -e 'smtpd_sasl_local_domain ='
$> sudo postconf -e 'smtpd_sasl_security_options = noanonymous'
$> sudo postconf -e 'broken_sasl_auth_clients = yes'
$> sudo postconf -e 'smtpd_sasl_auth_enable = yes'
$> sudo postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination'

Configure Postfix to provide TLS encryption for both incoming and outgoing mail:

$> sudo postconf -e 'smtp_tls_security_level = may'
$> sudo postconf -e 'smtp_tls_note_starttls_offer = yes'
$> sudo postconf -e 'smtpd_tls_security_level = may'
$> sudo postconf -e 'smtpd_tls_loglevel = 1'
$> sudo postconf -e 'smtpd_tls_received_header = yes'

Enabling the SASL lets users send messages outside the local domain without compromising the security of the relay.

See Configuring Dovecot for more information on Dovecot installation & configuration.

After setting up TLS, edit the Rails ActionMailer configuration accordingly to connect to the SSMTP transport layer inside the container using the new credentials set.


Recap: making the setup between Container <=> MTA as dynamic as possible

The container can send out e-mails easily without having to rely on a containerized MTA.

Considering that often the host has to be able to send out monitoring alerts or summaries via e-mail, relying on the host's MTA is definitely more convenient and possible.

Provided that, we'll assume:

  1. Postfix is the MTA on the host, and is configured for listening to the whole range of Docker bridge networks (typically, 172.0.0.0/8)

  2. Postfix is configured also to enable the submission/transport service for incoming connections from the enabled port

  3. SSMTP is installed and properly configure on the running container

Recap 2: Postfix submission service & configuration

The submission service must be set editing Postfix's master.cf:

$> sudo vi /etc/postfix/master.cf
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_reject_unlisted_recipient=no

Recap 3: change sender identity (root)

The best way to do this, is adding a bespoke user description for the app user that sends out the messages.

This can be easily achived with something like chfn. In Ubuntu, for example:

$> sudo chfn -f 'Deploy Sysop' deploy
$> sudo chfn -f 'Goggles Mailer' root

If chfn isn't directly available, as the case with Alpine Linux, simply add the shadow package to the container Dockerfile and use usermod instead.

Connect to the running container and type (or add these as steps for the image construction):

$# apk add shadow
$# usermod -c 'Goggles Mailer' root

Recap 4: making SSMTP configuration dynamic

So far, so good, the only issue being Docker will create the bridge network dynamically: the IPs are inevitably bound to change after every service restart.

To make the configuration dynamic also from the container side, we'll need to get the Gateway IP of the container using something like the following commands:

(From inside the running container)

$# MYROUTE=`ip route | grep default`
# Typical route format: "default via IP_NUM dev DEVICE_NAME proto dhcp metric XYZ"

# Extract the IP part to get the dynamic Gateway address (respect the spacing):
$# GATEWAY_IP=${MYROUTE##* via }
$# GATEWAY_IP=${GATEWAY_IP% dev*}

# $GATEWAY_IP will hold the actual Gateway IP

Edit directly the ssmtp.conf file with something like this:

(remember to use double quotes to allow parsing of ENV variables)

$# sed -i "s/mailhub=.\+/mailhub=$GATEWAY_IP/" /etc/ssmtp/ssmtp.conf
$# sed -i "s/#rewriteDomain=.\+/rewriteDomain=master-goggles.org/" /etc/ssmtp/ssmtp.conf
$# sed -i "s/#hostname=.\+/hostname=master-goggles.org/" /etc/ssmtp/ssmtp.conf

# Check the results:
$# cat /etc/ssmtp/ssmtp.conf

Override also the reverse-aliases of SSMTP with a list of names for any outgoing message type. For example, with something like this:

$# echo "# sSMTP aliases" > /etc/ssmtp/revaliases
$# echo "root:no-reply@master-goggles.org" >> /etc/ssmtp/revaliases

All of the above must be run after the bridge network is up and every time the service must be started. The entry-point script of the running container is the perfect candidate.

Take a look to the entrypoint files of the project to see the actual implementation.


Finally testing the completed mail server setup

If everything went well, by sending an outgoing e-mail to a service like mail-tester.com, you'll get a detailed report.

The perfect score achieved by the staging server when setup:

Successful configuration tested with MailTester


Clone this wiki locally