diff --git a/images/postfix-pgsql/tests/setup.md b/images/postfix-pgsql/tests/setup.md new file mode 100644 index 0000000..a5e5497 --- /dev/null +++ b/images/postfix-pgsql/tests/setup.md @@ -0,0 +1,142 @@ +# Local Test Environment Setup + +Step-by-step instructions for standing up a self-contained test environment for +the `postfix-pgsql` image on any Linux host with Docker installed. No cloud +access is required. + +## Architecture + +``` +External SMTP client (e.g. swaks / telnet) + | + | :25 + v + [ postfix-pgsql ] ──pgsql lookup──> [ postgres ] + | + | :2525 (SMTP_RELAY_PORT) + v + [ mailhog ] <── captured mail browsable at :8025 +``` + +MailHog acts as a stand-in for the downstream SMTP consumer and captures every +message delivered to it, making it easy to verify end-to-end relay without +actually sending real mail. + +## 1. Create a dedicated Docker network + +All containers must share the same network so they can resolve each other by +container name (required because chroot is disabled in this image — see the +README). + +```bash +docker network create postfix-test-net +``` + +## 2. Start PostgreSQL with the relay_domain schema + +```bash +docker run -d \ + --name test-postgres \ + --network postfix-test-net \ + -e POSTGRES_USER=relayuser \ + -e POSTGRES_PASSWORD=testpassword \ + -e POSTGRES_DB=relaydb \ + postgres:16-alpine +``` + +Wait for Postgres to be ready, then create the `relay_domain` table and insert +a test domain: + +```bash +docker exec -i test-postgres psql -U relayuser -d relaydb <<'SQL' +CREATE TABLE relay_domain (domain TEXT PRIMARY KEY); +INSERT INTO relay_domain VALUES ('example.com'); +SQL +``` + +Verify: +```bash +docker exec -i test-postgres psql -U relayuser -d relaydb -c "SELECT * FROM relay_domain;" +``` + +## 3. Start MailHog + +MailHog listens on port 2525 for SMTP (matching `SMTP_RELAY_PORT`) and exposes +a web UI on port 8025. + +```bash +docker run -d \ + --name test-mailhog \ + --network postfix-test-net \ + -p 8025:8025 \ + mailhog/mailhog \ + MailHog -smtp-bind-addr 0.0.0.0:2525 +``` + +## 4. Start the postfix-pgsql container + +```bash +docker run -d \ + --name test-postfix \ + --network postfix-test-net \ + -p 25:25 \ + -e MAIL_DOMAIN=example.com \ + -e MYHOSTNAME=mx1.example.com \ + -e POSTGRES_HOST=test-postgres \ + -e POSTGRES_PORT=5432 \ + -e POSTGRES_USER=relayuser \ + -e POSTGRES_PASSWORD=testpassword \ + -e POSTGRES_DB=relaydb \ + -e SMTP_RELAY_HOST=test-mailhog \ + -e SMTP_RELAY_PORT=2525 \ + gitea.arnodo.fr/damien/postfix-pgsql:latest +``` + +Confirm it started cleanly: +```bash +docker logs test-postfix +``` + +Expected: Postfix master process line and no errors. + +## 5. Send a test message + +You can use `swaks` (Swiss Army Knife for SMTP) or plain `telnet`: + +```bash +# With swaks (install: apt install swaks / brew install swaks) +swaks --to user@example.com \ + --from sender@external.com \ + --server 127.0.0.1 \ + --port 25 + +# Or with telnet +telnet 127.0.0.1 25 + EHLO test + MAIL FROM: + RCPT TO: + DATA + Subject: Test + . + QUIT +``` + +## 6. View captured mail in MailHog + +**Web UI** — if testing on a remote host, open an SSH tunnel first: +```bash +ssh -L 8025:localhost:8025 user@your-host +``` +Then open `http://localhost:8025` in a browser. + +**HTTP API** (no tunnel needed): +```bash +curl -s http://localhost:8025/api/v2/messages | jq '.items[].Content.Headers.Subject' +``` + +## 7. Cleanup + +```bash +docker rm -f test-postfix test-mailhog test-postgres +docker network rm postfix-test-net +``` diff --git a/images/postfix-pgsql/tests/test-plan.md b/images/postfix-pgsql/tests/test-plan.md new file mode 100644 index 0000000..716da37 --- /dev/null +++ b/images/postfix-pgsql/tests/test-plan.md @@ -0,0 +1,23 @@ +# Test Plan: postfix-pgsql image + +Manual test suite run against the image during initial validation on a Scaleway +test VM. All tests use the local environment described in `setup.md`. + +**Convention for this table:** +- *Command(s)* are abbreviated; see `setup.md` for full environment setup. +- Add new rows at the bottom as the image evolves; do not rewrite existing rows + (failed + fixed tests are part of the history). +- Status: ✅ Pass · ❌ Fail · 🔧 Fixed (failed, root-caused, fix applied) + +| # | Test | What was checked | Command(s) | Expected | Actual | Status | +|---|------|-----------------|------------|----------|--------|--------| +| 1 | Clean startup | No errors in container logs after start; Postfix master process running | `docker run ... gitea.arnodo.fr/damien/postfix-pgsql:latest` then `docker logs test-postfix` | Postfix `daemon started` line, no `fatal`/`panic` entries | Logs show `postfix/master[1]: daemon started -- version 3.7.x`, self-signed cert generation output, no errors | ✅ Pass | +| 2 | Missing required env var | Container exits with a clear error message when a required variable is absent | `docker run ... -e POSTGRES_HOST= ...` (POSTGRES_HOST set to empty) | Non-zero exit, `ERROR: required environment variable POSTGRES_HOST is not set` printed to stderr | Container exited immediately with the expected message | ✅ Pass | +| 3 | Template rendering correctness | Rendered `main.cf` contains substituted values, no leftover `${VAR}` placeholders | `docker exec test-postfix cat /etc/postfix/main.cf` | `myhostname = mx1.example.com`, `mydomain = example.com`, TLS paths resolved, no literal `${...}` strings | All values substituted; Postfix-native `$mydomain` references (no braces) correctly left untouched by envsubst | ✅ Pass | +| 4 | Self-signed TLS cert auto-generated | When no TLS_CERT_FILE/TLS_KEY_FILE are provided, a self-signed cert is generated and persisted | Start container without TLS vars; `docker exec test-postfix ls -l /etc/postfix/ssl/` | Files `snakeoil.pem` and `snakeoil.key` present; key mode 600 | Both files created on first start; `postconf smtpd_tls_cert_file` returned the correct path | ✅ Pass | +| 5 | Port 25 reachable | SMTP port 25 is accessible from outside the container and returns a valid Postfix banner | `telnet 127.0.0.1 25` | `220 mx1.example.com ESMTP Postfix` banner | Banner received as expected | ✅ Pass | +| 6 | Accepted domain → mail relayed | Mail sent to a recipient whose domain is in `relay_domain` is forwarded to the downstream SMTP target (MailHog) | `swaks --to user@example.com --server 127.0.0.1 --port 25`; check MailHog API | `250 2.0.0 Ok: queued` from Postfix; message appears in `GET /api/v2/messages` | Message delivered to MailHog and visible in the web UI | ✅ Pass | +| 7 | Unknown domain → relay denied | Mail sent to a domain not in `relay_domain` is rejected with relay access denied | `swaks --to user@notlisted.org --server 127.0.0.1 --port 25` | `554 5.7.1 Relay access denied` | Postfix returned `554 5.7.1 : Relay access denied` | ✅ Pass | +| 8 | PostgreSQL lookup failure — chroot DNS bug | pgsql-backed `relay_domains`/`transport_maps` lookups work correctly when Postgres is referenced by Docker container name | Send mail to `user@example.com` with `POSTGRES_HOST=test-postgres` (container name, not IP) | `250 Ok: queued`; no `451 Temporary lookup failure` in logs | **Without fix:** intermittent `451 4.3.0 Temporary lookup failure` — chrooted child processes (smtpd, trivial-rewrite) could not resolve `test-postgres` because they lack `/etc/resolv.conf` inside the chroot jail. **Fix applied:** added `postconf -F '*/*/chroot = n'` in `docker-entrypoint.sh` before `postfix start-fg`. After fix: lookups succeed consistently. | 🔧 Fixed | +| 9 | Container restart behavior | Config is re-rendered correctly on restart; pre-existing self-signed cert is reused without re-generation | `docker restart test-postfix`; `docker logs test-postfix` | No re-generation message for TLS cert; `main.cf` and pgsql maps fully substituted; Postfix starts without errors | Cert reuse message absent (new cert would log openssl output); Postfix started cleanly on restart | ✅ Pass | +| 10 | No credential leakage in logs | Postgres password does not appear anywhere in `docker logs` output | `docker logs test-postfix 2>&1 \| grep testpassword` | No output (password not printed) | No lines containing the password; envsubst substitution into config files is silent | ✅ Pass |