entrypoint: no link followed at the DMARC maps (audit I2)
Security audit 2026-10-17, finding I2 (= M4), stage 0. drop_links listed the outbound maps, sasldb2, the key tree and mail.log but not the three DMARC maps that postfix-config.sh creates as root with `[ -e ] || : >` when DMARC_REPORTS_ENABLE=true; -e is false for a dangling link and the redirect follows it, and /data/postfix is the panel's. Now the three are in drop_links, and both map loops refuse to create through a link (`[ -e ] || [ -L ] || : >`). e2e: TestStartsOnA28Data plants a dangling /data/postfix/dmarc_transport with DMARC reports on and expects the "removing the symbolic link" line, no file at the link's target, and a regular map. To be run on the server with the stage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|||||||
check. Adding a user, setting a password, changing an e-mail or a role on
|
check. Adding a user, setting a password, changing an e-mail or a role on
|
||||||
the user form, and deleting a user now ask for your own account password,
|
the user form, and deleting a user now ask for your own account password,
|
||||||
as a backup does, under the same limit on wrong guesses.
|
as a backup does, under the same limit on wrong guesses.
|
||||||
|
- **The entrypoint follows no link at the DMARC maps** (audit I2): the
|
||||||
|
three `dmarc_*` maps root creates at the start were missing from the list
|
||||||
|
of paths where a symbolic link is removed first.
|
||||||
|
|
||||||
## [2.14.3] - 2026-10-17
|
## [2.14.3] - 2026-10-17
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ drop_links /data/dkim /data/dkim.new /data/opendkim /data/sasl /data/postfix /da
|
|||||||
/data/sasl/sasldb2 /data/postfix/queue \
|
/data/sasl/sasldb2 /data/postfix/queue \
|
||||||
/data/postfix/sender_login_maps /data/postfix/relay_domains \
|
/data/postfix/sender_login_maps /data/postfix/relay_domains \
|
||||||
/data/postfix/transport /data/postfix/relay_recipients /data/postfix/tls_policy \
|
/data/postfix/transport /data/postfix/relay_recipients /data/postfix/tls_policy \
|
||||||
|
/data/postfix/dmarc_recipients /data/postfix/dmarc_transport /data/postfix/dmarc_relay_domains \
|
||||||
/data/log/mail.log
|
/data/log/mail.log
|
||||||
# Restored backups or previously-created state may contain panel-owned files
|
# Restored backups or previously-created state may contain panel-owned files
|
||||||
# under /data; make sure they stay writable without disturbing anything that a
|
# under /data; make sure they stay writable without disturbing anything that a
|
||||||
|
|||||||
@@ -534,8 +534,12 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
|||||||
TRANSPORT_SETTING=""
|
TRANSPORT_SETTING=""
|
||||||
RECIPIENT_RESTRICTIONS="reject"
|
RECIPIENT_RESTRICTIONS="reject"
|
||||||
if [ "${INBOUND_RELAY_ENABLE}" = "true" ]; then
|
if [ "${INBOUND_RELAY_ENABLE}" = "true" ]; then
|
||||||
|
# [ -e ] is false for a dangling link and the redirect would follow
|
||||||
|
# it: the entrypoint's drop_links removes links at these paths, and
|
||||||
|
# this refuses to create through one all the same (audit 2026-10-17,
|
||||||
|
# I2).
|
||||||
for f in "$RELAY_DOMAINS_MAP" "$TRANSPORT_MAP" "$RELAY_RECIPIENTS_MAP" "$TLS_POLICY_MAP"; do
|
for f in "$RELAY_DOMAINS_MAP" "$TRANSPORT_MAP" "$RELAY_RECIPIENTS_MAP" "$TLS_POLICY_MAP"; do
|
||||||
[ -e "$f" ] || : > "$f"
|
[ -e "$f" ] || [ -L "$f" ] || : > "$f"
|
||||||
done
|
done
|
||||||
RELAY_DOMAINS_SETTING="texthash:${RELAY_DOMAINS_MAP}"
|
RELAY_DOMAINS_SETTING="texthash:${RELAY_DOMAINS_MAP}"
|
||||||
TRANSPORT_SETTING="texthash:${TRANSPORT_MAP}"
|
TRANSPORT_SETTING="texthash:${TRANSPORT_MAP}"
|
||||||
@@ -543,7 +547,7 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
|||||||
fi
|
fi
|
||||||
if [ "${DMARC_REPORTS_ENABLE}" = "true" ]; then
|
if [ "${DMARC_REPORTS_ENABLE}" = "true" ]; then
|
||||||
for f in "$DMARC_RECIPIENTS_MAP" "$DMARC_TRANSPORT_MAP" "$DMARC_RELAY_DOMAINS_MAP"; do
|
for f in "$DMARC_RECIPIENTS_MAP" "$DMARC_TRANSPORT_MAP" "$DMARC_RELAY_DOMAINS_MAP"; do
|
||||||
[ -e "$f" ] || : > "$f"
|
[ -e "$f" ] || [ -L "$f" ] || : > "$f"
|
||||||
done
|
done
|
||||||
ln -sf /usr/local/bin/panel /usr/local/bin/dmarc-ingest
|
ln -sf /usr/local/bin/panel /usr/local/bin/dmarc-ingest
|
||||||
# flags=, user= and argv= are arguments of pipe(8), not -o parameters:
|
# flags=, user= and argv= are arguments of pipe(8), not -o parameters:
|
||||||
|
|||||||
@@ -693,9 +693,10 @@ the argument.
|
|||||||
tests pass with the field. — **Opus**, 2 h. *Done 2026-10-17:* also on a
|
tests pass with the field. — **Opus**, 2 h. *Done 2026-10-17:* also on a
|
||||||
change of e-mail (where reset links go); a save of the name and the
|
change of e-mail (where reset links go); a save of the name and the
|
||||||
reach alone is not asked; TestUserFormAsksForTheAccountPassword.
|
reach alone is not asked; TestUserFormAsksForTheAccountPassword.
|
||||||
- [ ] **I2.** The three DMARC maps in `drop_links`; `[ -L ]` beside `[ -e ]`
|
- [x] **I2.** The three DMARC maps in `drop_links`; `[ -L ]` beside `[ -e ]`
|
||||||
in postfix-config.sh:538,545; an e2e case plants a link and expects the
|
in postfix-config.sh:538,545; an e2e case plants a link and expects the
|
||||||
log line. — **Opus**, 0.5 h.
|
log line. — **Opus**, 0.5 h. *Done 2026-10-17:* TestStartsOnA28Data
|
||||||
|
plants a dangling `dmarc_transport` with DMARC reports on.
|
||||||
- [ ] **M2.** Greedy reply group anchored on Postfix's own tail; the live
|
- [ ] **M2.** Greedy reply group anchored on Postfix's own tail; the live
|
||||||
line above as a test; security.md § Mail path reworded ("the last `;
|
line above as a test; security.md § Mail path reworded ("the last `;
|
||||||
from=<`, which is Postfix's because a HELO name can hold none of
|
from=<`, which is Postfix's because a HELO name can hold none of
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ func TestStartsOnA28Data(t *testing.T) {
|
|||||||
t.Fatalf("generate throwaway TLS cert: %v", err)
|
t.Fatalf("generate throwaway TLS cert: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A dangling link where root creates an empty DMARC map: the panel owns
|
||||||
|
// /data/postfix and could plant one. The entrypoint removes it and
|
||||||
|
// creates nothing at its target (audit 2026-10-17, I2).
|
||||||
|
const planted = "/etc/selfpost-planted-by-the-panel"
|
||||||
|
if err := os.Symlink(planted, filepath.Join(dataDir, "postfix", "dmarc_transport")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
name := "selfpost-e2e-upgrade"
|
name := "selfpost-e2e-upgrade"
|
||||||
_ = exec.Command("docker", "rm", "-f", name).Run()
|
_ = exec.Command("docker", "rm", "-f", name).Run()
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -103,7 +111,7 @@ func TestStartsOnA28Data(t *testing.T) {
|
|||||||
_ = exec.Command("docker", "rm", "-f", name).Run()
|
_ = exec.Command("docker", "rm", "-f", name).Run()
|
||||||
}()
|
}()
|
||||||
if out, err := exec.Command("docker", "run", "-d", "--name", name,
|
if out, err := exec.Command("docker", "run", "-d", "--name", name,
|
||||||
"-e", "SELFPOST_HOSTNAME="+host, "-e", "MILTER_WAIT_TIMEOUT=10",
|
"-e", "SELFPOST_HOSTNAME="+host, "-e", "MILTER_WAIT_TIMEOUT=10", "-e", "DMARC_REPORTS_ENABLE=true",
|
||||||
"-v", dataDir+":/data", "-v", certDir+":/etc/postfix/tls:ro",
|
"-v", dataDir+":/data", "-v", certDir+":/etc/postfix/tls:ro",
|
||||||
"selfpost:e2e").CombinedOutput(); err != nil {
|
"selfpost:e2e").CombinedOutput(); err != nil {
|
||||||
t.Fatalf("docker run: %v\n%s", err, out)
|
t.Fatalf("docker run: %v\n%s", err, out)
|
||||||
@@ -147,6 +155,12 @@ func TestStartsOnA28Data(t *testing.T) {
|
|||||||
// its postrotate asks no reload of a Postfix not running yet (the test
|
// its postrotate asks no reload of a Postfix not running yet (the test
|
||||||
// server's rollout of 2026-10-16 logged "fatal: the Postfix mail system
|
// server's rollout of 2026-10-16 logged "fatal: the Postfix mail system
|
||||||
// is not running"; build/logrotate-mail.conf).
|
// is not running"; build/logrotate-mail.conf).
|
||||||
|
if !strings.Contains(logs(), "entrypoint: removing the symbolic link /data/postfix/dmarc_transport") {
|
||||||
|
t.Errorf("the entrypoint did not remove the planted link:\n%s", logs())
|
||||||
|
}
|
||||||
|
if got := exec1(`test -e ` + planted + ` && echo created || echo absent; test -f /data/postfix/dmarc_transport && ! test -L /data/postfix/dmarc_transport && echo file`); got != "absent\nfile" {
|
||||||
|
t.Errorf("planted link: %q, want the target absent and the map a regular file", got)
|
||||||
|
}
|
||||||
if got := exec1(`test -f /data/log/mail.log.1 && echo rotated`); got != "rotated" {
|
if got := exec1(`test -f /data/log/mail.log.1 && echo rotated`); got != "rotated" {
|
||||||
t.Errorf("the rotation due at the start did not run: %q", got)
|
t.Errorf("the rotation due at the start did not run: %q", got)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user