61 lines
4.2 KiB
Markdown
61 lines
4.2 KiB
Markdown
# Task 4 — Unix-socket DWH verification report
|
|
|
|
## Scope
|
|
|
|
Implemented the standalone Linux verifier at `tools/dwh-auth/internal/service` and wired the exact command:
|
|
|
|
```text
|
|
dwh-auth serve --registry-root ABSOLUTE_CANONICAL --socket ABSOLUTE_CANONICAL
|
|
```
|
|
|
|
The service accepts only `GET /verify`. It returns empty `204` responses with `X-DWH-Key-ID` for verified v1 or reserved legacy credentials; credential failures are generic empty `401` responses, and registry/integrity faults are empty `503` responses. Other paths/methods return empty `404`/`405`.
|
|
|
|
## Security decisions
|
|
|
|
- Exactly one `X-API-Key` header, maximum 128 bytes.
|
|
- Strict `thtdwh_v1.` parsing precedes legacy lookup; non-v1 values alone may use the reserved legacy record.
|
|
- Registry integrity is checked before every verification request, so unrelated malformed/unsafe records fail closed with `503`.
|
|
- Logs emit only timestamp, decision code, and (when safely parsed or verified) public key ID; test sentinels prove no key, digest, description, or query value is emitted.
|
|
- `serve` validates canonical absolute paths, performs startup `Store.Check`, and reports service startup errors as non-secret `integrity failure`.
|
|
- Socket collisions that are regular files, directories, symlinks, live sockets, or foreign-owned stale sockets are refused. Only an owned stale Unix socket after `ECONNREFUSED` can be reclaimed.
|
|
- Published sockets are mode `0660`; cancellation calls graceful shutdown and removes only a revalidated same-device/same-inode owned socket. A test seam proves a changed path is retained rather than unlinked.
|
|
|
|
## Required supporting security fix
|
|
|
|
Commit `943f809` (`fix: reject duplicate legacy DWH records`) tightens the Task 2 registry contract: a synthetically valid active plus revoked legacy pair is now an integrity failure. It is intentionally separate from the Task 4 commit.
|
|
|
|
## TDD evidence
|
|
|
|
RED was observed for the missing handler, listener/configuration API, CLI wiring, unrelated-registry corruption, active+revoked legacy state, and cleanup replacement race. Each increment was then implemented minimally and rerun GREEN.
|
|
|
|
## Verification
|
|
|
|
All commands were executed in official `golang:1.26.5`, with only this worktree mounted:
|
|
|
|
```text
|
|
gofmt -w cmd internal/command internal/service
|
|
go test ./internal/service ./internal/command -count=1
|
|
go test ./... -count=1
|
|
go test -race ./... -count=1
|
|
go vet ./...
|
|
git diff --check
|
|
```
|
|
|
|
All passed. A dependency scan also found no third-party Go dependencies.
|
|
|
|
## Scope boundary
|
|
|
|
No Nginx, systemd, real Unix socket, real registry, credential, legacy stack, or external service was changed. All test data was synthetic and temporary.
|
|
|
|
## Follow-up hardening: runtime read-only registry and socket parent
|
|
|
|
The Task 5 storage contract uses `root:dwh-auth` SGID directories (`2750`) and a service account with read-only group access. The original registry reader path was incompatible because shared locks were opened `O_RDWR` and lazily created as `0600`; secure-directory validation also rejected SGID.
|
|
|
|
The runtime path now uses `registry.OpenReadOnly`: it opens only preprovisioned root, `active`, `revoked`, and `.writer.lock` paths, and rejects `Add`/`Revoke`. The administrative `Open` path bootstraps the lock through the exclusive writer path. Shared lock acquisition opens the existing `root:dwh-auth 0640` lock `O_RDONLY` with `LOCK_SH`; writer acquisition remains `O_RDWR` with `LOCK_EX`, preserving cross-process snapshot exclusion. Secure directories allow SGID but still reject setuid, sticky, group-write, and world-write bits.
|
|
|
|
Task 5 must create `.writer.lock` as `0640 root:dwh-auth` alongside the `2750 root:dwh-auth` registry directories before the service starts.
|
|
|
|
The socket parent must be a canonical non-symlink directory owned by the service EUID and not group/world writable. This removes the bind-to-chmod and path-replacement exposure from other principals. The remaining POSIX path race is bounded to trusted processes sharing the service EUID inside that non-contendible parent.
|
|
|
|
Additional verification (official `golang:1.26.5`, worktree only): focused securefile/registry/service/command tests, full tests, full race tests, vet, plus ten race repetitions each for cross-store snapshot readers, `OpenReadOnly`, and listener tests: all PASS.
|