From 349489c58b2b1267a7cc77d5d3edb48aa31a614d Mon Sep 17 00:00:00 2001 From: Gyurix Date: Wed, 5 Mar 2025 12:49:33 +0100 Subject: [PATCH] update to multiarch --- .drone.yml | 47 +++++++++++++++ Dockerfile | 33 +++++++++++ LICENSE | 21 +++++++ README.md | 132 ++++++++++++++++++++++++++++++++++++++++- VERSION | 1 + docker-compose-dev.yml | 33 +++++++++++ entrypoint.sh | 108 +++++++++++++++++++++++++++++++++ 7 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 .drone.yml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 VERSION create mode 100644 docker-compose-dev.yml create mode 100644 entrypoint.sh diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..7100768 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,47 @@ +kind: pipeline +type: kubernetes +name: default + +node_selector: + physical-node: dev2 + +trigger: + branch: + - master + event: + - push +workspace: + path: /drone/src + +steps: + - name: build multiarch from dev + image: docker.io/owncloudci/drone-docker-buildx:4 + privileged: true + settings: + cache-from: [ "registry.dev.format.hu/smtp" ] + registry: registry.dev.format.hu + repo: registry.dev.format.hu/smtp + tags: latest + dockerfile: Dockerfile + username: + from_secret: dev-hu-registry-username + password: + from_secret: dev-hu-registry-password + platforms: + - linux/amd64 + - linux/arm64 + + - name: pull image to dockerhub + image: docker.io/owncloudci/drone-docker-buildx:4 + privileged: true + settings: + cache-from: [ "safebox/smtp" ] + repo: safebox/smtp + tags: latest + username: + from_secret: dockerhub-username + password: + from_secret: dockerhub-password + platforms: + - linux/amd64 + - linux/arm64 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..21952d1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +FROM alpine:3.20 + +ARG BUILD_DATE + +LABEL \ + maintainer="Logan Marchione " \ + org.opencontainers.image.authors="Logan Marchione " \ + org.opencontainers.image.title="docker-postfixrelay" \ + org.opencontainers.image.description="Runs Postfix (as a relay) in Docker" \ + org.opencontainers.image.created=$BUILD_DATE + +RUN apk add --no-cache --update \ + bash \ + ca-certificates \ + cyrus-sasl-login \ + dumb-init \ + postfix \ + postfix-doc \ + tzdata + +EXPOSE 25 + +VOLUME [ "/var/spool/postfix" ] + +COPY ./entrypoint.sh / + +COPY VERSION / + +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/usr/bin/dumb-init", "--", "/entrypoint.sh"] + +HEALTHCHECK CMD netstat -ltn | grep -c ":25" || exit 1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0c895e8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Logan Marchione + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. diff --git a/README.md b/README.md index 3004a2b..c77f0f5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,132 @@ -# smtp-client +# docker-postfixrelay +[![CI/CD](https://github.com/loganmarchione/docker-postfixrelay/actions/workflows/main.yml/badge.svg)](https://github.com/loganmarchione/docker-postfixrelay/actions/workflows/main.yml) +[![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/loganmarchione/docker-postfixrelay)](https://hub.docker.com/r/loganmarchione/docker-postfixrelay) + +Runs Postfix (as a relay) in Docker + - Source code: [GitHub](https://github.com/loganmarchione/docker-postfixrelay) + - Docker container: [Docker Hub](https://hub.docker.com/r/loganmarchione/docker-postfixrelay) + - Image base: [Alpine Linux](https://hub.docker.com/_/alpine/) + - Init system: [dumb-init](https://github.com/Yelp/dumb-init) + - Application: [Postfix](http://www.postfix.org/) + - Architecture: `linux/amd64,linux/arm64,linux/arm/v7` + +## Explanation + + - This runs Postfix (as a relay) in Docker. + - Most home ISPs block port 25, so outbound emails must be relayed through an external SMTP server (e.g., Gmail). + - This container acts as a single collections point for devices needing to send email. + - ⚠️ Postfix acts as an open relay. As such, this is not meant to be run on the internet, only on a trusted internal network! ⚠️ + +``` + Internal (LAN) network Public internet + +------------------ +| | +| Device sending | | | +| email alert | ------------- | | +| | | | | +------------------ | | | + | | F | +------------------ v | i | +| | ------------------ | r | ----------------------------- ------------------- +| Device sending | | | | e | | | | | +| email alert | ----> | This container | --| w |--> | SMTP server (e.g., Gmail) | ----> | Recipient email | +| | | | | a | | | | | +------------------ ------------------ | l | ----------------------------- ------------------- + ^ | l | +------------------ | | | +| | | | | +| Device sending | | | | +| email alert | ------------- | | +| | +------------------ +``` + +## Requirements + + - You must already have an account on an external SMTP server (e.g., Gmail, AWS SES, etc...). + - Your external SMTP server must be using encryption (i.e., plaintext is not allowed) + +## Docker image information + +### Docker image tags + - `latest`: Latest version + - `X.X.X`: [Semantic version](https://semver.org/) (use if you want to stick on a specific version) + +### Environment variables +| Variable | Required? | Definition | Example | Comments | +|-------------|---------------------------|---------------------------------------------|----------------------------|--------------------------------------------------------------| +| TZ | Yes | Timezone | America/New_York | https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | +| RELAY_HOST | Yes | Public SMTP server to use | smtp.gmail.com | | +| RELAY_PORT | Yes | Public SMTP port to use | 587 | | +| RELAY_USER | No | Address to login to $RELAY_HOST | SMTP username | | +| RELAY_PASS | No | Password to login to $RELAY_HOST | SMTP password | If using Gmail 2FA, you will need to setup an app password | +| TEST_EMAIL | No | Address to receive test email | receive_address@domain.com | If not set, test email will **not** be sent | +| MYORIGIN | No | Domain of the "from" address | domain.com | Needed for things like AWS SES where the domain must be set | +| FROMADDRESS | No | Changes the "from" address | my_email@domain.com | Needed for some SMTP services where the FROM address needs to be set, [fixes issue 19](https://github.com/loganmarchione/docker-postfixrelay/issues/19) | +| MYNETWORKS | No (default: 0.0.0.0/0) | Networks that Postfix will forward mail for | 1.2.3.4/24, 5.6.7.8/24 | Single or multiple trusted networks separated with a comma | +| MSG_SIZE | No (default: 10240000) | Postfix `message_size_limit` in bytes | 30720000 | | +| LOG_DISABLE | No (default: false) | Setting to `true` disables logging | true | | + +### Ports +| Port on host | Port in container | Comments | +|---------------------------|-------------------|---------------------| +| Choose at your discretion | 25 | Postfix SMTP server | + +### Volumes +| Volume on host | Volume in container | Comments | +|---------------------------|---------------------|------------------------------------| +| Choose at your discretion | /var/spool/postfix | Used to store Postfix's mail spool | + +### Example usage +Below is an example docker-compose.yml file. +``` +version: '3' +services: + postfixrelay: + container_name: docker-postfixrelay + restart: unless-stopped + environment: + - TZ=America/New_York + - RELAY_HOST=smtp.gmail.com + - RELAY_PORT=587 + - RELAY_USER=your_email_here@gmail.com + - RELAY_PASS=your_password_here + - TEST_EMAIL=test_email@domain.com + - MYORIGIN=domain.com + - FROMADDRESS=my_email@domain.com + - MYNETWORKS=1.2.3.4/24 + - MSG_SIZE=30720000 + - LOG_DISABLE=true + networks: + - postfixrelay + ports: + - '25:25' + volumes: + - 'postfixrelay_data:/var/spool/postfix' + image: loganmarchione/docker-postfixrelay:latest + +networks: + postfixrelay: + +volumes: + postfixrelay_data: + driver: local +``` + +Below is an example of running locally (used to edit/test/debug). +``` +# Build the Dockerfile +docker compose -f docker-compose-dev.yml up -d + +# View logs +docker compose -f docker-compose-dev.yml logs -f + +# Destroy when done +docker compose -f docker-compose-dev.yml down +``` + +## TODO +- [x] ~~Add a [healthcheck](https://docs.docker.com/engine/reference/builder/#healthcheck)~~ +- [ ] Add TLS support for SMTPD and listen on 587 diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..7b5753f --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.4.6 \ No newline at end of file diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 0000000..f8ad6e0 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,33 @@ +version: '3' +services: + postfixrelay: + container_name: docker-postfixrelay + restart: unless-stopped + build: + context: . + dockerfile: Dockerfile + environment: + - TZ=America/New_York + - RELAY_HOST=smtp.gmail.com + - RELAY_PORT=587 + - RELAY_USER=your_email_here@gmail.com + - RELAY_PASS=your_password_here + - TEST_EMAIL=test_email@domain.com + - MYORIGIN=domain.com + - FROMADDRESS=my_email@domain.com + - MYNETWORKS=1.2.3.4/24 + - MSG_SIZE=30720000 + - LOG_DISABLE=true + networks: + - postfixrelay + ports: + - '25:25' + volumes: + - 'postfixrelay_data:/var/spool/postfix' + +networks: + postfixrelay: + +volumes: + postfixrelay_data: + driver: local \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..f680c8d --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,108 @@ +#!/bin/sh -e + +printf "#####\n" +printf "# Container starting up!\n" +printf "#####\n" + +# Test variables for timezone +if [ -z "$TZ" ]; then + printf "# ERROR: TZ is undefined, exiting!\n" +else + printf "# STATE: Setting container timezone to $TZ\n" + ln -sf /usr/share/zoneinfo/"$TZ" /etc/localtime + echo "$TZ" > /etc/timezone +fi + +# Test variables for relay +if [[ -z "$RELAY_HOST" || -z "$RELAY_PORT" ]]; then + printf "# ERROR: Either RELAY_HOST or RELAY_PORT are undefined, exiting!\n" + exit 1 +fi + +# Create directories +printf "# STATE: Changing permissions\n" +postfix set-permissions + +# Set logging +if [[ "$LOG_DISABLE" == "true" ]]; then + printf "# WARN: Setting Postfix logging to /dev/null\n" +else + printf "# STATE: Setting Postfix logging to /dev/stdout\n" + postconf -e "maillog_file = /dev/stdout" +fi + +# Configure Postfix +printf "# STATE: Configuring Postfix\n" +postconf -e "inet_interfaces = all" +postconf -e "mydestination =" +postconf -e "mynetworks = ${MYNETWORKS:=0.0.0.0/0}" +postconf -e "relayhost = [$RELAY_HOST]:$RELAY_PORT" + +# Set the "from" domain, needed for things like AWS SES +if [[ -z "$MYORIGIN" ]]; then + printf "# WARN: MYORIGIN is undefined, continuing\n" +else + printf "# STATE: MYORIGIN is defined as $MYORIGIN\n" + postconf -e "myorigin = $MYORIGIN" +fi + +# Set the "from" address, needed for some SMTP providers +# https://serverfault.com/questions/147921/forcing-the-from-address-when-postfix-relays-over-smtp +if [[ -z "$FROMADDRESS" ]]; then + printf "# WARN: FROMADDRESS is undefined, continuing\n" +else + printf "# STATE: FROMADDRESS is defined as $FROMADDRESS\n" + postconf -e "smtp_header_checks = regexp:/etc/postfix/header_checks" + echo "/^From:.*/ REPLACE From: $FROMADDRESS" | tee /etc/postfix/header_checks > /dev/null + postconf -e "sender_canonical_maps = regexp:/etc/postfix/sender_canonical_maps" + echo "/.+/ $FROMADDRESS" | tee /etc/postfix/sender_canonical_maps > /dev/null +fi + +# Set the message_size_limit +if [[ -z "$MSG_SIZE" ]]; then + printf "# WARN: MSG_SIZE is undefined, continuing\n" +else + printf "# STATE: MSG_SIZE is defined as $MSG_SIZE\n" + postconf -e "message_size_limit = $MSG_SIZE" +fi + +# Client settings (for sending to the relay) +postconf -e "smtp_tls_security_level = encrypt" +postconf -e "smtp_tls_loglevel = 1" +postconf -e "smtp_tls_note_starttls_offer = yes" +postconf -e "smtp_sasl_auth_enable = yes" +postconf -e "smtp_sasl_security_options = noanonymous" +postconf -e "smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd" +postconf -e "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt" + +# Create password file +# Alpine 3.13 dropped support for Berkeley DB, so using lmdb instead +# https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.13.0#Deprecation_of_Berkeley_DB_.28BDB.29 +echo "[$RELAY_HOST]:$RELAY_PORT $RELAY_USER:$RELAY_PASS" > /etc/postfix/sasl_passwd +chown root:root /etc/postfix/sasl_passwd +chmod 600 /etc/postfix/sasl_passwd +postmap lmdb:/etc/postfix/sasl_passwd +rm -f /etc/postfix/sasl_passwd +chown root:root /etc/postfix/sasl_passwd.lmdb +chmod 600 /etc/postfix/sasl_passwd.lmdb + +# Rebuild the database for the mail aliases file +newaliases + +# Send test email +# Test for variable and queue the message now, it will send when Postfix starts +if [[ -z "$TEST_EMAIL" ]]; then + printf "# WARN: TEST_EMAIL is undefined, continuing without a test email\n" +else + printf "# STATE: Sending test email\n" + echo -e "Subject: Postfix relay test \r\nTest of Postfix relay from Docker container startup\nSent on $(date)\n" | sendmail -F "[Alert from Postfix]" "$TEST_EMAIL" +fi + +# Start Postfix +# Nothing else can log after this +printf "# STATE: Starting Postfix\n" +if [[ "$LOG_DISABLE" == "true" ]]; then + postfix start-fg > /dev/null +else + postfix start-fg +fi