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:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user