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>
This commit is contained in:
@@ -116,14 +116,14 @@ func TestCheckCertificate(t *testing.T) {
|
||||
|
||||
func TestCheckSocket(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
sock := filepath.Join(dir, "opendkim.sock")
|
||||
sock := filepath.Join(dir, "sign.sock")
|
||||
l, err := net.Listen("unix", sock)
|
||||
if err != nil {
|
||||
t.Skipf("unix sockets unavailable here: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
if got := CheckSocket("OpenDKIM", sock, true); got.Status != StatusOK || !got.Present {
|
||||
if got := CheckSocket("dkim-signer", sock, true); got.Status != StatusOK || !got.Present {
|
||||
t.Errorf("live socket: status %q present=%v", got.Status, got.Present)
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ func TestCheckSocket(t *testing.T) {
|
||||
if got := CheckSocket("journal", missing, false); got.Status != StatusWarn {
|
||||
t.Errorf("missing optional socket: status %q", got.Status)
|
||||
}
|
||||
if got := CheckSocket("OpenDKIM", missing, true); got.Status != StatusError {
|
||||
if got := CheckSocket("dkim-signer", missing, true); got.Status != StatusError {
|
||||
t.Errorf("missing required socket: status %q", got.Status)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func TestCheckSocket(t *testing.T) {
|
||||
if err := os.WriteFile(plain, nil, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := CheckSocket("OpenDKIM", plain, true); got.Status != StatusError || got.Present {
|
||||
if got := CheckSocket("dkim-signer", plain, true); got.Status != StatusError || got.Present {
|
||||
t.Errorf("regular file in place of a socket: status %q present=%v", got.Status, got.Present)
|
||||
}
|
||||
}
|
||||
@@ -170,8 +170,7 @@ func writeCert(t *testing.T, path, cn string, validFor time.Duration) {
|
||||
|
||||
func TestLivenessFromParsedProcesses(t *testing.T) {
|
||||
// Composed, in the columns of TestParseProcesses.
|
||||
allRunning := `opendkim RUNNING pid 21, uptime 0:04:10
|
||||
panel RUNNING pid 22, uptime 0:04:09
|
||||
allRunning := `panel RUNNING pid 22, uptime 0:04:09
|
||||
postfix RUNNING pid 23, uptime 0:04:08
|
||||
postfix-reload STOPPED Not started
|
||||
`
|
||||
@@ -182,8 +181,7 @@ postfix-reload STOPPED Not started
|
||||
}
|
||||
}
|
||||
|
||||
postfixDead := `opendkim RUNNING pid 21, uptime 0:04:10
|
||||
panel RUNNING pid 22, uptime 0:04:09
|
||||
postfixDead := `panel RUNNING pid 22, uptime 0:04:09
|
||||
postfix FATAL Exited too quickly
|
||||
`
|
||||
procs = parseProcesses(postfixDead)
|
||||
|
||||
@@ -8,14 +8,13 @@ import (
|
||||
// mailPathPrograms are the supervised processes whose absence means the
|
||||
// container should not report healthy to orchestrators.
|
||||
var mailPathPrograms = map[string]bool{
|
||||
"opendkim": true,
|
||||
"panel": true,
|
||||
"postfix": true,
|
||||
"panel": true,
|
||||
"postfix": true,
|
||||
}
|
||||
|
||||
// Liveness reports whether the mail path is healthy enough for container
|
||||
// probes. It requires opendkim, panel, and postfix to be RUNNING under
|
||||
// supervisord.
|
||||
// probes. It requires panel - whose roles include the DKIM signer - and
|
||||
// postfix to be RUNNING under supervisord.
|
||||
func Liveness() error {
|
||||
procs, err := Processes()
|
||||
if err != nil {
|
||||
|
||||
@@ -146,7 +146,7 @@ func TestCheckMilterInet6(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A socket file whose listener is gone - a killed OpenDKIM or panel leaves
|
||||
// A socket file whose listener is gone - a killed panel leaves
|
||||
// one (unix(7)) - is not a listener, though the file is there (A-38).
|
||||
func TestStaleSocketFileIsNotListening(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -163,8 +163,8 @@ func TestStaleSocketFileIsNotListening(t *testing.T) {
|
||||
t.Fatalf("the socket file should outlive its listener: %v", err)
|
||||
}
|
||||
|
||||
got := CheckSocket("OpenDKIM", sock, true)
|
||||
if got.Present || got.Status != StatusError || !strings.HasPrefix(got.Detail, "Nothing listens on the OpenDKIM socket") {
|
||||
got := CheckSocket("dkim-signer", sock, true)
|
||||
if got.Present || got.Status != StatusError || !strings.HasPrefix(got.Detail, "Nothing listens on the dkim-signer socket") {
|
||||
t.Errorf("stale required socket: %+v", got)
|
||||
}
|
||||
if got := CheckSocket("send-log", sock, false); got.Status != StatusWarn {
|
||||
|
||||
@@ -21,9 +21,10 @@ type Socket struct {
|
||||
Detail string
|
||||
}
|
||||
|
||||
// CheckSocket checks a milter socket. required distinguishes the two milters:
|
||||
// Postfix attaches OpenDKIM with default_action=tempfail, so a missing socket
|
||||
// stops mail leaving the server, while the journal-milter is attached with
|
||||
// CheckSocket checks a milter socket. required distinguishes the milters:
|
||||
// Postfix attaches the DKIM signer and the sender check with
|
||||
// default_action=tempfail, so a missing socket stops mail leaving the server,
|
||||
// while the journal-milter is attached with
|
||||
// default_action=accept and fails open — mail still goes out, only the send
|
||||
// log stops being written (build/postfix-config.sh smtpd_milters; Postfix
|
||||
// 3.10.13, src/milter/milter8.c, milter8_comm_error(): tempfail answers "451
|
||||
@@ -32,13 +33,9 @@ type Socket struct {
|
||||
// The socket is dialled, as Postfix will dial it; a file is not a listener.
|
||||
// A unix socket's file outlives the process that bound it unless that process
|
||||
// unlinks it, and a connect() to it then fails with ECONNREFUSED (unix(7)), so
|
||||
// a killed OpenDKIM or panel leaves a file that refuses every connection -
|
||||
// assumptions A-38. The connection is closed before a byte is sent, which
|
||||
// none of the three milters logs: OpenDKIM's libmilter reads the EOF as
|
||||
// SMFIC_EOF and ends the session without a line (libmilter 8.18.1,
|
||||
// libmilter/comm.c, mi_rd_cmd(); engine.c, mi_engine()), and OpenDKIM's
|
||||
// mlfi_close() has no connection to clean up (opendkim 2.11.0~beta2,
|
||||
// opendkim/opendkim.c); go-milter v0.4.1 returns quietly on io.EOF
|
||||
// a killed panel leaves a file that refuses every connection - assumptions
|
||||
// A-38. The connection is closed before a byte is sent, which none of the
|
||||
// panel's milters logs: go-milter v0.4.1 returns quietly on io.EOF
|
||||
// (session.go, HandleMilterCommands()).
|
||||
func CheckSocket(name, path string, required bool) Socket {
|
||||
s := Socket{Name: name, Path: path}
|
||||
|
||||
Reference in New Issue
Block a user