audit 3: backup CLI, envelope budget, directory sync, docs; findings of the reviews
Steps 3-6 of stage 3 and what the two reviews found in steps 1-6, in one commit: the review fixes sit in the same hunks. Step 3, CLI backup (M3, M5, L1, L2; L4 of the stage's list): - backup.snapshotDB refuses a database that is not there and opens the source mode=ro (measured: mode=ro creates no file). - selfpost-backup holds an encrypting password to validate.MinSecretFilePasswordLen; -o of both directions writes a temporary file beside the name and renames it whole; -decrypt peeks the first chunk (password and type authenticated) before the output exists. - what a backup leaves out (a link under data/, a link under certs/ to a directory, a socket...) is told to OnWarn; certs/ with no file is backup.ErrNoCerts, refused by Check and Create, named by the panel. - parseTrustedProxies: a bare address takes /128 when written with a colon (::ffff:10.0.0.1/32 was ::/32). Step 4, secretfile (M4, L3): scrypt's V at most 128 MiB, r <= 64, p <= 2; bytes after the last chunk are ErrCorrupt. Step 5 (L7): atomicfile.SyncDir after the rename, used by Write, replaceSASLDB and the CLI; atomicfile_test.go. Step 6: security.md and guide on backup and restore. Reviews (technical, security): - H1: a restore of a server with DMARC on was refused on every start - the archive records no owner, tar as root left the DMARC maps root's, and the entrypoint only chowned the maps it named. It now hands every file beside queue/ in /data/postfix to panel:selfpost 0640; e2e writes an extra map before the backup and finds it panel:selfpost 640 after a root unpack. - the comparison reads regular files only (Lstat, size, LimitReader); a record unpacked again wins over the earlier check's mark; ConsumeRestore removes both; the mark is synced; a journal file beside a freshly unpacked database is refused. - AttachRefusalLine refuses an empty line as the old statement did; skipped entries named in words; -decrypt of an empty payload is no failure; guide wording on the shell's '>' and a killed CLI; the in-place restore's 'mv .' (older than this stage) fixed. Tested on the test server: go vet + go test of 25 packages, make e2e (all pass, 643 s) on this tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1114,17 +1114,46 @@ those after it move up one patch number each.
|
||||
top-N). The statements are constants/builders the test explains
|
||||
(`TestSendLogQueriesUseTheirIndexes`, which replaces the hand-written
|
||||
plan check of `TestLimitRefusalsSince`).
|
||||
- [ ] **CLI backup**: open the source `mode=ro`, refuse a missing file; password
|
||||
- [x] **CLI backup**: open the source `mode=ro`, refuse a missing file; password
|
||||
floor of 12 shared with the panel (`validate.MinSecretFilePasswordLen`);
|
||||
`-decrypt -o` verifies the password (first chunk) before opening the
|
||||
output; `OnWarn` for skipped symlinks and `Check` fails when `certs/` is
|
||||
named but empty. — **Opus**
|
||||
- [ ] **`secretfile` caps**: bound `128·N·r` to a stated budget (e.g. 128 MiB)
|
||||
*Done 2026-10-15.* The source check is in `backup.snapshotDB`, so the
|
||||
panel has it too; `mode=ro` measured not to create a missing file
|
||||
(`TestSnapshotRefusesAMissingDatabase`). `-o` of both directions writes
|
||||
a temporary file beside the name and renames it whole (a backup that
|
||||
fails half way left a partial file too); `-decrypt` peeks the first
|
||||
chunk - the password, and the envelope's type, authenticated - before
|
||||
it opens the output. Since 2.5.2 links under `certs/` are followed, so
|
||||
what is still left out and now warned of: a link under `data/`, a link
|
||||
under `certs/` to a directory, a socket or the like; `certs/` with no
|
||||
file refused by Check and Create alike. L4, in this stage's list without
|
||||
a line: a bare address takes /128 when written with a colon, /32
|
||||
otherwise (`::ffff:10.0.0.1/32` was `::/32`).
|
||||
- [x] **`secretfile` caps**: bound `128·N·r` to a stated budget (e.g. 128 MiB)
|
||||
and `p` to 1-2; refuse the rest with `ErrHeader`; reject trailing bytes
|
||||
after the last chunk. Tests. — **Opus**
|
||||
- [ ] **`atomicfile`**: fsync the directory after rename; a test file for the
|
||||
*Done 2026-10-15:* 128 MiB of scrypt's `V` (x/crypto v0.58.0 `Key`:
|
||||
`make([]uint32, 32*N*r)`), `p` ≤ 2, `logN` ≤ 30 for the arithmetic; the
|
||||
refusal is the existing `ErrCorrupt` (there is no `ErrHeader`), which the
|
||||
import shows as "damaged or incomplete". One byte is read after the last
|
||||
chunk; any is `ErrCorrupt`. `TestKDFBudget` (the old cap's 4 GiB among
|
||||
the refused), `TestBytesAfterTheLastChunkRejected`.
|
||||
- [x] **`atomicfile`**: fsync the directory after rename; a test file for the
|
||||
package. — **Opus**
|
||||
- [ ] Docs: security.md § Backup; guide § Backup. — **Sonnet**
|
||||
*Done 2026-10-15:* `atomicfile.SyncDir`, best effort as sasl.go's (the
|
||||
file is in place either way; Go on Windows cannot sync a directory), used
|
||||
by `Write`, `app.replaceSASLDB` (its inline copy replaced) and the CLI's
|
||||
`-o`. `atomicfile_test.go`: create, replace, mode, nothing left beside;
|
||||
an open reader keeps the old file; a failed write (missing directory,
|
||||
a directory in the way) leaves the old state; SyncDir works on Linux.
|
||||
- [x] Docs: security.md § Backup; guide § Backup. — **Sonnet**
|
||||
*Done 2026-10-15:* security.md § Backup and domain export (what is warned
|
||||
of, the missing database, the restore's record - integrity, not
|
||||
authenticity -, the KDF budget, trailing bytes, the CLI's floor and
|
||||
`-o`); guide § Restore (step 1), § Full backup (certs/, `-o`, the
|
||||
database) and § Encrypting (the first block, a cut file, the floor).
|
||||
|
||||
### Stage 4 — panel behaviour (M8, M11, L10–L19) → `2.5.4`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user