audit 5: the supervisor socket the panel's alone, OpenDKIM logs and oversigns From
test / test (push) Canceled after 0s
test / test (push) Canceled after 0s
M17: the supervisord control socket is root:supervisor 0770, a group whose one member is the panel; OpenDKIM and Postfix keep `selfpost` for the files under /data and no longer reach the socket. L30, read in OpenDKIM 2.11.0~beta2's source first: it logs through syslog(3) only and has no Logfile option, and nothing in the container served /dev/log. internal/syslogrelay (the panel binary with -syslog-relay=/run/syslog/log, program syslog-relay before OpenDKIM, /dev/log a link to its socket) passes the datagrams to stdout, error and worse marked. opendkim.conf: Syslog yes, Canonicalization relaxed/simple, OversignHeaders From (RFC 6376 § 3.4, § 8.15). e2e: OpenDKIM's start line reaches the container log; every program RUNNING; DKIM verifies. Tested on the test server: go vet + go test of 26 packages, make e2e (all pass, 626 s) on this tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+7
-2
@@ -70,8 +70,8 @@ RUN useradd --system --no-create-home --shell /usr/sbin/nologin panel
|
|||||||
# sender map, while OpenDKIM and Postfix (different users) must read them.
|
# sender map, while OpenDKIM and Postfix (different users) must read them.
|
||||||
# Membership in this group — plus setgid dirs under /data (set up in
|
# Membership in this group — plus setgid dirs under /data (set up in
|
||||||
# entrypoint.sh) — lets OpenDKIM read the panel-owned keys and lets Postfix read
|
# entrypoint.sh) — lets OpenDKIM read the panel-owned keys and lets Postfix read
|
||||||
# the sasldb2/sender map, and lets the panel reach the supervisor control socket
|
# the sasldb2/sender map. (The supervisor control socket has a group of its
|
||||||
# to signal OpenDKIM/Postfix reloads without any process running as root.
|
# own, `supervisor`, below.)
|
||||||
# The id is fixed, and it is the one the group has always had: an operator
|
# The id is fixed, and it is the one the group has always had: an operator
|
||||||
# gives the TLS private key on the host to this group by number, so that the
|
# gives the TLS private key on the host to this group by number, so that the
|
||||||
# panel can put it into a full backup (guide § Taking a full backup).
|
# panel can put it into a full backup (guide § Taking a full backup).
|
||||||
@@ -97,6 +97,11 @@ RUN groupadd --system --gid 998 selfpost \
|
|||||||
# root:$setgid_group 2755. Linux, Documentation/userspace-api/no_new_privs.rst:
|
# root:$setgid_group 2755. Linux, Documentation/userspace-api/no_new_privs.rst:
|
||||||
# with no_new_privs, execve() no longer changes the gid for a setgid file.)
|
# with no_new_privs, execve() no longer changes the gid for a setgid file.)
|
||||||
RUN usermod -aG postdrop panel
|
RUN usermod -aG postdrop panel
|
||||||
|
# The supervisord control socket's group (supervisord.conf, [unix_http_server]):
|
||||||
|
# the panel alone - not OpenDKIM's or Postfix's users, which share `selfpost`
|
||||||
|
# for the files under /data.
|
||||||
|
RUN groupadd --system supervisor \
|
||||||
|
&& usermod -aG supervisor panel
|
||||||
|
|
||||||
# Runtime directories: milter sockets and the consolidated persistent root.
|
# Runtime directories: milter sockets and the consolidated persistent root.
|
||||||
RUN mkdir -p /run/opendkim /run/selfpost /data \
|
RUN mkdir -p /run/opendkim /run/selfpost /data \
|
||||||
|
|||||||
@@ -180,6 +180,17 @@ chmod 2750 /run/opendkim /run/selfpost
|
|||||||
# (test(1), -S: true for any socket file, whether or not anything listens.)
|
# (test(1), -S: true for any socket file, whether or not anything listens.)
|
||||||
rm -f /run/opendkim/*.sock /run/selfpost/*.sock /run/selfpost/*.alive
|
rm -f /run/opendkim/*.sock /run/selfpost/*.sock /run/selfpost/*.alive
|
||||||
|
|
||||||
|
# The container's /dev/log: a link to the socket the syslog-relay program (the
|
||||||
|
# panel's binary, as `panel`) listens on, so what OpenDKIM logs through
|
||||||
|
# syslog(3) reaches the container's log (build/supervisord.conf; audit
|
||||||
|
# 2026-10-12, L30). The directory is the relay's and reachable by all: the
|
||||||
|
# socket is the container's local log, as /dev/log is on a host.
|
||||||
|
mkdir -p /run/syslog
|
||||||
|
chown panel:panel /run/syslog
|
||||||
|
chmod 0755 /run/syslog
|
||||||
|
rm -f /run/syslog/log
|
||||||
|
ln -sfn /run/syslog/log /dev/log
|
||||||
|
|
||||||
# Generate the outbound-relay Postfix configuration from the environment (spec
|
# Generate the outbound-relay Postfix configuration from the environment (spec
|
||||||
# 5). Kept out of the image build so cert paths, rate limits, hostname and the
|
# 5). Kept out of the image build so cert paths, rate limits, hostname and the
|
||||||
# optional 587 service are all driven by env at run time, and re-derived on every
|
# optional 587 service are all driven by env at run time, and re-derived on every
|
||||||
|
|||||||
+16
-1
@@ -14,10 +14,25 @@
|
|||||||
# dkimf_db_open(): a file or refile table is read line by line; an empty file
|
# dkimf_db_open(): a file or refile table is read line by line; an empty file
|
||||||
# is an empty table, not an error.)
|
# is an empty table, not an error.)
|
||||||
|
|
||||||
Syslog no
|
# Logged through syslog(3) to /dev/log, which the syslog-relay program passes
|
||||||
|
# to the container's log (build/supervisord.conf; audit 2026-10-12, L30). The
|
||||||
|
# errors are what is wanted - a key that cannot be read, a table that cannot
|
||||||
|
# be opened; a line per signed message (SyslogSuccess) is not.
|
||||||
|
Syslog yes
|
||||||
UMask 007
|
UMask 007
|
||||||
Mode s
|
Mode s
|
||||||
|
|
||||||
|
# relaxed/simple: the header fields relaxed, so a relay that refolds or
|
||||||
|
# respaces one does not break the signature; the body simple, as signed
|
||||||
|
# (opendkim.conf.5.in, Canonicalization: header/body, the default
|
||||||
|
# simple/simple; RFC 6376 § 3.4). OversignHeaders From lists From: in h= once
|
||||||
|
# more than it is present, so a From: added on the way fails the signature
|
||||||
|
# instead of being shown above the signed one (opendkim.conf.5.in,
|
||||||
|
# OversignHeaders; RFC 6376 § 8.15) - the replay 2.4.0's sender check is
|
||||||
|
# about.
|
||||||
|
Canonicalization relaxed/simple
|
||||||
|
OversignHeaders From
|
||||||
|
|
||||||
# SigningTable uses refile: so the "*@example.com" left-hand patterns match any
|
# SigningTable uses refile: so the "*@example.com" left-hand patterns match any
|
||||||
# local-part for a domain; KeyTable maps each domain to its key and selector.
|
# local-part for a domain; KeyTable maps each domain to its key and selector.
|
||||||
# (opendkim/opendkim.conf.5.in, SigningTable: with "refile" the keys are
|
# (opendkim/opendkim.conf.5.in, SigningTable: with "refile" the keys are
|
||||||
|
|||||||
+29
-9
@@ -26,17 +26,20 @@ logfile_maxbytes=0
|
|||||||
pidfile=/run/supervisord.pid
|
pidfile=/run/supervisord.pid
|
||||||
loglevel=info
|
loglevel=info
|
||||||
|
|
||||||
; The control socket is reachable by the shared `selfpost` group so the panel
|
; The control socket is reachable by the `supervisor` group, whose one member
|
||||||
; (running as the unprivileged `panel` user) can ask supervisord to send
|
; is the panel (the unprivileged `panel` user): it asks supervisord to signal
|
||||||
; OpenDKIM a reload signal after it rewrites the DKIM tables. It is still
|
; OpenDKIM after it rewrites the DKIM tables, to run the Postfix reload, and
|
||||||
; unreachable by world; only root and the two SelfPost services are in the
|
; for the processes' state. Whoever reaches the socket can stop, start or
|
||||||
; group. No HTTP auth is configured because access is gated by socket
|
; restart any program, so it is not the shared `selfpost` group's: OpenDKIM
|
||||||
; permissions, not the network. (supervisor 4.2.5, docs/configuration.rst,
|
; and Postfix are in that one for the files under /data, and a fault in either
|
||||||
; [unix_http_server]: chmod and chown set the socket's mode and owner; without
|
; must not reach every other program (audit 2026-10-12, stage 5). Not
|
||||||
; username and password no authentication is asked.)
|
; reachable by world. No HTTP auth is configured because access is gated by
|
||||||
|
; socket permissions, not the network. (supervisor 4.2.5,
|
||||||
|
; docs/configuration.rst, [unix_http_server]: chmod and chown set the socket's
|
||||||
|
; mode and owner; without username and password no authentication is asked.)
|
||||||
[unix_http_server]
|
[unix_http_server]
|
||||||
file=/run/supervisor.sock
|
file=/run/supervisor.sock
|
||||||
chown=root:selfpost
|
chown=root:supervisor
|
||||||
chmod=0770
|
chmod=0770
|
||||||
|
|
||||||
[rpcinterface:supervisor]
|
[rpcinterface:supervisor]
|
||||||
@@ -45,6 +48,23 @@ supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
|||||||
[supervisorctl]
|
[supervisorctl]
|
||||||
serverurl=unix:///run/supervisor.sock
|
serverurl=unix:///run/supervisor.sock
|
||||||
|
|
||||||
|
; The container's /dev/log (a link to the socket below, build/entrypoint.sh):
|
||||||
|
; what OpenDKIM - which logs through syslog(3) and nothing else - sends there
|
||||||
|
; comes out on stdout, into the container's log (internal/syslogrelay; audit
|
||||||
|
; 2026-10-12, L30). Before OpenDKIM, so its first lines are not lost; a
|
||||||
|
; message sent while nothing listens is dropped by syslog(3), not queued.
|
||||||
|
[program:syslog-relay]
|
||||||
|
command=/usr/local/bin/panel -syslog-relay=/run/syslog/log
|
||||||
|
user=panel
|
||||||
|
priority=50
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
[program:opendkim]
|
[program:opendkim]
|
||||||
command=/usr/sbin/opendkim -f -x /etc/opendkim.conf
|
command=/usr/sbin/opendkim -f -x /etc/opendkim.conf
|
||||||
priority=100
|
priority=100
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/mixeme/selfpost/internal/inbound"
|
"github.com/mixeme/selfpost/internal/inbound"
|
||||||
"github.com/mixeme/selfpost/internal/logtail"
|
"github.com/mixeme/selfpost/internal/logtail"
|
||||||
"github.com/mixeme/selfpost/internal/store"
|
"github.com/mixeme/selfpost/internal/store"
|
||||||
|
"github.com/mixeme/selfpost/internal/syslogrelay"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -47,12 +48,21 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showVersion := flag.Bool("version", false, "print version and exit")
|
showVersion := flag.Bool("version", false, "print version and exit")
|
||||||
|
relay := flag.String("syslog-relay", "", "relay syslog(3) datagrams sent to this socket path to stdout, and do nothing else")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *showVersion {
|
if *showVersion {
|
||||||
fmt.Println(buildinfo.Version)
|
fmt.Println(buildinfo.Version)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if *relay != "" {
|
||||||
|
// The container's /dev/log, for OpenDKIM (internal/syslogrelay;
|
||||||
|
// build/supervisord.conf, [program:syslog-relay]).
|
||||||
|
if err := syslogrelay.Run(*relay, os.Stdout); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.SetFlags(log.LstdFlags | log.LUTC)
|
log.SetFlags(log.LstdFlags | log.LUTC)
|
||||||
log.SetPrefix("panel: ")
|
log.SetPrefix("panel: ")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Managed programs ([build/supervisord.conf](../build/supervisord.conf)):
|
|||||||
|
|
||||||
| Program | User | Priority | Role |
|
| Program | User | Priority | Role |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|
| `syslog-relay` | panel | 50 | The container's `/dev/log`: passes what OpenDKIM logs through syslog(3) to the container's log (`panel -syslog-relay`) |
|
||||||
| `opendkim` | root → `opendkim` (`UserID` in opendkim.conf) | 100 | DKIM signing milter |
|
| `opendkim` | root → `opendkim` (`UserID` in opendkim.conf) | 100 | DKIM signing milter |
|
||||||
| `panel` | panel | 200 | HTTP UI + sender check + journal-milter + log-tailer + rate-limit recalc; with the inbound relay on also the inbound journal milter |
|
| `panel` | panel | 200 | HTTP UI + sender check + journal-milter + log-tailer + rate-limit recalc; with the inbound relay on also the inbound journal milter |
|
||||||
| `postfix` | root (wrapper) | 300 | MTA — started only after the milter sockets exist (OpenDKIM's, the sender check's, the journal milter's and, with the inbound relay on, the inbound journal milter's) |
|
| `postfix` | root (wrapper) | 300 | MTA — started only after the milter sockets exist (OpenDKIM's, the sender check's, the journal milter's and, with the inbound relay on, the inbound journal milter's) |
|
||||||
@@ -33,7 +34,7 @@ Managed programs ([build/supervisord.conf](../build/supervisord.conf)):
|
|||||||
| `cert-reload` | root | 400 | Daily: re-copies a renewed TLS certificate for Postfix and reloads it |
|
| `cert-reload` | root | 400 | Daily: re-copies a renewed TLS certificate for Postfix and reloads it |
|
||||||
| `logrotate` | root | 400 | Periodic `mail.log` rotation |
|
| `logrotate` | root | 400 | Periodic `mail.log` rotation |
|
||||||
|
|
||||||
Start order: OpenDKIM → panel (opens the sender check's and the journal-milter's sockets) → Postfix wrapper
|
Start order: syslog-relay → OpenDKIM → panel (opens the sender check's and the journal-milter's sockets) → Postfix wrapper
|
||||||
polls unix sockets (timeout `MILTER_WAIT_TIMEOUT`, default 30s) then runs
|
polls unix sockets (timeout `MILTER_WAIT_TIMEOUT`, default 30s) then runs
|
||||||
`postfix start-fg` and stays its parent: on TERM it runs `postfix stop`, so a
|
`postfix start-fg` and stays its parent: on TERM it runs `postfix stop`, so a
|
||||||
stop of the program or of the container stops Postfix as Postfix means it to
|
stop of the program or of the container stops Postfix as Postfix means it to
|
||||||
|
|||||||
+4
-1
@@ -209,7 +209,10 @@ operator can act on it.
|
|||||||
- Rendering goes through `html/template` with auto-escaping (queue, log,
|
- Rendering goes through `html/template` with auto-escaping (queue, log,
|
||||||
journal, themes).
|
journal, themes).
|
||||||
- The panel process is **not root** (`user=panel` in supervisord); path access
|
- The panel process is **not root** (`user=panel` in supervisord); path access
|
||||||
is granted through the `selfpost` group with minimal permissions.
|
is granted through the `selfpost` group with minimal permissions. The
|
||||||
|
supervisord control socket, which can stop or restart any program, is the
|
||||||
|
`supervisor` group's, whose one member is the panel; OpenDKIM and Postfix
|
||||||
|
share `selfpost` for files and do not reach it (`2.6.0`).
|
||||||
|
|
||||||
### Mail path (security-relevant)
|
### Mail path (security-relevant)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// Package syslogrelay passes what programs in the container send to syslog(3)
|
||||||
|
// on to standard output, where supervisord takes it into the container's log.
|
||||||
|
//
|
||||||
|
// OpenDKIM logs through syslog(3) and nothing else (OpenDKIM 2.11.0~beta2,
|
||||||
|
// opendkim/opendkim.conf.5.in: Syslog, SyslogFacility and SyslogName are the
|
||||||
|
// only logging options; opendkim/opendkim.c calls openlog() and syslog()).
|
||||||
|
// syslog(3) sends each message as a datagram to /dev/log, and in the container
|
||||||
|
// nothing listened there: OpenDKIM's errors - a key it cannot read, a table it
|
||||||
|
// cannot open - went nowhere (audit 2026-10-12, L30). Postfix is not among the
|
||||||
|
// senders: it logs through its own postlogd (build/postfix-config.sh).
|
||||||
|
package syslogrelay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stamp is the BSD syslog timestamp that follows the priority, "Oct 15
|
||||||
|
// 09:00:00 " (RFC 3164 § 4.1.2: "Mmm dd hh:mm:ss", the day padded with a
|
||||||
|
// space). The container's log has times of its own.
|
||||||
|
var stamp = regexp.MustCompile(`^[A-Z][a-z]{2} [ 0-9][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} `)
|
||||||
|
|
||||||
|
// Line turns one datagram into the line written out: the priority and the
|
||||||
|
// timestamp taken off, trailing newlines too; what remains is "ident[pid]:
|
||||||
|
// message". A severity of error or worse (RFC 3164 § 4.1.1: the priority is
|
||||||
|
// facility*8 + severity, and 0-3 are emergency to error) is said in front.
|
||||||
|
func Line(datagram []byte) string {
|
||||||
|
b := bytes.TrimRight(datagram, "\r\n\x00")
|
||||||
|
severity := -1
|
||||||
|
if len(b) > 2 && b[0] == '<' {
|
||||||
|
if end := bytes.IndexByte(b, '>'); end > 1 && end <= 4 {
|
||||||
|
pri := 0
|
||||||
|
ok := true
|
||||||
|
for _, c := range b[1:end] {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pri = pri*10 + int(c-'0')
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
severity = pri % 8
|
||||||
|
b = b[end+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if loc := stamp.FindIndex(b); loc != nil {
|
||||||
|
b = b[loc[1]:]
|
||||||
|
}
|
||||||
|
line := string(b)
|
||||||
|
if severity >= 0 && severity <= 3 {
|
||||||
|
line = "error: " + line
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run listens on a datagram socket at path, which anyone in the container may
|
||||||
|
// write to, and writes each message to w as a line until the socket fails.
|
||||||
|
// /dev/log is a link to path (build/entrypoint.sh).
|
||||||
|
func Run(path string, w io.Writer) error {
|
||||||
|
_ = os.Remove(path)
|
||||||
|
conn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: path, Net: "unixgram"})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("syslog relay: listen on %s: %w", path, err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
// OpenDKIM runs as its own user; the socket is the container's local log,
|
||||||
|
// as /dev/log is on a host.
|
||||||
|
if err := os.Chmod(path, 0o666); err != nil {
|
||||||
|
return fmt.Errorf("syslog relay: %w", err)
|
||||||
|
}
|
||||||
|
buf := make([]byte, 64<<10)
|
||||||
|
for {
|
||||||
|
n, _, err := conn.ReadFromUnix(buf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("syslog relay: read: %w", err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, Line(buf[:n]))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package syslogrelay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLine(t *testing.T) {
|
||||||
|
for in, want := range map[string]string{
|
||||||
|
"<22>Oct 15 09:00:00 opendkim[45]: 3A2B: DKIM-Signature field added (s=sp, d=example.com)\n": "opendkim[45]: 3A2B: DKIM-Signature field added (s=sp, d=example.com)",
|
||||||
|
"<19>Oct 5 09:00:00 opendkim[45]: key retrieval failed": "error: opendkim[45]: key retrieval failed",
|
||||||
|
"<20>opendkim[45]: no timestamp": "opendkim[45]: no timestamp",
|
||||||
|
"not syslog at all": "not syslog at all",
|
||||||
|
"<x>Oct 15 09:00:00 odd": "<x>Oct 15 09:00:00 odd",
|
||||||
|
} {
|
||||||
|
if got := Line([]byte(in)); got != want {
|
||||||
|
t.Errorf("Line(%q) = %q, want %q", in, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncBuffer is a bytes.Buffer the relay and the test may share.
|
||||||
|
type syncBuffer struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
b bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncBuffer) Write(p []byte) (int, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.b.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncBuffer) String() string {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// What a program sends to the socket comes out as a line.
|
||||||
|
func TestRunRelaysADatagram(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("no unixgram sockets on Windows; the container is Linux")
|
||||||
|
}
|
||||||
|
path := filepath.Join(t.TempDir(), "log")
|
||||||
|
out := &syncBuffer{}
|
||||||
|
go Run(path, out)
|
||||||
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
if conn, err = net.Dial("unixgram", path); err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(20 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if _, err := conn.Write([]byte("<19>Oct 15 09:00:00 opendkim[7]: can't load key")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
|
for time.Now().Before(deadline) && !strings.Contains(out.String(), "\n") {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if got := out.String(); got != "error: opendkim[7]: can't load key\n" {
|
||||||
|
t.Errorf("relayed %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,6 +113,9 @@ func TestE2E(t *testing.T) {
|
|||||||
if err := checkSupervisorProcesses(h); err != nil {
|
if err := checkSupervisorProcesses(h); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if err := checkOpenDKIMLogs(h); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if err := checkInboundRelayOff(h); err != nil {
|
if err := checkInboundRelayOff(h); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// excluded: autostart=false, so it rests STOPPED until its first run and
|
// excluded: autostart=false, so it rests STOPPED until its first run and
|
||||||
// EXITED after it (supervisor 4.2.5, supervisor/process.py,
|
// EXITED after it (supervisor 4.2.5, supervisor/process.py,
|
||||||
// Subprocess.finish(); assumptions C-V10).
|
// Subprocess.finish(); assumptions C-V10).
|
||||||
var wantRunning = []string{"opendkim", "panel", "postfix", "cert-reload", "logrotate"}
|
var wantRunning = []string{"syslog-relay", "opendkim", "panel", "postfix", "cert-reload", "logrotate"}
|
||||||
|
|
||||||
// checkSupervisorProcesses shells into the container directly (not through the
|
// checkSupervisorProcesses shells into the container directly (not through the
|
||||||
// panel's /overview page) so it works before an administrator account even
|
// panel's /overview page) so it works before an administrator account even
|
||||||
@@ -129,3 +129,15 @@ func checkCitedPackageVersions(s *stack) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkOpenDKIMLogs waits for OpenDKIM's start line in the container's log:
|
||||||
|
// OpenDKIM logs through syslog(3) only, and /dev/log is the syslog-relay
|
||||||
|
// program's (audit 2026-10-12, L30). OpenDKIM 2.11.0~beta2,
|
||||||
|
// opendkim/opendkim.c: syslog(LOG_INFO, "%s v%s starting%s%s%s",
|
||||||
|
// DKIMF_PRODUCT, ...), DKIMF_PRODUCT "OpenDKIM Filter" (opendkim.h).
|
||||||
|
func checkOpenDKIMLogs(s *stack) error {
|
||||||
|
return waitFor("OpenDKIM's start line in the container log", 20*time.Second, 500*time.Millisecond, func() (bool, error) {
|
||||||
|
out := s.logs("selfpost")
|
||||||
|
return strings.Contains(out, "opendkim[") && strings.Contains(out, "OpenDKIM Filter v"), fmt.Errorf("no OpenDKIM line in the log")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user