From 21ba201b611e2b94f3590809f60d57aee06891e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Mass=C3=A9?= Date: Sun, 7 Jun 2026 13:38:12 +0000 Subject: [PATCH] add cookbook for smtprelay --- cookbooks/smtprelay/Makefile | 12 +++ cookbooks/smtprelay/README.md | 68 +++++++++++++++++ cookbooks/smtprelay/SPECS.md | 73 +++++++++++++++++++ .../smtprelay/config/container/Containerfile | 22 ++++++ .../config/container/install-smtprelay.sh | 9 +++ .../config/examples/allowed_users.txt | 16 ++++ .../smtprelay/config/examples/smtprelay.ini | 42 +++++++++++ cookbooks/smtprelay/other/lego/smtprelay.sh | 6 ++ cookbooks/smtprelay/overlay.bu | 9 +++ cookbooks/smtprelay/smtprelay-build.timer | 10 +++ cookbooks/smtprelay/smtprelay.build | 10 +++ cookbooks/smtprelay/smtprelay.container | 49 +++++++++++++ cookbooks/smtprelay/smtprelay.target | 11 +++ cookbooks/smtprelay/tmpfiles.d/smtprelay.conf | 2 + 14 files changed, 339 insertions(+) create mode 100644 cookbooks/smtprelay/Makefile create mode 100644 cookbooks/smtprelay/README.md create mode 100644 cookbooks/smtprelay/SPECS.md create mode 100644 cookbooks/smtprelay/config/container/Containerfile create mode 100755 cookbooks/smtprelay/config/container/install-smtprelay.sh create mode 100644 cookbooks/smtprelay/config/examples/allowed_users.txt create mode 100644 cookbooks/smtprelay/config/examples/smtprelay.ini create mode 100755 cookbooks/smtprelay/other/lego/smtprelay.sh create mode 100644 cookbooks/smtprelay/overlay.bu create mode 100644 cookbooks/smtprelay/smtprelay-build.timer create mode 100644 cookbooks/smtprelay/smtprelay.build create mode 100644 cookbooks/smtprelay/smtprelay.container create mode 100644 cookbooks/smtprelay/smtprelay.target create mode 100644 cookbooks/smtprelay/tmpfiles.d/smtprelay.conf diff --git a/cookbooks/smtprelay/Makefile b/cookbooks/smtprelay/Makefile new file mode 100644 index 0000000..513932c --- /dev/null +++ b/cookbooks/smtprelay/Makefile @@ -0,0 +1,12 @@ +## +## Makefile for smtprelay quadlet +## + +DEPENDENCIES = lego + +# smtprelay quadlet is mapped to the 10030 user (smtprelay) and 10000 group (itix-svc) +PROJECT_UID = 10030 +PROJECT_GID = 10000 + +# Include common Makefile +include ../../scripts/common.mk diff --git a/cookbooks/smtprelay/README.md b/cookbooks/smtprelay/README.md new file mode 100644 index 0000000..0dd8cca --- /dev/null +++ b/cookbooks/smtprelay/README.md @@ -0,0 +1,68 @@ +# Podman Quadlet: smtprelay + +## Overview + +[smtprelay](https://github.com/decke/smtprelay) is a small Golang based SMTP relay/proxy server that accepts mail via SMTP and forwards it to an upstream smarthost (ex: Mailgun, Gmail, ...). + +This cookbook: + +- Builds a custom smtprelay container image locally, from CentOS Stream 10. +- Runs smtprelay directly as a dedicated, unprivileged UID/GID (no user namespace mapping). +- Listens on the submission port (587) with STARTTLS, authenticating clients against a local user/password file. +- Loads TLS certificates issued by the `lego` cookbook and reloads them automatically when renewed. +- Includes a timer to periodically rebuild the container image. + +## Prerequisites + +- Configuration file `/etc/quadlets/smtprelay/smtprelay.ini` must exist. +- File `/etc/quadlets/smtprelay/allowed_users.txt` must exist, listing the users allowed to relay mail. +- The `lego` cookbook should be configured to provide TLS certificates. + +## Usage + +In a separate terminal, follow the logs. + +```sh +sudo make tail-logs +``` + +Install the Podman Quadlets and start smtprelay. + +```sh +sudo make clean install +``` + +You should see the **smtprelay-build.service** building the smtprelay container image. +Then, the **smtprelay.service** should start up. + +Verify smtprelay is running: + +```sh +sudo systemctl status smtprelay.service +``` + +Send a test mail with [swaks](https://www.jetmore.org/john/code/swaks/): + +```sh +swaks --to youremail@example.com --from youremail@example.com --auth-user yourusername --auth-password yourpassword --port 587 --tls +``` + +When Let's Encrypt certificates are renewed, the renewal hook automatically restarts smtprelay so it picks up the new certificates. + +Restart the **smtprelay.target** unit. + +```sh +sudo systemctl restart smtprelay.target +``` + +Finally, remove the quadlets, their configuration and their data. + +```sh +sudo make uninstall clean +``` + +## Integration tests + +```sh +sudo make test +``` diff --git a/cookbooks/smtprelay/SPECS.md b/cookbooks/smtprelay/SPECS.md new file mode 100644 index 0000000..508bfe8 --- /dev/null +++ b/cookbooks/smtprelay/SPECS.md @@ -0,0 +1,73 @@ +# Specification for smtprelay Quadlet Cookbook + +You will have to develop a Quadlet cookbook for smtprelay, the mail transfer agent. + +## Architecture + +smtprelay is a mail transfer agent, deployed as a container image. +The container image will be built from the CentOS Stream 10 image (`quay.io/centos/centos:stream10`). + +## Common requirements + +- The `quay.io/centos/centos:stream10` docker image MUST have its own quadlet .image file. +- Each cookbook MUST have a dedicated unique UID. The GID is 10000. + +## Security + +Directly set the UID and GID in the quadlet file (no mapping). +Use the host network, like other quadlet cookbooks. +Let's Encrypt certificates will be handled by Traefik, so no need to worry about that in the smtprelay cookbook. + +## Installation + +Create the Containerfile for smtprelay, which will install the smtprelay binary. +The smtprelay binary can be obtained from the official releases on GitHub: https://github.com/decke/smtprelay. + +Look at `cookbooks/base/config/install-fastfetch.sh` for an example of how to install a binary from a GitHub release in a Containerfile. + +## Configuration + +A sample configuration file for smtprelay: + +```ini +; Hostname for this SMTP server +hostname = localhost + +; File which contains username and password used for +; authentication before they can send mail. +allowed_users = /etc/smtprelay/allowed_users.txt + +; Networks that are allowed to send mails to us +; Defaults to localhost. If set to "", then any address is allowed. +;allowed_nets = 0.0.0.0/0 ::/0 +allowed_nets = 0.0.0.0/0 + +; Enable TLS for incoming connections on port 587 +listen = starttls://0.0.0.0:587 +local_cert = /etc/smtprelay/tls/localhost.crt +local_key = /etc/smtprelay/tls/localhost.key + +; Enforce encrypted connection on STARTTLS ports before +; accepting mails from client. +local_forcetls = true + +; Relay Config (ex: Mailgun) +remotes = starttls://user:pass@smtp.mailgun.org:587 +``` + +## Entrypoint + +```sh +smtprelay --config /etc/smtprelay/smtprelay.ini -logfile=/dev/stdout +``` + +## How to test + +```sh +swaks --to youremail@example.com --from youremail@example.com --auth-user yourusername --auth-password yourpassword --port 587 --tls +``` + +## Useful examples + +You can copy the structure of the `miniflux` cookbook. +Look at the `samba` cookbook for an example of how to handle the container image building. diff --git a/cookbooks/smtprelay/config/container/Containerfile b/cookbooks/smtprelay/config/container/Containerfile new file mode 100644 index 0000000..0b618ee --- /dev/null +++ b/cookbooks/smtprelay/config/container/Containerfile @@ -0,0 +1,22 @@ +FROM quay.io/centos/centos:stream10 AS builder + +# Tools needed to fetch and unpack the smtprelay release +RUN dnf install -y curl jq tar gzip \ + && dnf clean all + +COPY install-smtprelay.sh / +RUN /install-smtprelay.sh + +FROM quay.io/centos/centos:stream10 + +# CA certificates are required to establish TLS connections to the relay host +RUN dnf install -y ca-certificates \ + && dnf clean all + +COPY --from=builder /usr/local/bin/smtprelay /usr/local/bin/smtprelay + +# Submission port +EXPOSE 587 + +ENTRYPOINT [ "/usr/local/bin/smtprelay" ] +CMD [ "--config", "/etc/smtprelay/smtprelay.ini", "-logfile=/dev/stdout" ] diff --git a/cookbooks/smtprelay/config/container/install-smtprelay.sh b/cookbooks/smtprelay/config/container/install-smtprelay.sh new file mode 100755 index 0000000..9e755d8 --- /dev/null +++ b/cookbooks/smtprelay/config/container/install-smtprelay.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -Eeuo pipefail +SMTPRELAY_LATEST_VERSION="$(curl -sSfL https://api.github.com/repos/decke/smtprelay/releases | jq -r '.[] | select(.prerelease == false and .draft == false) | .tag_name' | sort -V | tail -1)" +SMTPRELAY_VERSION="${SMTPRELAY_VERSION:-$SMTPRELAY_LATEST_VERSION}" +declare -A ARCH_MAP=( ["x86_64"]="amd64" ["aarch64"]="arm64" ) +arch="$(arch)" +arch=${ARCH_MAP[$arch]} +echo "Installing smtprelay $SMTPRELAY_VERSION for $arch..." +curl -sSfL https://github.com/decke/smtprelay/releases/download/$SMTPRELAY_VERSION/smtprelay-$SMTPRELAY_VERSION-linux-$arch.tar.gz | tar -zx -C /usr/local/bin --no-same-owner smtprelay diff --git a/cookbooks/smtprelay/config/examples/allowed_users.txt b/cookbooks/smtprelay/config/examples/allowed_users.txt new file mode 100644 index 0000000..0939054 --- /dev/null +++ b/cookbooks/smtprelay/config/examples/allowed_users.txt @@ -0,0 +1,16 @@ +# File which contains username and password used for authentication +# before clients can relay mail through this server. +# +# Format (one user per line, fields separated by spaces): +# username bcrypt-hash [email[,email[,...]]] +# +# username: the SMTP auth username +# bcrypt-hash: the bcrypt hash of the password +# email: comma-separated list of "from" addresses the user is +# allowed to send as (omit to allow any address) +# +# Generate the bcrypt hash of a password with: +# python3 -c "import bcrypt; print(bcrypt.hashpw(b'', bcrypt.gensalt()).decode())" +# +# Example user "demo" (password "changeme") allowed to send from any address: +demo $2b$12$V3XmeosOSoI4B.2D8HLzWu7y2YQaoBeF2unnGopZ2ZJJpQ58sKToa diff --git a/cookbooks/smtprelay/config/examples/smtprelay.ini b/cookbooks/smtprelay/config/examples/smtprelay.ini new file mode 100644 index 0000000..d250511 --- /dev/null +++ b/cookbooks/smtprelay/config/examples/smtprelay.ini @@ -0,0 +1,42 @@ +# ----------------------------------------------------------------------- +# smtprelay configuration - relay-only SMTP server +# +# Copy this file to /etc/quadlets/smtprelay/smtprelay.ini and adjust the +# values for your environment. +# ----------------------------------------------------------------------- + +; Hostname for this SMTP server +hostname = mail.example.com + +; File which contains username and password used for +; authentication before they can send mail (see allowed_users.txt example). +allowed_users = /etc/smtprelay/allowed_users.txt + +; Networks that are allowed to send mails to us. +; The container uses the host network, so clients connect from outside - +; allow any address and rely on SMTP authentication instead. +allowed_nets = 0.0.0.0/0 + +; ----------------------------------------------------------------------- +; Inbound TLS (certificates provided by the lego cookbook) +; +; Replace "localhost" with the actual certificate filename from +; /var/lib/quadlets/lego/certificates/. +; ----------------------------------------------------------------------- + +; Enable TLS for incoming connections on port 587 +listen = starttls://0.0.0.0:587 +local_cert = /etc/smtprelay/tls/localhost.crt +local_key = /etc/smtprelay/tls/localhost.key + +; Enforce encrypted connection on STARTTLS ports before +; accepting mails from client. +local_forcetls = true + +; ----------------------------------------------------------------------- +; Outbound relay +; ----------------------------------------------------------------------- + +; Relay all mail through an upstream smarthost (ex: Mailgun). +; Adjust the credentials and host for your provider. +remotes = starttls://user:pass@smtp.mailgun.org:587 diff --git a/cookbooks/smtprelay/other/lego/smtprelay.sh b/cookbooks/smtprelay/other/lego/smtprelay.sh new file mode 100755 index 0000000..0b4a060 --- /dev/null +++ b/cookbooks/smtprelay/other/lego/smtprelay.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -Eeuo pipefail + +install -o 10030 -g 10000 -m 0600 -t /run/quadlets/smtprelay/tls /var/lib/quadlets/lego/certificates/*.crt /var/lib/quadlets/lego/certificates/*.key +systemctl --no-block restart smtprelay.service diff --git a/cookbooks/smtprelay/overlay.bu b/cookbooks/smtprelay/overlay.bu new file mode 100644 index 0000000..626da1f --- /dev/null +++ b/cookbooks/smtprelay/overlay.bu @@ -0,0 +1,9 @@ +variant: fcos +version: 1.4.0 +passwd: + users: + - name: smtprelay + uid: 10030 + gecos: smtprelay MTA + home_dir: /var/lib/quadlets/smtprelay + primary_group: itix-svc diff --git a/cookbooks/smtprelay/smtprelay-build.timer b/cookbooks/smtprelay/smtprelay-build.timer new file mode 100644 index 0000000..ba44219 --- /dev/null +++ b/cookbooks/smtprelay/smtprelay-build.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Rebuild the smtprelay container image +PartOf=smtprelay.target + +[Timer] +OnCalendar=daily +Persistent=true + +[Install] +WantedBy=smtprelay.target diff --git a/cookbooks/smtprelay/smtprelay.build b/cookbooks/smtprelay/smtprelay.build new file mode 100644 index 0000000..24a0fb2 --- /dev/null +++ b/cookbooks/smtprelay/smtprelay.build @@ -0,0 +1,10 @@ +[Unit] +Description=Build of the smtprelay MTA +Wants=network-online.target +After=network-online.target centos-stream10-image.service +Requires=centos-stream10-image.service + +[Build] +File=/etc/quadlets/smtprelay/container/Containerfile +ImageTag=localhost/smtprelay:latest +SetWorkingDirectory=/etc/quadlets/smtprelay/container diff --git a/cookbooks/smtprelay/smtprelay.container b/cookbooks/smtprelay/smtprelay.container new file mode 100644 index 0000000..6335466 --- /dev/null +++ b/cookbooks/smtprelay/smtprelay.container @@ -0,0 +1,49 @@ +[Unit] +Description=smtprelay MTA +Documentation=https://github.com/decke/smtprelay +After=local-fs.target network.target smtprelay-build.service lego.target +Wants=smtprelay-build.service lego.target + +# Only start if the main configuration file exists +ConditionPathExists=/etc/quadlets/smtprelay/smtprelay.ini + +# Stop when the target is stopped +PartOf=smtprelay.target + +[Container] +ContainerName=smtprelay + +# Image +Image=localhost/smtprelay:latest +AutoUpdate=local + +# Security - run directly as a dedicated, unprivileged UID/GID (no mapping) +User=10030 +Group=10000 + +# Port 587 is a privileged port (< 1024); grant the capability to bind to it +AddCapability=CAP_NET_BIND_SERVICE + +# Command and arguments +Entrypoint=/usr/local/bin/smtprelay +Exec=--config /etc/smtprelay/smtprelay.ini -logfile=/dev/stdout + +# Storage +Volume=/etc/quadlets/smtprelay/smtprelay.ini:/etc/smtprelay/smtprelay.ini:ro,Z +Volume=/etc/quadlets/smtprelay/allowed_users.txt:/etc/smtprelay/allowed_users.txt:ro,Z +Volume=/run/quadlets/smtprelay/tls:/etc/smtprelay/tls:Z + +# Network +Network=host + +[Service] +Restart=always +RestartSec=10 +TimeoutStartSec=120 +TimeoutStopSec=30 + +# Get the TLS certificates in place before starting smtprelay +ExecStartPre=/bin/sh -c 'install -o 10030 -g 10000 -m 0600 -t /run/quadlets/smtprelay/tls /var/lib/quadlets/lego/certificates/*.crt /var/lib/quadlets/lego/certificates/*.key' + +[Install] +WantedBy=smtprelay.target diff --git a/cookbooks/smtprelay/smtprelay.target b/cookbooks/smtprelay/smtprelay.target new file mode 100644 index 0000000..fa074e8 --- /dev/null +++ b/cookbooks/smtprelay/smtprelay.target @@ -0,0 +1,11 @@ +[Unit] +Description=smtprelay Service Target +Documentation=man:systemd.target(5) +Requires=smtprelay.service smtprelay-build.timer +After=smtprelay.service smtprelay-build.timer + +# Allow isolation - can stop/start this target independently +AllowIsolate=yes + +[Install] +WantedBy=multi-user.target diff --git a/cookbooks/smtprelay/tmpfiles.d/smtprelay.conf b/cookbooks/smtprelay/tmpfiles.d/smtprelay.conf new file mode 100644 index 0000000..1f69694 --- /dev/null +++ b/cookbooks/smtprelay/tmpfiles.d/smtprelay.conf @@ -0,0 +1,2 @@ +d$ /run/quadlets/smtprelay 0700 10030 10000 - +d$ /run/quadlets/smtprelay/tls 0700 10030 10000 -