test / test (push) Waiting to run
The panel's signer is the only one. Gone: the opendkim package and its
configuration, the syslog-relay and opendkim programs, /dev/log and
/run/opendkim, the reload by USR1 through supervisord
(internal/supervisor/supervisor.go), internal/syslogrelay, the port of
OpenDKIM's From: parser (internal/milter/mailparse.go and its probe,
harness and test/upstream scripts), OPENDKIM_SOCKET and OPENDKIM_DIR.
Keys: internal/domain/keys.go (Keys, NewKeys, DKIM_DIR=/data/dkim) keeps
the keys and writes a selector table, /data/dkim/selectors ("<name>
<selector>"), which the signer reads in place of the KeyTable - it reads
no database, as the sender check reads none. The table is written before
the signer serves (EnsureServerSigned moved there from the notifier).
Keys and table 0600, directories 0700, the panel's. The entrypoint copies
/data/opendkim/keys once, as the panel user, into /data/dkim.new renamed
when whole, and stops the start if the copy fails; /data/opendkim is left
for a rollback to 2.7.x.
Sender check: one RFC 5322 address as a mail program reads it, held
against the bindings; the 256-byte refusal and the texts naming OpenDKIM
go. Allowed() folded the domain through strings.ToLower, which makes the
Kelvin sign a "k" - the comparison with OpenDKIM's reading had hidden it;
it folds ASCII only now.
Health: two dkim-signer rows (applications, notifications) in Milter
sockets, opendkim out of the liveness set; the card no longer takes any
row with a note for the spam filter. Texts as in the design commit.
KILL stays in deploy/docker-compose.yml: measured on the test server,
root without it cannot signal a process of another uid - supervisord
stopping the panel, Postfix's master its children. The plan's "only use
was the reload" was wrong; the comment now gives the real uses.
e2e: one signature, the panel's, verified by dkimpy; the /data/dkim modes;
the OpenDKIM log and process checks, testNeverSignedForAnother and the
cited opendkim, libopendkim11 and libmilter1.0.1 go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
124 lines
5.4 KiB
YAML
124 lines
5.4 KiB
YAML
|
|
# Hermetic e2e stand override for ../../deploy/docker-compose.yml (plan C.4).
|
|
#
|
|
# Applied as a second -f on top of the SHIPPED compose file, not a separate
|
|
# test compose — the whole point is to exercise cap_drop/cap_add/
|
|
# no-new-privileges exactly as documented, not a laxer stand-in. Run only via
|
|
# `make e2e`, which also sets --project-directory to a scratch stage
|
|
# directory so the base file's relative ./data and ./certs bind mounts land
|
|
# there instead of next to a real deployment, and -p selfpost-e2e so the
|
|
# containers/network never collide with a prod stack on the same host.
|
|
#
|
|
# `ports:` and `networks:` use the compose-spec `!override` merge tag to fully
|
|
# replace the base file's list rather than concatenate with it (the default
|
|
# merge behaviour for sequences) — otherwise the real 465/587/8080 would stay
|
|
# published here too and collide with a prod container on the same host.
|
|
# (docs.docker.com/reference/compose-file/merge/, "!override": "fully replace
|
|
# an attribute, bypassing the standard merge rules". The page names no
|
|
# minimum Compose version; the stand's own Compose is what each run uses.)
|
|
|
|
services:
|
|
selfpost:
|
|
# Crash-looping with unless-stopped makes `compose exec` fail with
|
|
# "Container is restarting" and hides the entrypoint/supervisord exit
|
|
# reason; keep the stand exited so logs stay attached to one attempt.
|
|
restart: "no"
|
|
build:
|
|
# Resolved relative to --project-directory (.stage), NOT this file's own
|
|
# directory: with --project-directory, build.context and bind-mount
|
|
# volume paths alike resolve against it - which is why ./data, ./certs
|
|
# and the ./dns-stage and ./mail-stage below land in .stage
|
|
# (docs.docker.com/compose/how-tos/multiple-compose-files/merge/, "Paths
|
|
# are evaluated relative to the base file"; assumptions C-K8).
|
|
context: ../../..
|
|
dockerfile: build/Dockerfile
|
|
args:
|
|
# release.yml sets SELFPOST_E2E_VERSION to the release's X.Y.Z, so the
|
|
# image this build tags selfpost:e2e - the one release.yml then pushes -
|
|
# carries the release's version, not "e2e". A local `make e2e` leaves it
|
|
# unset. The suite checks the stamp (image_stamped_with_version).
|
|
VERSION: ${SELFPOST_E2E_VERSION:-e2e}
|
|
image: selfpost:e2e
|
|
volumes:
|
|
# Where certs/ links lead when it is laid out as the guide has it with
|
|
# certbot (certbot_layout_followed): certbot's directory, mounted at the
|
|
# same path as on a host. Empty until that test fills it. Added to the
|
|
# base file's volumes (a new target).
|
|
- ./letsencrypt:/etc/letsencrypt:ro
|
|
environment:
|
|
SELFPOST_HOSTNAME: mail.e2e.test
|
|
# Plain HTTP panel for the test client — no reverse proxy in this stand.
|
|
PANEL_COOKIE_SECURE: "false"
|
|
# The panel's deliverability checks query resolvers directly rather than
|
|
# the container's own (see internal/dnscheck), so the fake zone has to be
|
|
# named here too — `dns:` below only redirects the system resolver.
|
|
SELFPOST_DNS_RESOLVERS: 10.77.0.10:53
|
|
# High enough that the whole suite's other ~10 sends (positive path,
|
|
# level-2 test, the various rejected attempts) never trip it by
|
|
# accident — only the dedicated level-1 test (negative check 4, which
|
|
# loops well past this count) is meant to hit it.
|
|
RATE_LIMIT_MESSAGES_PER_IP: "50"
|
|
RATE_LIMIT_WINDOW_SECONDS: "60"
|
|
# cert-reload compares the mounted certificate with Postfix's copy this
|
|
# often, so renewed_certificate_picked_up does not wait a day. It
|
|
# reloads Postfix only when the certificate changed.
|
|
TLS_RELOAD_INTERVAL_SECONDS: "2"
|
|
# The wrapper gives up on the milter sockets after this long, and
|
|
# program postfix's startsecs is this plus ten (build/entrypoint.sh):
|
|
# supervision_stops_postfix_and_gives_up waits for one give-up and
|
|
# every start for startsecs, so the stand keeps both short.
|
|
MILTER_WAIT_TIMEOUT: "10"
|
|
ports: !override
|
|
- "20465:465"
|
|
- "20587:587"
|
|
- "20025:25"
|
|
- "127.0.0.1:20080:8080"
|
|
dns:
|
|
- 10.77.0.10
|
|
networks: !override
|
|
e2e:
|
|
depends_on:
|
|
- coredns
|
|
- sink
|
|
|
|
# Fake authoritative DNS for e2e.test (plan C.4): serves the DKIM/host
|
|
# records the test harness publishes, so Postfix's outbound MX lookup for
|
|
# the sink and the panel's own dnscheck resolve inside the isolated network
|
|
# instead of hitting the real internet.
|
|
#
|
|
# Pinned to a release tag rather than `latest`, so a CoreDNS upload cannot
|
|
# change what the release gate runs against between two runs of the same
|
|
# commit. Not a digest: the tag is a multi-arch manifest and the stand has to
|
|
# come up on arm64 developer machines as well as amd64 CI.
|
|
coredns:
|
|
image: coredns/coredns:1.14.6
|
|
command: ["-conf", "/dns/Corefile"]
|
|
volumes:
|
|
- ./dns-stage:/dns
|
|
networks:
|
|
e2e:
|
|
ipv4_address: 10.77.0.10
|
|
ports:
|
|
- "20053:53/udp"
|
|
|
|
# Sink-MX: accepts any inbound mail and dumps each transaction to its own
|
|
# file under ./mail-stage, which the harness reads directly to check the
|
|
# DKIM-Signature header the panel's signer produced.
|
|
sink:
|
|
build:
|
|
context: ../sink
|
|
volumes:
|
|
- ./mail-stage:/mail
|
|
# dkimverify looks the DKIM keys up in the fake zone.
|
|
dns:
|
|
- 10.77.0.10
|
|
networks:
|
|
e2e:
|
|
ipv4_address: 10.77.0.11
|
|
|
|
networks:
|
|
e2e:
|
|
ipam:
|
|
config:
|
|
- subnet: 10.77.0.0/24
|