1

I recently updated a django api from 2.2 to 3.1. I updated the dockerfile and related bash files like django-cookiecutter did https://github.com/pydanny/cookiecutter-django/commit/b22045bcd4ebf563ccdcf226fb389a6bb71e2654#diff-1872e6a6f0bbcb27f2eda185ac89eed05eb7a402b298e58bcbef29bf039c2c13

The upgrade mostly went well except now in production we cannot send email. I have a minimal management command I run in production to test email settings

from django.conf import settings
from django.core.mail import send_mail
from django.core.management.base import BaseCommand


class Command(BaseCommand):
    """
    Sends an email to the provided addresses.
    """

    help = "Sends an email to the provided addresses."

    def add_arguments(self, parser):
        parser.add_argument("emails", nargs="+")

    def handle(self, *args, **options):
        self.stdout.write(f"send_email from: {settings.EMAIL_FROM_FIELD}")
        for email in options["emails"]:
            try:
                send_mail(
                    "expert-system test email",
                    "a simple email",
                    settings.EMAIL_FROM_FIELD,
                    [email],
                )
            except BaseException as e:
                self.stdout.write(f"Problem sending email: {e}")

This returns

$ python manage.py send_email harry@test.com
send_email from: test@test.com
Problem sending email: EOF occurred in violation of protocol (_ssl.c:1125)

Another stackoverflow suggested testing if tls 1.1 is supported.

$ python -c "from urllib.request import urlopen ; 
print(urlopen('https://www.howsmyssl.com/a/check').read())"
b'{"given_cipher_suites":["TLS_AES_256_GCM_SHA384","TLS_CHACHA20_POLY1305_SHA256","TLS_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV"],"ephemeral_keys_supported":true,"session_ticket_supported":true,"tls_compression_supported":false,"unknown_cipher_suite_supported":false,"beast_vuln":false,"able_to_detect_n_minus_one_splitting":false,"insecure_cipher_suites":{},"tls_version":"TLS 1.3","rating":"Probably Okay"}'

How do I get email to send on production?

Harry Moreno
  • 10,231
  • 7
  • 64
  • 116
  • 1
    > Another stackoverflow suggested testing if tls 1.1 is supported. That's good advice, but check your mail server, not some random site. The lack of support is most likely at the mail server. In addition to upgrading Django did you also upgrade python? –  Apr 03 '21 at 04:26
  • Following the cookiecutter PR, we updated django from 2.2 to 3.1 and the base docker image from `python:3.7-alpine` to `python:3.8-slim-buster`. Regarding the mail server, we did not touch it. We have always had django's setting `EMAIL_USE_TLS = True` so I assume tls 1.1 is supported at the mail server. – Harry Moreno Apr 03 '21 at 14:52
  • So it's possible, your e-mail server doesn't support TLS 1.1, but only 1.0 and your change to slim-buster removed TLS 1.0 from the openssl library as it's being phased out ever so slowly. –  Apr 03 '21 at 23:23
  • @Melvyn I opened an issue with cookiecutter https://github.com/pydanny/cookiecutter-django/issues/3114 I verified swaks can send email from the host but not from this docker container – Harry Moreno Apr 03 '21 at 23:30
  • Or it can even be TLS 1.1 has been removed in the 3.8/slim-buster combination. I suggest to run the Django 3.1 installation on a 3.7-alpine and see if the mail server is coming back and then figure out what exactly has been dropped by what library. See [here](https://blog.barracuda.com/2021/03/31/tls1-0-and-tls1-1-officially-deprecated-by-ietf-rfc8996/) for some background. –  Apr 03 '21 at 23:32
  • ok I'll try django 3.1 on 3.7-alpine. Though this person I respect recommends `x-slim-buster` base images https://pythonspeed.com/articles/base-image-python-docker-images/ – Harry Moreno Apr 03 '21 at 23:34
  • That's questionable advice. Alpine needs a bit more care to work with, for example, I don't use Alpine whenever I require a package that needs to be compiled and isn't available as an Alpine package. I also don't use the python images, but the python that comes with alpine itself. So this has some limitations, but has nothing to do with speed. –  Apr 03 '21 at 23:43

2 Answers2

1

The fix was to use the originally working os python:3.7-alpine which seems to be able to send email however the smtp server requires it (still not sure what tls version it forces but I assume its tls 1.2).

Also wanted to add the reason we tried using python:3.8-slim-buster is because cookiecutter uses it, it's recommended by https://pythonspeed.com/articles/base-image-python-docker-images/ and the latest versions of the cryptography module (3.4+ https://cryptography.io/en/latest/changelog.html#v3-4) recommend using a modern version of pip (else you have to install rust to compile cryptography). Cryptography module is required by django-allauth (by way of pyjwt).

To use modern versions of django-allauth (which I think was required by django 3.1) I pinned the cryptography module to the latest pre-rust

django-allauth==0.44.0  # https://github.com/pennersr/django-allauth
cryptography==3.3.2 # https://github.com/pyca/cryptography
Harry Moreno
  • 10,231
  • 7
  • 64
  • 116
0

So in short, it's very likely your mail server only supports TLS 1.1 or even only TLS 1.0 and the slim-buster image no longer has support for those protocols.

Going back to 3.7-alpine (which is known working combination) or an older ubuntu/debian version which still supports those protocols will allow you to send mail again.

Then you should upgrade your mail server, cause both TLS 1.0 and TLS 1.1 should have died long ago.


Edit:

Another way to test your mailserver is to use openssl's s_client command:

openssl s_client -no_tls1 -no_tls1_1 -no_tls1_2 -connect your.mail.host:port

This will likely fail. Then remove a -no_tls flag till it starts working and you know the highest protocol it supports.

Note: -no_tls1_2 is only supported on openssl versions that support TLSv1.3

  • 3.7-alpine works with django 2.2 but not django 3.1 In 3.1 we get complaints about building the cryptography dependency. Perhaps 3.7-slim-buster would allow django 3.1 to build. – Harry Moreno Apr 04 '21 at 00:00
  • I think you have too many differences between your 2.2 and 3.1, because Django 3.1 doesn't depend on cryptography. But that could very well be, why the problem occurs, since cryptography pulls in the system's openssl library. –  Apr 04 '21 at 13:55
  • pip-compile says cryptography is required by my deps `django-allauth, pyjwt, argon2-cffi`. – Harry Moreno Apr 05 '21 at 16:01