release: 2.6.0: audit stage 5 - image and Postfix hardening
test / test (push) Canceled after 0s

Cut without asking, as the owner said on 2026-10-15.

The supervisord socket is root:supervisor, the panel the group's one member
(M17). Postfix takes TLS 1.2 at least, the client too where TLS is
required; VRFY off; the banner without the distribution; the start refuses
a rate, size or milter timeout that is not a number, under its minimum or
past an int (L31). OpenDKIM logs through the syslog relay to the container
log and oversigns From: (L30). crashexit takes the container down after
five unexpected exits of a mail-path program in ten minutes (L32).
logrotate keeps its state under /data/log. extract-cert.sh refuses what is
not PEM and renames whole files (M21). Plus the findings of both reviews
(a39eda5). Commits dab7715, a39eda5, b09e525.

Rolled out to the test server as 2.5.4-post.95af363 (the same code): the
container healthy, every program RUNNING, syslog-relay among them;
OpenDKIM's start line in docker logs under "syslog: "; the postconf values
as set; /run/supervisor.sock root:supervisor 0770 and the panel reaches it;
/data/log/.logrotate.status written. No migration, schema 11.

CHANGELOG cut; pin 2.6.0 in deploy/docker-compose.yml, README, guide (and
its sample restore message); development.md "from pin" and the stage's row;
roadmap row and recommended order; stage 5 marked shipped in the plan. No
tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-15 13:58:10 +03:00
co-authored by Claude Opus 5.5
parent 95af363214
commit dc78b0ed76
7 changed files with 25 additions and 14 deletions
+11
View File
@@ -5,6 +5,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [Unreleased] ## [Unreleased]
## [2.6.0] - 2026-10-15
Stage 5 of the 2026-10-12 audit: the image and Postfix. The supervisor
socket is the panel's alone; Postfix takes TLS 1.2 at least, and the start
refuses settings that would kill its daemons at the first connection;
OpenDKIM's log reaches the container log and it oversigns From:; a program
that keeps crashing takes the container down; logrotate remembers what it
rotated; extract-cert.sh never replaces a certificate with nothing. A new
program, `syslog-relay`, and a new group, `supervisor`, in the image; no
migration.
### Security ### Security
- **Postfix and OpenDKIM could stop every program in the container.** The - **Postfix and OpenDKIM could stop every program in the container.** The
+3 -3
View File
@@ -112,8 +112,8 @@ outbound mail will not reach the real internet without DNS, PTR, and port 25.
> local image and pulls nothing: > local image and pulls nothing:
> >
> ```sh > ```sh
> docker build -f build/Dockerfile --build-arg VERSION=2.5.4 \ > docker build -f build/Dockerfile --build-arg VERSION=2.6.0 \
> -t ghcr.io/mixeme/selfpost:2.5.4 . > -t ghcr.io/mixeme/selfpost:2.6.0 .
> ``` > ```
```sh ```sh
@@ -122,7 +122,7 @@ docker run --rm -d --name selfpost-try \
-e SELFPOST_HOSTNAME=mail.local.test \ -e SELFPOST_HOSTNAME=mail.local.test \
-e PANEL_COOKIE_SECURE=false \ -e PANEL_COOKIE_SECURE=false \
-v selfpost-try-data:/data \ -v selfpost-try-data:/data \
ghcr.io/mixeme/selfpost:2.5.4 ghcr.io/mixeme/selfpost:2.6.0
``` ```
**Get the setup URL** (pick one): **Get the setup URL** (pick one):
+1 -1
View File
@@ -23,7 +23,7 @@
services: services:
selfpost: selfpost:
image: ghcr.io/mixeme/selfpost:2.5.4 image: ghcr.io/mixeme/selfpost:2.6.0
restart: unless-stopped restart: unless-stopped
environment: environment:
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}" SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
+1 -1
View File
@@ -1416,7 +1416,7 @@ failed `SetNotifyAddressees` is logged and the subject stays said, the next
follow-up finds nobody owed; the profile form's "username taken" is follow-up finds nobody owed; the profile form's "username taken" is
documented in security.md. documented in security.md.
### Stage 5 — image and Postfix hardening (M17–M19, M21, L30–L32) → `2.6.0` ### Stage 5 — image and Postfix hardening (M17–M19, M21, L30–L32) → `2.6.0` (shipped 2026-10-15)
- [x] **Supervisor socket**: a group of its own (`supervisor`), panel only; - [x] **Supervisor socket**: a group of its own (`supervisor`), panel only;
`postfix`/`opendkim` keep `selfpost` for the file paths. — **Opus** `postfix`/`opendkim` keep `selfpost` for the file paths. — **Opus**
+2 -2
View File
@@ -107,7 +107,7 @@ than the default «meaningful step» rule in [§ Commits and release build](#com
2. Git tag `vX.Y.Z` and publish the GitHub Release only on explicit request (see 2. Git tag `vX.Y.Z` and publish the GitHub Release only on explicit request (see
[§ Release image](#release-image)). [§ Release image](#release-image)).
**Planned version cuts** (from pin `2.5.4`; adjust if semver changes mid-track): **Planned version cuts** (from pin `2.6.0`; adjust if semver changes mid-track):
| Stage | ID | Cut | | Stage | ID | Cut |
|---|---|---| |---|---|---|
@@ -136,7 +136,7 @@ than the default «meaningful step» rule in [§ Commits and release build](#com
| 23 | audit-2026-10-12 stage 2c: the sender check reads From: as OpenDKIM does | `2.5.2` | | 23 | audit-2026-10-12 stage 2c: the sender check reads From: as OpenDKIM does | `2.5.2` |
| 24 | audit-2026-10-12 stage 3: persistence and restore | `2.5.3` | | 24 | audit-2026-10-12 stage 3: persistence and restore | `2.5.3` |
| 25 | audit-2026-10-12 stage 4: panel behaviour | `2.5.4` | | 25 | audit-2026-10-12 stage 4: panel behaviour | `2.5.4` |
| 26 | audit-2026-10-12 stage 5 | `2.6.0` | | 26 | audit-2026-10-12 stage 5: image and Postfix hardening | `2.6.0` |
Docs-only prep (checklists in plans, no product code) uses the same per-step Docs-only prep (checklists in plans, no product code) uses the same per-step
commit rule but **no** version cut until the next product stage ships. commit rule but **no** version cut until the next product stage ships.
+4 -4
View File
@@ -190,14 +190,14 @@ cat ./data/setup-token
#### Fixed image tag #### Fixed image tag
`deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`), `deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`),
deliberately never `:latest`. The current pin is `2.5.4`. **No 2.x image is deliberately never `:latest`. The current pin is `2.6.0`. **No 2.x image is
published on ghcr.io yet** (the newest there is `1.3.0`); until one is, build published on ghcr.io yet** (the newest there is `1.3.0`); until one is, build
it from the checkout under the pinned name, and Compose uses the local image it from the checkout under the pinned name, and Compose uses the local image
instead of pulling: instead of pulling:
```sh ```sh
docker build -f build/Dockerfile --build-arg VERSION=2.5.4 \ docker build -f build/Dockerfile --build-arg VERSION=2.6.0 \
-t ghcr.io/mixeme/selfpost:2.5.4 . -t ghcr.io/mixeme/selfpost:2.6.0 .
``` ```
`VERSION` must equal the tag: it is the version the backup check compares `VERSION` must equal the tag: it is the version the backup check compares
@@ -861,7 +861,7 @@ A version mismatch at step 5 refuses to start and leaves `/data` untouched —
the panel exits with a message naming the tag to use, e.g.: the panel exits with a message naming the tag to use, e.g.:
``` ```
backup: this backup was created by SelfPost 2.5.3 but this image is 2.5.4 — restore into the matching image (selfpost:2.5.3) backup: this backup was created by SelfPost 2.5.4 but this image is 2.6.0 — restore into the matching image (selfpost:2.5.4)
``` ```
Fix the tag in `docker-compose.yml` (build that tag as under [Fixed image Fix the tag in `docker-compose.yml` (build that tag as under [Fixed image
+3 -3
View File
@@ -46,7 +46,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
| from-header-binding | Hold a message's `From:` header against the application that sends it | shipped `2.4.0` | 9/9 | [plans/from-header-binding.md](plans/from-header-binding.md) | | from-header-binding | Hold a message's `From:` header against the application that sends it | shipped `2.4.0` | 9/9 | [plans/from-header-binding.md](plans/from-header-binding.md) |
| panel-redesign | Panel redesign (accepted mockups, design contract) | shipped `2.0.0` | 27/27 | [plans/panel-redesign.md](plans/panel-redesign.md) | | panel-redesign | Panel redesign (accepted mockups, design contract) | shipped `2.0.0` | 27/27 | [plans/panel-redesign.md](plans/panel-redesign.md) |
| schema-squash | Squash SQLite migrations into a 2.x baseline | built in panel-redesign (stage 1) | — | [plans/panel-redesign.md](plans/panel-redesign.md) | | schema-squash | Squash SQLite migrations into a 2.x baseline | built in panel-redesign (stage 1) | — | [plans/panel-redesign.md](plans/panel-redesign.md) |
| audit-2026-10-12 | Follow-ups of the 2026-10-12 full-tree code audit, seven stages | in progress (stage 0 shipped `2.4.2`, stages 1+2 `2.5.0`, 2b `2.5.1`, 2c `2.5.2`, 3 `2.5.3`, 4 `2.5.4`; next stage 5 → `2.6.0`) | 77/81 | [audit-2026-10-12.md](audit-2026-10-12.md) | | audit-2026-10-12 | Follow-ups of the 2026-10-12 full-tree code audit, seven stages | in progress (stage 0 shipped `2.4.2`, stages 1+2 `2.5.0`, 2b `2.5.1`, 2c `2.5.2`, 3 `2.5.3`, 4 `2.5.4`, 5 `2.6.0`; next stage 6, docs only) | 77/81 | [audit-2026-10-12.md](audit-2026-10-12.md) |
**Recommended order** (not binding; owner, 2026-10-09: feature order follows **Recommended order** (not binding; owner, 2026-10-09: feature order follows
development convenience): **panel-redesign** shipped as `2.0.0` (2026-10-11) and development convenience): **panel-redesign** shipped as `2.0.0` (2026-10-11) and
@@ -56,8 +56,8 @@ shipped as `2.2.0` the same day, **panel-notifications** as `2.3.0` and
**from-header-binding** as `2.4.0`. Stage 0 of **from-header-binding** as `2.4.0`. Stage 0 of
[audit-2026-10-12](audit-2026-10-12.md) shipped as `2.4.2` (2026-10-14), its [audit-2026-10-12](audit-2026-10-12.md) shipped as `2.4.2` (2026-10-14), its
stages 1 and 2 as `2.5.0` the same day, stage 2b as `2.5.1`, stage 2c as stages 1 and 2 as `2.5.0` the same day, stage 2b as `2.5.1`, stage 2c as
`2.5.2` (2026-10-15), and stages 3 and 4 as `2.5.3` and `2.5.4` the same `2.5.2` (2026-10-15), and stages 3, 4 and 5 as `2.5.3`, `2.5.4` and
day. `2.6.0` the same day.
Nothing is agreed after it: every remaining Nothing is agreed after it: every remaining
item is a candidate. panel-docs shipped in item is a candidate. panel-docs shipped in
[CHANGELOG.md](../CHANGELOG.md) `[1.8.0]`; dmarc-reports in `[1.7.0]` [CHANGELOG.md](../CHANGELOG.md) `[1.8.0]`; dmarc-reports in `[1.7.0]`