From 33a278c7a56af1d4397119d7bccac8e3fad7259b Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Sun, 10 Apr 2022 21:58:50 +0000 Subject: [PATCH 01/12] Adding resty auto ssl parser. --- Dockerfile | 3 ++- assets/install.sh | 12 +++++++++--- assets/parse_resty_auto_ssl.py | 24 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 assets/parse_resty_auto_ssl.py diff --git a/Dockerfile b/Dockerfile index 5395bf5..d1389b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,9 +9,10 @@ RUN apt-get update # Start editing # Install package here for cache -RUN apt-get -y install supervisor postfix sasl2-bin opendkim opendkim-tools +RUN apt-get -y install python3.4-dev python3.4 supervisor postfix sasl2-bin opendkim opendkim-tools # Add files +ADD assets/parse_resty_auto_ssl.py /opt/parse_resty_auto_ssl.py ADD assets/install.sh /opt/install.sh # Run diff --git a/assets/install.sh b/assets/install.sh index 0b2aaff..091b240 100755 --- a/assets/install.sh +++ b/assets/install.sh @@ -52,8 +52,9 @@ done < /tmp/passwd chown postfix.sasl /etc/sasldb2 ############ -# Enable TLS +# Enable TLS and SSL ############ +python3 /opt/parse_resty_auto_ssl.py if [[ -n "$(find /etc/postfix/certs -iname *.crt)" && -n "$(find /etc/postfix/certs -iname *.key)" ]]; then # /etc/postfix/main.cf postconf -e smtpd_tls_cert_file=$(find /etc/postfix/certs -iname *.crt) @@ -62,10 +63,15 @@ if [[ -n "$(find /etc/postfix/certs -iname *.crt)" && -n "$(find /etc/postfix/ce # /etc/postfix/master.cf postconf -M submission/inet="submission inet n - n - - smtpd" postconf -P "submission/inet/syslog_name=postfix/submission" - postconf -P "submission/inet/smtpd_tls_security_level=encrypt" + postconf -P "submission/inet/smtpd_tls_security_level=may" postconf -P "submission/inet/smtpd_sasl_auth_enable=yes" postconf -P "submission/inet/milter_macro_daemon_name=ORIGINATING" postconf -P "submission/inet/smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination" + + postconf -M smtps/inet="smtps inet n - n - - smtpd" + postconf -P "smtps/inet/syslog_name=postfix/smtps" + postconf -P "smtps/inet/smtpd_tls_wrappermode=yes" + postconf -P "smtps/inet/smtpd_sasl_auth_enable=yes" fi ############# @@ -127,4 +133,4 @@ cat >> /etc/opendkim/SigningTable < Date: Mon, 18 Apr 2022 22:53:00 +0000 Subject: [PATCH 02/12] Forcing ecnryption. --- .gitignore | 3 ++- assets/install.sh | 21 +++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 3718fd4..556fb47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.env.prod debug.sh sftp-config.json -assets/domainkeys/mail.* \ No newline at end of file +assets/domainkeys/mail.* diff --git a/assets/install.sh b/assets/install.sh index 091b240..5bb6c62 100755 --- a/assets/install.sh +++ b/assets/install.sh @@ -59,19 +59,32 @@ if [[ -n "$(find /etc/postfix/certs -iname *.crt)" && -n "$(find /etc/postfix/ce # /etc/postfix/main.cf postconf -e smtpd_tls_cert_file=$(find /etc/postfix/certs -iname *.crt) postconf -e smtpd_tls_key_file=$(find /etc/postfix/certs -iname *.key) + postconf -e smtpd_tls_protocols=TLSv1.2,TLSv1.1,!TLSv1,!SSLv2,!SSLv3 + postconf -e smtp_tls_protocols=TLSv1.2,TLSv1.1,!TLSv1,!SSLv2,!SSLv3 + postconf -e smtpd_tls_ciphers=high + postconf -e smtp_tls_ciphers=high + postconf -e smtpd_tls_mandatory_protocols=TLSv1.2,TLSv1.1,!TLSv1,!SSLv2,!SSLv3 + postconf -e smtp_tls_mandatory_protocols=TLSv1.2,TLSv1.1,!TLSv1,!SSLv2,!SSLv3 + postconf -e smtpd_tls_mandatory_ciphers=high + postconf -e smtp_tls_mandatory_ciphers=high + postconf -e smtpd_tls_mandatory_exclude_ciphers=MD5,DES,ADH,RC4,PSD,SRP,3DES,eNULL,aNULL + postconf -e smtp_tls_mandatory_exclude_ciphers=MD5,DES,ADH,RC4,PSD,SRP,3DES,eNULL,aNULL + postconf -e smtpd_tls_exclude_ciphers=MD5,DES,ADH,RC4,PSD,SRP,3DES,eNULL,aNULL + postconf -e smtp_tls_exclude_ciphers=MD5,DES,ADH,RC4,PSD,SRP,3DES,eNULL,aNULL + postconf -e smtpd_tls_security_level=encrypt + postconf -e smtp_tls_security_level=encrypt + postconf -e smtpd_tls_loglevel=2 + postconf -e smtp_tls_loglevel=2 + postconf -e tls_preempt_cipherlist=yes chmod 400 /etc/postfix/certs/*.* # /etc/postfix/master.cf postconf -M submission/inet="submission inet n - n - - smtpd" postconf -P "submission/inet/syslog_name=postfix/submission" - postconf -P "submission/inet/smtpd_tls_security_level=may" - postconf -P "submission/inet/smtpd_sasl_auth_enable=yes" postconf -P "submission/inet/milter_macro_daemon_name=ORIGINATING" postconf -P "submission/inet/smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination" postconf -M smtps/inet="smtps inet n - n - - smtpd" postconf -P "smtps/inet/syslog_name=postfix/smtps" - postconf -P "smtps/inet/smtpd_tls_wrappermode=yes" - postconf -P "smtps/inet/smtpd_sasl_auth_enable=yes" fi ############# From 9a94ebfe7fce6a5854f2e08894a3ca9e1e3f2400 Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Tue, 1 Oct 2024 00:27:03 +0200 Subject: [PATCH 03/12] Refactor: Changing docker image to use alpine. Adding multi domain support. --- .gitignore | 6 +- Dockerfile | 19 ---- LICENSE | 4 +- README.md | 45 +-------- assets/certs/readme | 1 - assets/domainkeys/readme | 1 - assets/parse_resty_auto_ssl.py | 24 ----- build.sh | 3 - data/.users.example | 2 + docker-compose.yml | 20 ++++ docker/.env.example | 2 + docker/parse_resty_auto_ssl.py | 36 +++++++ assets/install.sh => docker/setup.sh | 138 +++++++++++++-------------- hub/alpine/Dockerfile | 23 +++++ 14 files changed, 159 insertions(+), 165 deletions(-) delete mode 100644 Dockerfile delete mode 100644 assets/certs/readme delete mode 100644 assets/domainkeys/readme delete mode 100644 assets/parse_resty_auto_ssl.py delete mode 100755 build.sh create mode 100644 data/.users.example create mode 100644 docker-compose.yml create mode 100644 docker/.env.example create mode 100755 docker/parse_resty_auto_ssl.py rename assets/install.sh => docker/setup.sh (56%) create mode 100644 hub/alpine/Dockerfile diff --git a/.gitignore b/.gitignore index 556fb47..5aec8f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ +.DS_Store .env.prod -debug.sh -sftp-config.json -assets/domainkeys/mail.* +data/opendkim/* +data/restry-auto-ssl diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index d1389b6..0000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -From ubuntu:trusty -MAINTAINER Elliott Ye - -# Set noninteractive mode for apt-get -ENV DEBIAN_FRONTEND noninteractive - -# Update -RUN apt-get update - -# Start editing -# Install package here for cache -RUN apt-get -y install python3.4-dev python3.4 supervisor postfix sasl2-bin opendkim opendkim-tools - -# Add files -ADD assets/parse_resty_auto_ssl.py /opt/parse_resty_auto_ssl.py -ADD assets/install.sh /opt/install.sh - -# Run -CMD /opt/install.sh;/usr/bin/supervisord -c /etc/supervisor/supervisord.conf diff --git a/LICENSE b/LICENSE index bdedff0..8244556 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Minghou Ye +Copyright (c) 2014 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 955a64c..1f926b2 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,12 @@ docker-postfix ============== -run postfix with smtp authentication (sasldb) in a docker container. -TLS and OpenDKIM support are optional. - -## Requirement -+ Docker 1.0 +Postfix with smtp authentication (sasldb) in a docker container. +TLS and OpenDKIM also supported. ## Installation 1. Build image ```bash - $ sudo docker pull catatnight/postfix + $ docker compose build ``` - -## Usage -1. Create postfix container with smtp authentication - - ```bash - $ sudo docker run -p 25:25 \ - -e maildomain=mail.example.com -e smtp_user=user:pwd \ - --name postfix -d catatnight/postfix - # Set multiple user credentials: -e smtp_user=user1:pwd1,user2:pwd2,...,userN:pwdN - ``` -2. Enable OpenDKIM: save your domain key ```.private``` in ```/path/to/domainkeys``` - - ```bash - $ sudo docker run -p 25:25 \ - -e maildomain=mail.example.com -e smtp_user=user:pwd \ - -v /path/to/domainkeys:/etc/opendkim/domainkeys \ - --name postfix -d catatnight/postfix - ``` -3. Enable TLS(587): save your SSL certificates ```.key``` and ```.crt``` to ```/path/to/certs``` - - ```bash - $ sudo docker run -p 587:587 \ - -e maildomain=mail.example.com -e smtp_user=user:pwd \ - -v /path/to/certs:/etc/postfix/certs \ - --name postfix -d catatnight/postfix - ``` - -## Note -+ Login credential should be set to (`username@mail.example.com`, `password`) in Smtp Client -+ You can assign the port of MTA on the host machine to one other than 25 ([postfix how-to](http://www.postfix.org/MULTI_INSTANCE_README.html)) -+ Read the reference below to find out how to generate domain keys and add public key to the domain's DNS records - -## Reference -+ [Postfix SASL Howto](http://www.postfix.org/SASL_README.html) -+ [How To Install and Configure DKIM with Postfix on Debian Wheezy](https://www.digitalocean.com/community/articles/how-to-install-and-configure-dkim-with-postfix-on-debian-wheezy) -+ TBD diff --git a/assets/certs/readme b/assets/certs/readme deleted file mode 100644 index 2edd6fe..0000000 --- a/assets/certs/readme +++ /dev/null @@ -1 +0,0 @@ -put your SSL certificates key(.key) and cert(.crt) here \ No newline at end of file diff --git a/assets/domainkeys/readme b/assets/domainkeys/readme deleted file mode 100644 index 625ae5a..0000000 --- a/assets/domainkeys/readme +++ /dev/null @@ -1 +0,0 @@ -put your domain key file (.private) here \ No newline at end of file diff --git a/assets/parse_resty_auto_ssl.py b/assets/parse_resty_auto_ssl.py deleted file mode 100644 index cd05da9..0000000 --- a/assets/parse_resty_auto_ssl.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -import json -import os - -MAIL_DOMAIN = os.environ.get('maildomain') -PATH = '/etc/resty-auto-ssl/storage/file/' -FILE_PATH = os.path.join(PATH, '{}%3Alatest'.format(MAIL_DOMAIN)) -POSTFIX_CRT_PATH = '/etc/postfix/certs/' -FILE_KEY_PATH = os.path.join(POSTFIX_CRT_PATH, '{}.key'.format(MAIL_DOMAIN)) -FILE_CRT_PATH = os.path.join(POSTFIX_CRT_PATH, '{}.crt'.format(MAIL_DOMAIN)) - -os.makedirs(POSTFIX_CRT_PATH, exist_ok=True) -with open(FILE_PATH) as ssl_file: - data = json.load(ssl_file) - key_content = data.get('privkey_pem') - cert_content = data.get('fullchain_pem') - - with open(FILE_KEY_PATH, 'w+') as key_file: - key_file.write(key_content) - - with open(FILE_CRT_PATH, 'w+') as crt_file: - crt_file.write(cert_content) - diff --git a/build.sh b/build.sh deleted file mode 100755 index d7b5420..0000000 --- a/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -docker build -t catatnight/postfix . \ No newline at end of file diff --git a/data/.users.example b/data/.users.example new file mode 100644 index 0000000..69dcc4c --- /dev/null +++ b/data/.users.example @@ -0,0 +1,2 @@ +example01.com:info@example01.com:somepassword:info.example01@gmail.com +example02.com:info@example02.com:somepassword:info.example02@gmail.com diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2771b5f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +services: + smtp: + build: + context: . + dockerfile: ./hub/alpine/Dockerfile + command: 'tail -f /dev/null' + container_name: postfix + env_file: + - ./docker/.env.example + ports: + - "25:25" + - "465:465" + - '587:587' + volumes: + - ./docker/parse_resty_auto_ssl.py:/root/parse_resty_auto_ssl.py + - ./docker/setup.sh:/root/setup.sh + - ./data/.users.example:/tmp/passwd + - ./data/opendkim/domainkey:/etc/opendkim/domainkey + - ./data/restry-auto-ssl/example01.com:/etc/resty-auto-ssl/storage/file/example01.com%3Alatest + - ./data/restry-auto-ssl/example02.com:/etc/resty-auto-ssl/storage/file/example02.com%3Alatest diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..326c207 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,2 @@ +maindomain=example01.com +maildomains=example01.com,example02.com diff --git a/docker/parse_resty_auto_ssl.py b/docker/parse_resty_auto_ssl.py new file mode 100755 index 0000000..8d3d93d --- /dev/null +++ b/docker/parse_resty_auto_ssl.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +import json +import os + +# constants +DOMAINS = os.environ.get('maildomains').split(',') +DOMAIN_MAP = os.path.join(os.sep, 'etc', 'postfix', 'ssl_map') +PATH = os.path.join(os.sep, 'etc', 'resty-auto-ssl', 'storage', 'file') +POSTFIX_CRT_PATH = os.path.join(os.sep, 'etc', 'postfix', 'certs') + +# open a file to write the mapping +with open(DOMAIN_MAP, 'w+') as mapping: + + # loop per domain + for domain in DOMAINS: + + file_path = os.path.join(PATH, f'{domain}%3Alatest') + file_key_path = os.path.join(POSTFIX_CRT_PATH, f'{domain}.key') + file_crt_path = os.path.join(POSTFIX_CRT_PATH, f'{domain}.crt') + + # write the domain, key and crt + mapping.write(f'{domain} {file_key_path} {file_crt_path}\n') + + os.makedirs(POSTFIX_CRT_PATH, exist_ok=True) + with open(file_path) as ssl_file: + data = json.load(ssl_file) + key_content = data.get('privkey_pem') + cert_content = data.get('fullchain_pem') + + with open(file_key_path, 'w+') as key_file: + key_file.write(key_content) + + with open(file_crt_path, 'w+') as crt_file: + crt_file.write(cert_content) + diff --git a/assets/install.sh b/docker/setup.sh similarity index 56% rename from assets/install.sh rename to docker/setup.sh index 5bb6c62..53d436e 100755 --- a/assets/install.sh +++ b/docker/setup.sh @@ -1,64 +1,51 @@ -#!/bin/bash - -#judgement -if [[ -a /etc/supervisor/conf.d/supervisord.conf ]]; then - exit 0 -fi - -#supervisor -cat > /etc/supervisor/conf.d/supervisord.conf <> /opt/postfix.sh <> /etc/postfix/sasl/smtpd.conf < /tmp/passwd -while IFS=':' read -r _user _pwd; do - echo $_pwd | saslpasswd2 -p -c -u $maildomain $_user -done < /tmp/passwd -chown postfix.sasl /etc/sasldb2 +# /etc/postfix/main.cf +postconf -e smtpd_sasl_auth_enable=yes +postconf -e broken_sasl_auth_clients=yes +postconf -e smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination -############ -# Enable TLS and SSL -############ -python3 /opt/parse_resty_auto_ssl.py -if [[ -n "$(find /etc/postfix/certs -iname *.crt)" && -n "$(find /etc/postfix/certs -iname *.key)" ]]; then +###################### +# Enable TLS and SSL # +###################### +./parse_resty_auto_ssl.py +postmap -F lmdb:/etc/postfix/ssl_map +if [[ -n "/etc/postfix/certs/$maindomain.key" && -n "/etc/postfix/certs/$maindomain.crt" ]]; then # /etc/postfix/main.cf - postconf -e smtpd_tls_cert_file=$(find /etc/postfix/certs -iname *.crt) - postconf -e smtpd_tls_key_file=$(find /etc/postfix/certs -iname *.key) + postconf -e smtpd_tls_key_file=/etc/postfix/certs/$maindomain.key + postconf -e smtpd_tls_cert_file=/etc/postfix/certs/$maindomain.crt postconf -e smtpd_tls_protocols=TLSv1.2,TLSv1.1,!TLSv1,!SSLv2,!SSLv3 postconf -e smtp_tls_protocols=TLSv1.2,TLSv1.1,!TLSv1,!SSLv2,!SSLv3 postconf -e smtpd_tls_ciphers=high @@ -76,6 +63,7 @@ if [[ -n "$(find /etc/postfix/certs -iname *.crt)" && -n "$(find /etc/postfix/ce postconf -e smtpd_tls_loglevel=2 postconf -e smtp_tls_loglevel=2 postconf -e tls_preempt_cipherlist=yes + postconf -e tls_server_sni_maps=lmdb:/etc/postfix/ssl_map chmod 400 /etc/postfix/certs/*.* # /etc/postfix/master.cf postconf -M submission/inet="submission inet n - n - - smtpd" @@ -87,25 +75,34 @@ if [[ -n "$(find /etc/postfix/certs -iname *.crt)" && -n "$(find /etc/postfix/ce postconf -P "smtps/inet/syslog_name=postfix/smtps" fi -############# -# opendkim -############# +############################### +# Setting up sender canonical # +############################### +while IFS=':' read -r _domain _user _pwd _cannonical; do + echo "/$_cannonical/ $_user" >> /etc/postfix/sender_canonical + echo "/From:$_cannonical/ REPLACE From: $_user" >> /etc/postfix/header_checks +done < /tmp/passwd -if [[ -z "$(find /etc/opendkim/domainkeys -iname *.private)" ]]; then - exit 0 -fi -cat >> /etc/supervisor/conf.d/supervisord.conf <> /etc/opendkim.conf <> /etc/opendkim/opendkim.conf <> /etc/default/opendkim <> /etc/opendkim/TrustedHosts <> /etc/opendkim/KeyTable <> /etc/opendkim/SigningTable <> /etc/opendkim/TrustedHosts + echo "mail._domainkey.$domain $domain:mail:/etc/opendkim/domainkey/$domain.private" >> /etc/opendkim/KeyTable + echo "*@$domain mail._domainkey.$domain" >> /etc/opendkim/SigningTable + if [ ! -f /etc/opendkim/domainkey/$domain.private ]; then + opendkim-genkey -b 1024 -d $domain -D /etc/opendkim/domainkey -s $domain -v + chown opendkim:opendkim /etc/opendkim/domainkey/$domain.private + chmod 400 /etc/opendkim/domainkey/$domain.private + fi +done +IFS=$OLDIFS diff --git a/hub/alpine/Dockerfile b/hub/alpine/Dockerfile new file mode 100644 index 0000000..28e0431 --- /dev/null +++ b/hub/alpine/Dockerfile @@ -0,0 +1,23 @@ +From alpine:latest + +# Defining packages to install +ENV ESSENTIAL_PACKAGES="python3 postfix opendkim opendkim-utils" \ + UTILITY_PACKAGES="mlocate vim" \ + BUILDER_PACKAGES="g++ gdbm-dev make openssl-dev" + +# Installing packages +RUN apk update && \ + apk --no-cache --progress add $ESSENTIAL_PACKAGES $UTILITY_PACKAGES $BUILDER_PACKAGES + +# building cyrus-sasl because alpine package give error +RUN wget https://github.com/cyrusimap/cyrus-sasl/releases/download/cyrus-sasl-2.1.28/cyrus-sasl-2.1.28.tar.gz && \ + tar -xvzf cyrus-sasl-2.1.28.tar.gz && \ + cd cyrus-sasl-2.1.28 && \ + ./configure --enable-plain --enable-login --enable-cram --enable-digest --enable-ntlm && \ + make && \ + make install + +# Removing non needed builder packages +RUN apk del $BUILDER_PACKAGES + +WORKDIR /root From 9ec503799425af9da96088648ce418c1c2f73e14 Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Wed, 2 Oct 2024 02:02:06 +0200 Subject: [PATCH 04/12] Fixing: Setup. --- docker/parse_resty_auto_ssl.py | 2 +- docker/setup.sh | 16 +++++++++++++--- hub/alpine/Dockerfile | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docker/parse_resty_auto_ssl.py b/docker/parse_resty_auto_ssl.py index 8d3d93d..9837bf5 100755 --- a/docker/parse_resty_auto_ssl.py +++ b/docker/parse_resty_auto_ssl.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import json import os diff --git a/docker/setup.sh b/docker/setup.sh index 53d436e..8465fa5 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -19,6 +19,7 @@ chown postfix:postfix /etc/sasldb2 ######################## # Setting up Postfix # ######################## +postmap -F /etc/postfix/aliases postconf -e myhostname=$maindomain postconf -F '*/*/chroot = n' @@ -87,11 +88,20 @@ postconf -e sender_canonical_classes=envelope_sender,header_sender postconf -e sender_canonical_maps=regexp:/etc/postfix/sender_canonical postconf -e smtp_header_checks=regexp:/etc/postfix/header_checks +########################## +# Setting up fordwarding # +########################## +ALL_DOMAINS="${maildomains//,/ }" +postconf -e virtual_alias_domains=$ALL_DOMAINS +postconf -e virtual_alias_maps=lmdb:/etc/postfix/virtual +while IFS=':' read -r _domain _user _pwd _cannonical; do + echo $_user $_cannonical >> /etc/postfix/virtual +done < /tmp/passwd + ##################### # Setting up loggin # ##################### postconf -e maillog_file=/var/log/postfix.log -postconf -e maillog_file_permissions=0644 ############## # opendkim # @@ -102,7 +112,7 @@ postconf -e milter_default_action=accept postconf -e smtpd_milters=inet:localhost:12301 postconf -e non_smtpd_milters=inet:localhost:12301 -cat >> /etc/opendkim/opendkim.conf < /etc/opendkim/opendkim.conf <> /etc/opendkim/SigningTable if [ ! -f /etc/opendkim/domainkey/$domain.private ]; then opendkim-genkey -b 1024 -d $domain -D /etc/opendkim/domainkey -s $domain -v - chown opendkim:opendkim /etc/opendkim/domainkey/$domain.private chmod 400 /etc/opendkim/domainkey/$domain.private fi + chown opendkim:opendkim -R /etc/opendkim/domainkey done IFS=$OLDIFS diff --git a/hub/alpine/Dockerfile b/hub/alpine/Dockerfile index 28e0431..e91b558 100644 --- a/hub/alpine/Dockerfile +++ b/hub/alpine/Dockerfile @@ -2,7 +2,7 @@ From alpine:latest # Defining packages to install ENV ESSENTIAL_PACKAGES="python3 postfix opendkim opendkim-utils" \ - UTILITY_PACKAGES="mlocate vim" \ + UTILITY_PACKAGES="busybox mlocate vim" \ BUILDER_PACKAGES="g++ gdbm-dev make openssl-dev" # Installing packages From 4748df0efa91e785ba315d6ce0221be15810c2eb Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Wed, 2 Oct 2024 21:10:30 +0200 Subject: [PATCH 05/12] Improve: Documentation and other small things. --- README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++-- docker-compose.yml | 11 ++++-- docker/setup.sh | 2 +- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1f926b2..c97736b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ docker-postfix ============== -Postfix with smtp authentication (sasldb) in a docker container. -TLS and OpenDKIM also supported. +Postfix with SMTP authentication (SASLdb) in a Docker container. +TLS and OpenDKIM are also supported. +With a simple environment and configuration file, you can set up multiple domains and email addresses. +The settings allow you to forward incoming emails to other addresses, such as Gmail. +This option can be useful if you want to use Google Gmail with your own email domain. ## Installation 1. Build image @@ -10,3 +13,81 @@ TLS and OpenDKIM also supported. ```bash $ docker compose build ``` + +## Configure the service: +There are two important files: +1. One for the environment, which should contain two variables: +``` +maindomain=example01.com +maildomains=example01.com,example02.com +``` +The `maindomain`, as its name indicates, should be the main domain of your server and will be used to set the Postfix variable `myhostname`. +The second variable, `maildomains`, is the list of domains that you want Postfix to support. This value will be used to set the Postfix `virtual_alias_domains`. +It will also be used by OpenDKIM to generate the required keys for each domain. +Please check the example file `docker/.env.example`. + +2. The second file is the list of users/emails that need to be created. We use a Docker volume to sync this file into the container path /tmp/passwd. +The file follows this format: +``` +_domain:_user:_password:_cannonical +example01.com:info@example01.com:somepassword:info.example01@gmail.com +``` +The content of this file is used to set up the SASL DB (`/etc/sasldb2`). +Additionally, it sets the Postfix `sender_canonical` and `header_checks` variables, which replace outgoing emails `_canonical` `From` values with the email address for our domain `_user`. +Please check the example file `data/.users.example`. + + +## Start the service +All the service configurations will be performed by simply running the `setup.sh` script inside the container. This script will read the environment variables and the user list file to generate the required Postfix and OpenDKIM configuration files. +The steps to follow are: +```BASH +# login to the container +docker exec -it postfix sh + +# when you get the container prompt then you run the setup script: +./setup.sh + +# After the setup we launch the service in this order +syslogd # optional to capture opemdkim logs +opemdkim # start the opmkdim service +postfix start # to start postfix +``` + +You can check if the services started properly by tailing the logs: +```BASH +tail -f /var/log/messages # sys log where you can find details about openkdim service +tail -f /var/log/postfix +``` + +## Setting up your DNS records: +You can find plenty documentation about this on internet but basically to have a valid smtp server running you need 3 things: +### SPF: It specifies a list of IP addresses where email messages are allowed to sent on behalf of that domain. +For instance, if you want to be able to use your SMTP server to send email from a google gmail account, then you can do something like this: +``` +Type: txt +Name: @ +Data/Value: v=spf1 mx a include:_spf.google.com -all +``` +You can use this generator: https://dmarcly.com/tools/spf-record-generator + +### DKIM: DKIM detects forged header fields and content in emails. DKIM enables the receiver to check if email headers and content have been altered in transit. +The content of this records will be automatically generated by the `setup.sh` script and it will place it in the docker volume `data/opendkim/domainkey` with the name convention `{domain}.txt`. +``` +Type: txt +Name: mail._domainkey +Data/Value: v=DKIM1; k=rsa; p={your_pub_key_generated_by_opendkim} +``` +You can use this generator: https://dmarcly.com/tools/dkim-record-generator + +### DMARC: Domain-based Message Authentication Reporting & Conformance (DMARC) is an email security protocol. +You can learn more about this here (https://www.fortinet.com/resources/cyberglossary/dmarc) +Example: +``` +Type: txt +Name: _dmarc +Data/Value: v=DMARC1; p=reject; rua=mailto:your_email@here.com; adkim=s; aspf=s; +``` +You can use this generator: https://dmarcly.com/tools/dmarc-generator + +### My favorite validator: +I use this exceptional validator https://www.learndmarc.com/ to see if everything is working as expected. diff --git a/docker-compose.yml b/docker-compose.yml index 2771b5f..a0ae025 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,18 +3,23 @@ services: build: context: . dockerfile: ./hub/alpine/Dockerfile - command: 'tail -f /dev/null' + # we push the log out so we can capture it by other service like fail2band + command: 'tail -f /var/log/postfix.log' container_name: postfix env_file: + # for production I switch this to ./docker/.env.prod - ./docker/.env.example ports: - "25:25" - "465:465" - '587:587' volumes: - - ./docker/parse_resty_auto_ssl.py:/root/parse_resty_auto_ssl.py - - ./docker/setup.sh:/root/setup.sh - ./data/.users.example:/tmp/passwd - ./data/opendkim/domainkey:/etc/opendkim/domainkey + # this is for dev purpose only, the scripts are being copied by the dockerfile build + - ./docker/parse_resty_auto_ssl.py:/root/parse_resty_auto_ssl.py + - ./docker/setup.sh:/root/setup.sh + # example to re use let's encrypt certificates + # you should re use the certificate volume instead - ./data/restry-auto-ssl/example01.com:/etc/resty-auto-ssl/storage/file/example01.com%3Alatest - ./data/restry-auto-ssl/example02.com:/etc/resty-auto-ssl/storage/file/example02.com%3Alatest diff --git a/docker/setup.sh b/docker/setup.sh index 8465fa5..bc7fdf8 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -19,7 +19,6 @@ chown postfix:postfix /etc/sasldb2 ######################## # Setting up Postfix # ######################## -postmap -F /etc/postfix/aliases postconf -e myhostname=$maindomain postconf -F '*/*/chroot = n' @@ -97,6 +96,7 @@ postconf -e virtual_alias_maps=lmdb:/etc/postfix/virtual while IFS=':' read -r _domain _user _pwd _cannonical; do echo $_user $_cannonical >> /etc/postfix/virtual done < /tmp/passwd +postmap etc/postfix/virtual ##################### # Setting up loggin # From a607b1388b880175cbfeb3d9a3f705fe7ab1eb0d Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Sat, 5 Oct 2024 17:18:12 +0200 Subject: [PATCH 06/12] Improve: Use custom user. --- .gitignore | 2 +- docker-compose.yml | 6 +----- docker/map_ssl.py | 18 +++++++++++++++++ docker/parse_resty_auto_ssl.py | 36 ---------------------------------- docker/setup.sh | 15 +++++++------- hub/alpine/Dockerfile | 9 +++++++++ 6 files changed, 37 insertions(+), 49 deletions(-) create mode 100755 docker/map_ssl.py delete mode 100755 docker/parse_resty_auto_ssl.py diff --git a/.gitignore b/.gitignore index 5aec8f3..1e3541e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .DS_Store .env.prod data/opendkim/* -data/restry-auto-ssl +data/.users.prod diff --git a/docker-compose.yml b/docker-compose.yml index a0ae025..a4617b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,9 +17,5 @@ services: - ./data/.users.example:/tmp/passwd - ./data/opendkim/domainkey:/etc/opendkim/domainkey # this is for dev purpose only, the scripts are being copied by the dockerfile build - - ./docker/parse_resty_auto_ssl.py:/root/parse_resty_auto_ssl.py + - ./docker/map_ssl.py:/root/map_ssl.py - ./docker/setup.sh:/root/setup.sh - # example to re use let's encrypt certificates - # you should re use the certificate volume instead - - ./data/restry-auto-ssl/example01.com:/etc/resty-auto-ssl/storage/file/example01.com%3Alatest - - ./data/restry-auto-ssl/example02.com:/etc/resty-auto-ssl/storage/file/example02.com%3Alatest diff --git a/docker/map_ssl.py b/docker/map_ssl.py new file mode 100755 index 0000000..afea07b --- /dev/null +++ b/docker/map_ssl.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +import json +import os + +# constants +DOMAINS = os.environ.get('maildomains').split(',') +DOMAIN_MAP = os.path.join(os.sep, 'etc', 'postfix', 'ssl_map') +PATH = os.path.join(os.sep, 'etc', 'letsencrypt', 'live') + +# open a file to write the mapping +with open(DOMAIN_MAP, 'w+') as mapping: + # loop per domain + for domain in DOMAINS: + # write the domain, key and crt + privkey_pem = os.path.join(PATH, domain, 'privkey.pem') + fullchain_pem = os.path.join(PATH, domain, 'fullchain.pem') + mapping.write(f'{domain} {privkey_pem} {fullchain_pem}\n') diff --git a/docker/parse_resty_auto_ssl.py b/docker/parse_resty_auto_ssl.py deleted file mode 100755 index 9837bf5..0000000 --- a/docker/parse_resty_auto_ssl.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -import json -import os - -# constants -DOMAINS = os.environ.get('maildomains').split(',') -DOMAIN_MAP = os.path.join(os.sep, 'etc', 'postfix', 'ssl_map') -PATH = os.path.join(os.sep, 'etc', 'resty-auto-ssl', 'storage', 'file') -POSTFIX_CRT_PATH = os.path.join(os.sep, 'etc', 'postfix', 'certs') - -# open a file to write the mapping -with open(DOMAIN_MAP, 'w+') as mapping: - - # loop per domain - for domain in DOMAINS: - - file_path = os.path.join(PATH, f'{domain}%3Alatest') - file_key_path = os.path.join(POSTFIX_CRT_PATH, f'{domain}.key') - file_crt_path = os.path.join(POSTFIX_CRT_PATH, f'{domain}.crt') - - # write the domain, key and crt - mapping.write(f'{domain} {file_key_path} {file_crt_path}\n') - - os.makedirs(POSTFIX_CRT_PATH, exist_ok=True) - with open(file_path) as ssl_file: - data = json.load(ssl_file) - key_content = data.get('privkey_pem') - cert_content = data.get('fullchain_pem') - - with open(file_key_path, 'w+') as key_file: - key_file.write(key_content) - - with open(file_crt_path, 'w+') as crt_file: - crt_file.write(cert_content) - diff --git a/docker/setup.sh b/docker/setup.sh index bc7fdf8..fca3db9 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -14,7 +14,7 @@ touch /etc/postfix/sasl/smtpd.conf while IFS=':' read -r _domain _user _pwd _cannonical; do echo $_pwd | saslpasswd2 -p -c -u $_domain $_user done < /tmp/passwd -chown postfix:postfix /etc/sasldb2 +chown app-user:postfix /etc/sasldb2 ######################## # Setting up Postfix # @@ -40,12 +40,14 @@ postconf -e smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth ###################### # Enable TLS and SSL # ###################### -./parse_resty_auto_ssl.py +./map_ssl.py postmap -F lmdb:/etc/postfix/ssl_map -if [[ -n "/etc/postfix/certs/$maindomain.key" && -n "/etc/postfix/certs/$maindomain.crt" ]]; then +MAIN_KEY="/etc/letsencrypt/live/$maindomain/privkey.pem" +MAIN_CRT="/etc/letsencrypt/live/$maindomain/fullchain.pem" +if [[ -n $MAIN_KEY && -n $MAIN_CRT ]]; then # /etc/postfix/main.cf - postconf -e smtpd_tls_key_file=/etc/postfix/certs/$maindomain.key - postconf -e smtpd_tls_cert_file=/etc/postfix/certs/$maindomain.crt + postconf -e smtpd_tls_key_file=$MAIN_KEY + postconf -e smtpd_tls_cert_file=$MAIN_CRT postconf -e smtpd_tls_protocols=TLSv1.2,TLSv1.1,!TLSv1,!SSLv2,!SSLv3 postconf -e smtp_tls_protocols=TLSv1.2,TLSv1.1,!TLSv1,!SSLv2,!SSLv3 postconf -e smtpd_tls_ciphers=high @@ -64,7 +66,6 @@ if [[ -n "/etc/postfix/certs/$maindomain.key" && -n "/etc/postfix/certs/$maindom postconf -e smtp_tls_loglevel=2 postconf -e tls_preempt_cipherlist=yes postconf -e tls_server_sni_maps=lmdb:/etc/postfix/ssl_map - chmod 400 /etc/postfix/certs/*.* # /etc/postfix/master.cf postconf -M submission/inet="submission inet n - n - - smtpd" postconf -P "submission/inet/syslog_name=postfix/submission" @@ -152,6 +153,6 @@ for domain in $maildomains; do opendkim-genkey -b 1024 -d $domain -D /etc/opendkim/domainkey -s $domain -v chmod 400 /etc/opendkim/domainkey/$domain.private fi - chown opendkim:opendkim -R /etc/opendkim/domainkey + chown app-user:opendkim -R /etc/opendkim/domainkey done IFS=$OLDIFS diff --git a/hub/alpine/Dockerfile b/hub/alpine/Dockerfile index e91b558..4d3eea8 100644 --- a/hub/alpine/Dockerfile +++ b/hub/alpine/Dockerfile @@ -20,4 +20,13 @@ RUN wget https://github.com/cyrusimap/cyrus-sasl/releases/download/cyrus-sasl-2. # Removing non needed builder packages RUN apk del $BUILDER_PACKAGES +# touch log file +RUN mkdir -p /var/log && touch /var/log/postfix.log + +# setup user +RUN addgroup --gid 1000 app-user \ + && adduser --disabled-password --no-create-home --gecos "" --home /root --ingroup opendkim --ingroup postfix --ingroup root --uid 1000 app-user + +USER app-user + WORKDIR /root From d9e70284ca2f91ca5f941d273cb3c29637e069a9 Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Sat, 5 Oct 2024 17:55:18 +0200 Subject: [PATCH 07/12] Fixing: User within container. --- hub/alpine/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hub/alpine/Dockerfile b/hub/alpine/Dockerfile index 4d3eea8..6c7c8da 100644 --- a/hub/alpine/Dockerfile +++ b/hub/alpine/Dockerfile @@ -24,9 +24,9 @@ RUN apk del $BUILDER_PACKAGES RUN mkdir -p /var/log && touch /var/log/postfix.log # setup user -RUN addgroup --gid 1000 app-user \ - && adduser --disabled-password --no-create-home --gecos "" --home /root --ingroup opendkim --ingroup postfix --ingroup root --uid 1000 app-user - -USER app-user +RUN addgroup --gid 1000 app-user && \ + adduser --disabled-password --no-create-home --gecos "" --home /root --ingroup app-user --uid 1000 app-user && \ + addgroup app-user opendkim && \ + addgroup app-user postfix WORKDIR /root From c0c236193fb9e2c63b61a298ee0588c7ea9187f7 Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Sat, 5 Oct 2024 18:09:18 +0200 Subject: [PATCH 08/12] Fixing: small bugs. --- docker/setup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/setup.sh b/docker/setup.sh index fca3db9..ca06402 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -14,7 +14,7 @@ touch /etc/postfix/sasl/smtpd.conf while IFS=':' read -r _domain _user _pwd _cannonical; do echo $_pwd | saslpasswd2 -p -c -u $_domain $_user done < /tmp/passwd -chown app-user:postfix /etc/sasldb2 +chown postfix:postfix /etc/sasldb2 ######################## # Setting up Postfix # @@ -97,7 +97,7 @@ postconf -e virtual_alias_maps=lmdb:/etc/postfix/virtual while IFS=':' read -r _domain _user _pwd _cannonical; do echo $_user $_cannonical >> /etc/postfix/virtual done < /tmp/passwd -postmap etc/postfix/virtual +postmap /etc/postfix/virtual ##################### # Setting up loggin # From ed6efcb98a6d5abf2697a37d7fe267a43aa4413c Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Sat, 5 Oct 2024 18:29:15 +0200 Subject: [PATCH 09/12] Fixing: User within container. --- docker/setup.sh | 2 +- hub/alpine/Dockerfile | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/setup.sh b/docker/setup.sh index ca06402..9e5130c 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -132,7 +132,7 @@ Mode sv PidFile /var/run/opendkim/opendkim.pid SignatureAlgorithm rsa-sha256 -UserID opendkim:opendkim +UserID app-user:app-user Socket inet:12301@localhost EOF diff --git a/hub/alpine/Dockerfile b/hub/alpine/Dockerfile index 6c7c8da..f10da6e 100644 --- a/hub/alpine/Dockerfile +++ b/hub/alpine/Dockerfile @@ -26,7 +26,6 @@ RUN mkdir -p /var/log && touch /var/log/postfix.log # setup user RUN addgroup --gid 1000 app-user && \ adduser --disabled-password --no-create-home --gecos "" --home /root --ingroup app-user --uid 1000 app-user && \ - addgroup app-user opendkim && \ addgroup app-user postfix WORKDIR /root From 85443bb1fe7449aea0f6fefc740a50ed105928ba Mon Sep 17 00:00:00 2001 From: "blasvicco@gmail.com" Date: Sat, 5 Oct 2024 19:46:04 +0200 Subject: [PATCH 10/12] Fixing: User setup. --- docker/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/setup.sh b/docker/setup.sh index 9e5130c..d2dc15f 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -153,6 +153,6 @@ for domain in $maildomains; do opendkim-genkey -b 1024 -d $domain -D /etc/opendkim/domainkey -s $domain -v chmod 400 /etc/opendkim/domainkey/$domain.private fi - chown app-user:opendkim -R /etc/opendkim/domainkey + chown app-user:app-user -R /etc/opendkim/domainkey done IFS=$OLDIFS From a9ed1d8556d69451d2a550b57c185e99c08e79fb Mon Sep 17 00:00:00 2001 From: Blas Date: Mon, 25 Nov 2024 20:58:33 +0100 Subject: [PATCH 11/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c97736b..2355398 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ docker exec -it postfix sh # After the setup we launch the service in this order syslogd # optional to capture opemdkim logs -opemdkim # start the opmkdim service +opendkim # start the opmkdim service postfix start # to start postfix ``` From 2f05f883c4bbcd69ce2bfd2211f878b74e09fe3a Mon Sep 17 00:00:00 2001 From: Blas Date: Tue, 26 Nov 2024 23:35:28 +0100 Subject: [PATCH 12/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2355398..2b3db56 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ postfix start # to start postfix You can check if the services started properly by tailing the logs: ```BASH tail -f /var/log/messages # sys log where you can find details about openkdim service -tail -f /var/log/postfix +tail -f /var/log/postfix.log ``` ## Setting up your DNS records: