733 lines
26 KiB
Markdown
733 lines
26 KiB
Markdown
# DWH REST Per-Installation Authentication Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Build, document, and safely activate a reusable server-side `dwh-auth` service that gives every remote ThothII installation an independently revocable `/dwh/` API key without changing portable ThothII connector behavior.
|
||
|
||
**Architecture:** A standalone Linux Go binary uses only the standard library, stores versioned credential digests in protected atomic files, and serves one fail-closed verification endpoint over a Unix socket. Host Nginx uses that endpoint through `auth_request`; PSD runs the binary as an independent hardened `systemd` service, while the local ThothII server continues to use `postgres_direct`.
|
||
|
||
**Tech Stack:** Go 1.26.0/toolchain 1.26.5, standard library only, Linux `systemd`, Nginx `auth_request`, Bash verification scripts, Markdown documentation, Git.
|
||
|
||
## Global Constraints
|
||
|
||
- Preserve the portable REST contract: successful clients continue to send exactly one `X-API-Key` header.
|
||
- Do not modify `postgres_direct` or `ssh_tunnel`, and do not add DWH commands to portable `tht`.
|
||
- Do not add `dwh-auth` to ThothII Compose or `tht start/stop`.
|
||
- Build Linux amd64/arm64 only; Mac and Windows do not need this binary.
|
||
- Use only the Go standard library; do not add SQLite, CGO, or runtime packages.
|
||
- New keys use `thtdwh_v1.<16-character-base64url-key-id>.<43-character-base64url-secret>` with 256 random secret bits.
|
||
- Permit one temporary unversioned `legacy_raw` record with public ID `legacy-shared`; revoke it after Mac migration.
|
||
- Store SHA-256 digests and non-secret metadata only; never emit digests through CLI or HTTP.
|
||
- Default to no expiry; optional expiry is RFC3339 UTC and fail-closed.
|
||
- Read/write real keys only through absolute protected files. Never put keys in argv, env, Git, logs, JSON output, or ordinary config.
|
||
- Credential failures are generic `401`; service/registry/integrity failures become `503`.
|
||
- PSD uses independent `systemd` plus a Unix socket. Stopping ThothII must not stop DWH auth.
|
||
- Do not preserve/migrate legacy test sessions, Qdrant indexes, or Ollama caches.
|
||
- Do not stop or modify the legacy ThothII stack in this plan.
|
||
- Do not mutate PSD Nginx, `systemd`, registry paths, or real keys before Task 9 authorization.
|
||
- When delegated, use Luna for deterministic execution and Terra for review. The primary executor owns secret-bearing production steps and reports sanitized results.
|
||
- Keep unrelated `brain/` changes and other dirty content out of every commit.
|
||
|
||
---
|
||
|
||
### Task 1: Freeze credential and record contracts
|
||
|
||
**Files:**
|
||
- Create: `tools/dwh-auth/go.mod`
|
||
- Create: `tools/dwh-auth/internal/credential/credential.go`
|
||
- Create: `tools/dwh-auth/internal/credential/credential_test.go`
|
||
- Create: `tools/dwh-auth/internal/record/record.go`
|
||
- Create: `tools/dwh-auth/internal/record/record_test.go`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Go standard-library randomness, hashing, constant-time comparison, base64url, JSON, and time.
|
||
- Produces: `credential.Generate(io.Reader) (Material, error)`, `credential.VerifyV1([]byte, record.Digest) (string, bool)`, `credential.VerifyLegacy([]byte, record.Digest) bool`, `record.Validate(Record) error`, and the record JSON schema.
|
||
|
||
- [ ] **Step 1: Add the isolated module**
|
||
|
||
```go
|
||
module github.com/aritmolab/thothii/tools/dwh-auth
|
||
|
||
go 1.26.0
|
||
|
||
toolchain go1.26.5
|
||
```
|
||
|
||
- [ ] **Step 2: Write failing format and validation tests**
|
||
|
||
Freeze:
|
||
|
||
```go
|
||
const Prefix = "thtdwh_v1"
|
||
const KeyIDEncodedLength = 16
|
||
const SecretEncodedLength = 43
|
||
const MaxHeaderBytes = 128
|
||
|
||
type Material struct {
|
||
Value []byte
|
||
KeyID string
|
||
Digest record.Digest
|
||
}
|
||
```
|
||
|
||
Tests use a deterministic 44-byte reader and assert exact segments/lengths, 32 decoded secret
|
||
bytes, valid digest, changed-byte rejection, comma/duplicate rejection, padding/whitespace
|
||
rejection, and 129-byte rejection. Legacy tests hash opaque raw values without v1 parsing.
|
||
Record tests cover every invalid enum, identifier, timestamp, digest, description, expiry, and
|
||
revocation combination.
|
||
|
||
- [ ] **Step 3: Prove RED**
|
||
|
||
```bash
|
||
cd tools/dwh-auth
|
||
go test ./internal/credential ./internal/record -count=1
|
||
```
|
||
|
||
Expected: non-zero because implementations are absent.
|
||
|
||
- [ ] **Step 4: Implement the exact record schema**
|
||
|
||
```go
|
||
type Kind string
|
||
|
||
const (
|
||
SchemaVersion = 1
|
||
KindV1 Kind = "per_installation_v1"
|
||
KindLegacyRaw Kind = "legacy_raw"
|
||
LegacyKeyID = "legacy-shared"
|
||
)
|
||
|
||
type Digest [32]byte
|
||
|
||
type Record struct {
|
||
SchemaVersion int `json:"schema_version"`
|
||
Kind Kind `json:"credential_kind"`
|
||
KeyID string `json:"key_id"`
|
||
InstallationID string `json:"installation_id"`
|
||
Description string `json:"description,omitempty"`
|
||
SecretSHA256 string `json:"secret_sha256"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||
RevokedAt *time.Time `json:"revoked_at,omitempty"`
|
||
RevocationReason string `json:"revocation_reason,omitempty"`
|
||
}
|
||
```
|
||
|
||
Require schema 1; exact kind/key relationship; installation ID
|
||
`[a-z0-9][a-z0-9-]{0,62}`; metadata at most 160 UTF-8 characters without controls;
|
||
canonical 32-byte base64url digest; UTC timestamps; expiry after creation; paired revocation fields.
|
||
|
||
- [ ] **Step 5: Implement generation and verification**
|
||
|
||
Read 12 random key-ID bytes and 32 secret bytes, use `base64.RawURLEncoding`, emit the 70-byte
|
||
canonical value, hash the complete value, and compare via `subtle.ConstantTimeCompare`.
|
||
Legacy accepts 1–128 opaque bytes without ASCII controls and hashes the entire raw value.
|
||
|
||
- [ ] **Step 6: Prove GREEN and standard-library-only**
|
||
|
||
```bash
|
||
cd tools/dwh-auth
|
||
gofmt -w internal/credential internal/record
|
||
go test ./internal/credential ./internal/record -count=1
|
||
go list -deps ./... | grep -v '^github.com/aritmolab/thothii/tools/dwh-auth' | grep '\.'
|
||
```
|
||
|
||
Expected: tests pass; dependency scan exits 1 with no third-party path.
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
git add tools/dwh-auth/go.mod tools/dwh-auth/internal/credential tools/dwh-auth/internal/record
|
||
git commit -m "feat: define DWH installation credentials"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: Implement the protected atomic registry
|
||
|
||
**Files:**
|
||
- Create: `tools/dwh-auth/internal/securefile/securefile_linux.go`
|
||
- Create: `tools/dwh-auth/internal/securefile/securefile_linux_test.go`
|
||
- Create: `tools/dwh-auth/internal/registry/store.go`
|
||
- Create: `tools/dwh-auth/internal/registry/store_test.go`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 1 records and key IDs.
|
||
- Produces: `registry.Open`, `Store.Add`, `Store.Find`, `Store.FindLegacy`, `Store.List`, `Store.Revoke`, and `Store.Check`.
|
||
|
||
- [ ] **Step 1: Write failing security tests**
|
||
|
||
Cover regular protected files; symlinked roots/directories/records/secret files; unsafe modes;
|
||
unknown/duplicate/trailing JSON; filename mismatch; partial files; concurrent reads; and revoked
|
||
state winning when both records exist. Use `t.TempDir` and Linux build tags.
|
||
|
||
- [ ] **Step 2: Prove RED**
|
||
|
||
```bash
|
||
cd tools/dwh-auth
|
||
go test ./internal/securefile ./internal/registry -count=1
|
||
```
|
||
|
||
- [ ] **Step 3: Implement no-follow bounded reads/writes**
|
||
|
||
Use `syscall.Open` with `O_NOFOLLOW|O_CLOEXEC`, compare `Fstat`/`Lstat`, require regular files,
|
||
reject group/world write, cap record reads at 4096 bytes, and detect size changes. Secret input is
|
||
`0600`; generated output uses `O_CREAT|O_EXCL` and `0600`.
|
||
|
||
- [ ] **Step 4: Implement state contracts**
|
||
|
||
```go
|
||
type State string
|
||
|
||
const (
|
||
StateActive State = "active"
|
||
StateRevoked State = "revoked"
|
||
)
|
||
|
||
type PublicRecord struct {
|
||
Kind record.Kind `json:"credential_kind"`
|
||
KeyID string `json:"key_id"`
|
||
InstallationID string `json:"installation_id"`
|
||
Description string `json:"description,omitempty"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||
State State `json:"state"`
|
||
RevokedAt *time.Time `json:"revoked_at,omitempty"`
|
||
RevocationReason string `json:"revocation_reason,omitempty"`
|
||
}
|
||
```
|
||
|
||
`Add` uses exclusive temp, canonical JSON plus newline, record mode `0640`, sync, rename, and
|
||
directory sync. `Revoke` publishes revoked state before removing active state. `Find` checks
|
||
revoked first.
|
||
`FindLegacy` allows at most one `legacy_raw`; multiple records are an integrity fault.
|
||
|
||
- [ ] **Step 5: Prove GREEN**
|
||
|
||
```bash
|
||
cd tools/dwh-auth
|
||
gofmt -w internal/securefile internal/registry
|
||
go test -race ./internal/securefile ./internal/registry -count=1
|
||
```
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add tools/dwh-auth/internal/securefile tools/dwh-auth/internal/registry
|
||
git commit -m "feat: add protected DWH credential registry"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: Add the secret-safe administrative CLI
|
||
|
||
**Files:**
|
||
- Create: `tools/dwh-auth/internal/command/command.go`
|
||
- Create: `tools/dwh-auth/internal/command/command_test.go`
|
||
- Create: `tools/dwh-auth/cmd/dwh-auth/main.go`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 1–2.
|
||
- Produces: `command.Run(context.Context, []string, io.Reader, io.Writer, io.Writer) int`.
|
||
|
||
- [ ] **Step 1: Write failing CLI tests**
|
||
|
||
Freeze exits 0 success, 2 unsafe invocation, 3 not found, 4 integrity/filesystem.
|
||
Prove create writes once to new `0600`; import reads `0600`; no overwrite; list/status omit digest;
|
||
revoke requires reason; relative/secret-valued flags fail; sentinel secret never enters output.
|
||
|
||
- [ ] **Step 2: Prove RED**
|
||
|
||
```bash
|
||
cd tools/dwh-auth
|
||
go test ./internal/command -count=1
|
||
```
|
||
|
||
- [ ] **Step 3: Implement only this grammar**
|
||
|
||
```text
|
||
dwh-auth --registry-root ABS key create --installation-id ID [--description TEXT] [--expires-at RFC3339] --output ABS
|
||
dwh-auth --registry-root ABS key import --legacy-raw --installation-id legacy-shared --from-file ABS
|
||
dwh-auth --registry-root ABS key list [--json]
|
||
dwh-auth --registry-root ABS key status --key-id ID [--json]
|
||
dwh-auth --registry-root ABS key revoke --key-id ID --reason TEXT
|
||
dwh-auth --registry-root ABS check [--json]
|
||
dwh-auth serve --registry-root ABS --socket ABS
|
||
```
|
||
|
||
No aliases, env fallback, interactive secret, or plaintext output. Create prints only
|
||
`created key_id=<id> installation_id=<id> output=<path>`.
|
||
|
||
- [ ] **Step 4: Implement failure rollback**
|
||
|
||
Create writes/syncs protected output before record publication and removes output if publication
|
||
fails. Import never modifies its source. Cleanup uncertainty returns exit 4 and path only.
|
||
|
||
- [ ] **Step 5: Prove GREEN**
|
||
|
||
```bash
|
||
cd tools/dwh-auth
|
||
gofmt -w cmd internal/command
|
||
go test -race ./internal/command -count=1
|
||
go test ./... -count=1
|
||
```
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add tools/dwh-auth/cmd tools/dwh-auth/internal/command
|
||
git commit -m "feat: add DWH credential administration CLI"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: Implement Unix-socket verification
|
||
|
||
**Files:**
|
||
- Create: `tools/dwh-auth/internal/service/service.go`
|
||
- Create: `tools/dwh-auth/internal/service/service_test.go`
|
||
- Modify: `tools/dwh-auth/internal/command/command.go`
|
||
- Modify: `tools/dwh-auth/internal/command/command_test.go`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 1–3.
|
||
- Produces: `service.New(*registry.Store, *log.Logger, func() time.Time) http.Handler`, `service.ListenAndServe(context.Context, Config) error`, and `GET /verify`.
|
||
|
||
- [ ] **Step 1: Write failing decision tests**
|
||
|
||
Valid v1=204/key ID; legacy=204/legacy ID; absent/duplicate/malformed/unknown/changed/expired/
|
||
revoked=401; corrupt/unsafe/multiple-legacy=503; other path=404; other method=405. Bodies empty.
|
||
Logs contain timestamp, decision code, parsed public ID only—never secret/digest/description/query.
|
||
|
||
- [ ] **Step 2: Prove RED**
|
||
|
||
```bash
|
||
cd tools/dwh-auth
|
||
go test ./internal/service -count=1
|
||
```
|
||
|
||
- [ ] **Step 3: Implement handler/listener**
|
||
|
||
Require exactly one header value and 128-byte bound. Try strict v1 then reserved legacy for non-v1.
|
||
Require canonical absolute socket/registry paths, refuse non-socket collision, remove only a proven
|
||
owned stale socket, chmod `0660`, and stop on context cancellation.
|
||
|
||
- [ ] **Step 4: Wire `serve` fail-closed**
|
||
|
||
Dispatch `serve` before administrative write access. Open registry read-only, run `Check`, and
|
||
refuse startup on malformed or unsafe state. A well-formed expired record remains valid registry
|
||
data but always authenticates as denied.
|
||
|
||
- [ ] **Step 5: Verify**
|
||
|
||
```bash
|
||
cd tools/dwh-auth
|
||
gofmt -w cmd internal/command internal/service
|
||
go test -race ./... -count=1
|
||
go vet ./...
|
||
```
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add tools/dwh-auth/cmd tools/dwh-auth/internal/command tools/dwh-auth/internal/service
|
||
git commit -m "feat: serve DWH authentication over Unix socket"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: Add reproducible packaging and hardened templates
|
||
|
||
**Files:**
|
||
- Create: `docker/dwh-auth.Dockerfile`
|
||
- Create: `scripts/build-dwh-auth.sh`
|
||
- Create: `scripts/test-dwh-auth-build-contract.sh`
|
||
- Create: `deploy/dwh-auth/dwh-auth.service`
|
||
- Create: `deploy/dwh-auth/dwh-auth.tmpfiles.conf`
|
||
- Create: `deploy/dwh-auth/nginx-http.conf.example`
|
||
- Create: `deploy/dwh-auth/nginx-dwh-location.conf.example`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 4 binary.
|
||
- Produces: static Linux amd64/arm64 outputs and generic deployment templates.
|
||
|
||
- [ ] **Step 1: Write failing contract**
|
||
|
||
Require the exact pinned builder digest from `docker/tht.Dockerfile`, no `require` in go.mod,
|
||
non-empty ELF outputs, and service directives `User=dwh-auth`, `Group=www-data`,
|
||
`SupplementaryGroups=dwh-auth`, `NoNewPrivileges=true`, `ProtectSystem=strict`,
|
||
`ProtectHome=true`, `RestrictAddressFamilies=AF_UNIX`, empty `CapabilityBoundingSet`, `UMask=0007`.
|
||
Tmpfiles creates root/active/revoked as `root:dwh-auth` mode `2750`.
|
||
|
||
- [ ] **Step 2: Prove RED**
|
||
|
||
```bash
|
||
bash scripts/test-dwh-auth-build-contract.sh
|
||
```
|
||
|
||
- [ ] **Step 3: Add static builds**
|
||
|
||
Docker runs tests, then builds only linux/amd64 and linux/arm64 with `CGO_ENABLED=0`, `-trimpath`,
|
||
`-ldflags='-s -w'`. Build script accepts `--output ABSOLUTE_DIRECTORY`, rejects unsafe paths, and
|
||
defaults to `dist/dwh-auth`.
|
||
|
||
- [ ] **Step 4: Add systemd/storage templates**
|
||
|
||
```ini
|
||
ExecStart=/usr/local/sbin/dwh-auth serve --registry-root /var/lib/dwh-auth --socket /run/dwh-auth/verify.sock
|
||
```
|
||
|
||
Service writes only `RuntimeDirectory=dwh-auth`, reads registry, and has no secret environment.
|
||
|
||
- [ ] **Step 5: Add Nginx templates**
|
||
|
||
HTTP map/zone rate key contains only remote address plus parsed public ID, never secret; rate is
|
||
20/s. Location uses Unix auth, `GET /verify`, no body, clears `X-API-Key` before PostgREST, burst
|
||
100, preserves `/dwh/`, maps auth infrastructure failure to 503.
|
||
|
||
- [ ] **Step 6: Prove GREEN**
|
||
|
||
```bash
|
||
bash -n scripts/build-dwh-auth.sh
|
||
bash -n scripts/test-dwh-auth-build-contract.sh
|
||
bash scripts/test-dwh-auth-build-contract.sh
|
||
```
|
||
|
||
Expected final output: `dwh-auth build and deployment contract passed`.
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
git add docker/dwh-auth.Dockerfile scripts/build-dwh-auth.sh scripts/test-dwh-auth-build-contract.sh deploy/dwh-auth
|
||
git commit -m "build: package standalone DWH authentication service"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: Gate Nginx and CI behavior
|
||
|
||
**Files:**
|
||
- Create: `scripts/test-dwh-auth-nginx-integration.sh`
|
||
- Create: `scripts/test-dwh-auth-nginx-contract.sh`
|
||
- Modify: `.github/workflows/deployment.yml`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 1–5.
|
||
- Produces: structural/runtime Nginx gates and `dwh-auth-linux` CI.
|
||
|
||
- [ ] **Step 1: Write failing structural checks**
|
||
|
||
Require effective (non-comment) directives:
|
||
|
||
```nginx
|
||
auth_request /_check_dwh_key;
|
||
proxy_method GET;
|
||
proxy_pass_request_body off;
|
||
proxy_set_header Content-Length "";
|
||
proxy_set_header X-API-Key $http_x_api_key;
|
||
proxy_set_header X-API-Key "";
|
||
error_page 500 =503 @dwh_auth_unavailable;
|
||
```
|
||
|
||
Negative fixtures remove each and test public verifier, TCP authenticator, PostgREST bypass, full
|
||
secret in rate key, and failure mapped to success.
|
||
|
||
- [ ] **Step 2: Prove RED**
|
||
|
||
```bash
|
||
bash scripts/test-dwh-auth-nginx-contract.sh
|
||
```
|
||
|
||
- [ ] **Step 3: Implement runtime smoke**
|
||
|
||
Use one temp root, synthetic v1/legacy keys, temp auth/Nginx Unix sockets, and a static marker behind
|
||
auth. Assert v1=200/body unchanged, legacy=200, invalid=401, revoked=401, expired=401, stopped
|
||
authenticator=503. Trap only recorded PIDs and exact temp root.
|
||
|
||
- [ ] **Step 4: Add CI job**
|
||
|
||
Checkout without credentials, Go 1.26.5 keyed by `tools/dwh-auth/go.mod`, install Ubuntu
|
||
`nginx-light`, then:
|
||
|
||
```bash
|
||
(cd tools/dwh-auth && go test -race ./... -count=1 && go vet ./...)
|
||
bash scripts/test-dwh-auth-build-contract.sh
|
||
bash scripts/test-dwh-auth-nginx-contract.sh
|
||
bash scripts/test-dwh-auth-nginx-integration.sh
|
||
```
|
||
|
||
- [ ] **Step 5: Run locally to GREEN**
|
||
|
||
Expected: all exit 0; output includes case/status only.
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add scripts/test-dwh-auth-nginx-integration.sh scripts/test-dwh-auth-nginx-contract.sh .github/workflows/deployment.yml
|
||
git commit -m "test: gate DWH authentication integration"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 7: Write and verify operator documentation
|
||
|
||
**Files:**
|
||
- Create: `docs/install/dwh-auth-server.md`
|
||
- Create: `docs/install/dwh-auth-client-enrollment.md`
|
||
- Create: `docs/install/dwh-auth-tls.md`
|
||
- Create: `docs/operations/psd-dwh-auth-rollout.md`
|
||
- Create: `docs/testing/dwh-auth-manual-acceptance.md`
|
||
- Create: `docs/testing/evidence/psd-dwh-auth-rollout-report-template.md`
|
||
- Create: `scripts/verify-dwh-auth-docs.sh`
|
||
- Create: `scripts/test-verify-dwh-auth-docs.sh`
|
||
- Modify: `docs/install/local-workspace-registry.md`
|
||
- Modify: `docs/install/server-workspace-registry.md`
|
||
- Modify: `docs/install/psd-workspace-setup.md`
|
||
- Modify: `docs/guida-utente.md`
|
||
- Modify: `docs/index.md`
|
||
- Modify: `mkdocs.yml`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 1–6.
|
||
- Produces: generic server/client/TLS manuals, PSD runbook, manual acceptance, sanitized evidence, navigation, docs gates.
|
||
|
||
- [ ] **Step 1: Write failing docs fixtures**
|
||
|
||
Require prerequisites/install/issue/deliver/GUI/headless/rotate/revoke/TLS/fingerprint/renewal/
|
||
rollback/401/503/evidence/uninstall. Reject key/digest literals, `curl -k`, disabled TLS, secrets
|
||
in env/argv, world-readable files, raw Nginx capture, and Compose coupling.
|
||
|
||
- [ ] **Step 2: Prove RED**
|
||
|
||
```bash
|
||
bash scripts/test-verify-dwh-auth-docs.sh
|
||
```
|
||
|
||
- [ ] **Step 3: Write generic manuals**
|
||
|
||
Document exact paths, identities/modes, safe commands, one key per installation, rotation
|
||
generations, optional expiry, protected delivery, GUI vault, headless `API_KEY_FILE`, `/rpc/ping`,
|
||
positive/negative checks, registry backup, revocation, troubleshooting.
|
||
|
||
- [ ] **Step 4: Write TLS/PSD/evidence documents**
|
||
|
||
Document current self-issued `.it` certificate and missing `.com` coverage; `TLS_CA_FILE`;
|
||
`openssl x509 -noout -fingerprint -sha256`; out-of-band confirmation; renewal order; never bypass
|
||
verification. PSD runbook mirrors Tasks 9–10 and excludes legacy test sessions/indexes/caches.
|
||
Evidence contains public IDs, paths, modes, timestamps, checksums, statuses, owners, approvals only.
|
||
|
||
- [ ] **Step 5: Link existing manuals**
|
||
|
||
Link local/server workspace docs, PSD setup, user guide, docs index, MkDocs. Explicitly state
|
||
`postgres_direct` and `ssh_tunnel` do not use these keys.
|
||
|
||
- [ ] **Step 6: Prove GREEN**
|
||
|
||
```bash
|
||
bash scripts/test-verify-dwh-auth-docs.sh
|
||
bash scripts/verify-dwh-auth-docs.sh
|
||
bash scripts/test-verify-workspace-install-docs.sh
|
||
bash scripts/auth-docs-smoke.sh
|
||
```
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
git add docs/install/dwh-auth-server.md docs/install/dwh-auth-client-enrollment.md docs/install/dwh-auth-tls.md docs/operations/psd-dwh-auth-rollout.md docs/testing/dwh-auth-manual-acceptance.md docs/testing/evidence/psd-dwh-auth-rollout-report-template.md scripts/verify-dwh-auth-docs.sh scripts/test-verify-dwh-auth-docs.sh docs/install/local-workspace-registry.md docs/install/server-workspace-registry.md docs/install/psd-workspace-setup.md docs/guida-utente.md docs/index.md mkdocs.yml
|
||
git commit -m "docs: explain per-installation DWH access"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: Run source and portability checkpoint
|
||
|
||
**Files:**
|
||
- Modify only after a focused test fails: files from Tasks 1–7.
|
||
- Record locally: `.artifacts/dwh-auth/source-verification.md`.
|
||
|
||
**Interfaces:**
|
||
- Consumes: complete candidate.
|
||
- Produces: reviewed clean SHA eligible for PSD mutation gate.
|
||
|
||
- [ ] **Step 1: Run focused gates**
|
||
|
||
```bash
|
||
(cd tools/dwh-auth && go test -race ./... -count=1 && go vet ./...)
|
||
bash scripts/test-dwh-auth-build-contract.sh
|
||
bash scripts/test-dwh-auth-nginx-contract.sh
|
||
bash scripts/test-dwh-auth-nginx-integration.sh
|
||
bash scripts/test-verify-dwh-auth-docs.sh
|
||
```
|
||
|
||
- [ ] **Step 2: Run portability regressions**
|
||
|
||
```bash
|
||
bash scripts/test-default-compose.sh
|
||
bash scripts/test-unified-compose.sh
|
||
bash scripts/test-no-deployment-coupling-scope.sh
|
||
bash scripts/test-no-deployment-coupling.sh
|
||
bash scripts/test-compose-secret-policy.sh
|
||
bash scripts/test-verify-workspace-install-docs.sh
|
||
(cd tools/tht && go test ./... -count=1)
|
||
```
|
||
|
||
Expected: all pass; Compose unchanged; `tht` has no DWH-key command; Mac/Windows need no new binary.
|
||
|
||
- [ ] **Step 3: Check scope and leaks**
|
||
|
||
```bash
|
||
git diff --check
|
||
git status --short
|
||
git log --oneline --decorate -8
|
||
rg -n --hidden --glob '!.git/**' --glob '!docs/superpowers/**' 'legacy-shared\.[A-Za-z0-9_-]|thtdwh_v1\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{43}' .
|
||
```
|
||
|
||
Expected: whitespace clean; only intended evidence untracked; secret scan exits 1; reviewed commits.
|
||
|
||
- [ ] **Step 4: Terra review**
|
||
|
||
Give Terra design, plan, diff, tests. Require requirement matrix and severity-ranked findings.
|
||
Resolve Important/Critical with focused tests and commits; record Minor.
|
||
|
||
- [ ] **Step 5: Freeze**
|
||
|
||
```bash
|
||
git rev-parse HEAD
|
||
git status --porcelain=v1
|
||
```
|
||
|
||
Expected: full SHA recorded; execution worktree clean. Do not push/merge/install here.
|
||
|
||
---
|
||
|
||
### Task 9: Install on PSD without changing public Nginx
|
||
|
||
**Files/objects:**
|
||
- Install: `/usr/local/sbin/dwh-auth`
|
||
- Install: `/etc/systemd/system/dwh-auth.service`
|
||
- Install: `/usr/lib/tmpfiles.d/dwh-auth.conf`
|
||
- Create: `/var/lib/dwh-auth/active/`, `/var/lib/dwh-auth/revoked/`, `/run/dwh-auth/verify.sock`
|
||
- Protect: `/root/dwh-auth-provision/`
|
||
- Evidence: owner-approved protected rollout directory
|
||
|
||
**Interfaces:**
|
||
- Consumes: frozen Task 8 SHA/binary.
|
||
- Produces: healthy local authenticator with legacy and Mac identities, disconnected from Nginx.
|
||
|
||
- [ ] **Step 1: Stop for explicit mutation authorization**
|
||
|
||
Present SHA, results, exact targets, rollback, legacy-stack non-impact. Without explicit approval,
|
||
stop with Activity 1 `IN_DISCUSSION`.
|
||
|
||
- [ ] **Step 2: Verify preconditions read-only**
|
||
|
||
Require `x86_64`, Nginx group `www-data`, free target names, systemd metadata, current `nginx -t`,
|
||
current `/dwh/` diagnostic, and regular root-owned `0600`
|
||
`/root/dwh-auth-provision/legacy-shared.key`. Never print/hash that key.
|
||
|
||
- [ ] **Step 3: Build/fingerprint frozen binary**
|
||
|
||
```bash
|
||
bash scripts/build-dwh-auth.sh --output /tmp/dwh-auth-release
|
||
sha256sum /tmp/dwh-auth-release/dwh-auth-linux-amd64
|
||
```
|
||
|
||
Record binary/source SHA only.
|
||
|
||
- [ ] **Step 4: Install service**
|
||
|
||
Create no-login group/user; install binary `0755`; install unit/tmpfiles `0644`; run
|
||
`systemd-tmpfiles --create dwh-auth.conf`; verify owners/modes. Do not start until `check` passes.
|
||
|
||
- [ ] **Step 5: Import legacy and issue Mac key**
|
||
|
||
Import fixed protected legacy file with `--legacy-raw --installation-id legacy-shared`. Create
|
||
non-expiring `psd-mac-primary` in root-owned `0600`
|
||
`/root/dwh-auth-provision/psd-mac-primary.key`. Capture public metadata only.
|
||
|
||
- [ ] **Step 6: Start/test disconnected service**
|
||
|
||
Run `check`, `systemd-analyze verify`, daemon-reload, enable/start, verify socket. Via protected curl
|
||
configs test new=204, legacy=204, random=401, absent=401 on Unix socket. Check bounded journal for
|
||
absence of both keys.
|
||
|
||
- [ ] **Step 7: Record checkpoint**
|
||
|
||
Retain checksums, public IDs/modes, hardening/socket metadata, statuses, rollback. Do not touch Nginx.
|
||
|
||
---
|
||
|
||
### Task 10: Roll out dual-key Nginx and close Activity 1
|
||
|
||
**Files/objects:**
|
||
- Create: `/etc/nginx/conf.d/dwh-auth-rate-limit.conf`
|
||
- Modify: `/etc/nginx/sites-available/policlinicosandonato`
|
||
- Preserve: protected pre-change copies
|
||
- Update: Mac vault or protected `API_KEY_FILE`
|
||
- Update: `docs/operations/psd-server-survey-remediation-checklist.md`
|
||
- Complete: protected rollout evidence template
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 9 checkpoint, both protected keys, CA/fingerprint, explicit Nginx approval.
|
||
- Produces: per-installation `/dwh/`, revoked legacy, verified Mac, Activity 1 PASS.
|
||
|
||
- [ ] **Step 1: Stop for separate Nginx/client gate**
|
||
|
||
Present files, backups, reload/rollback, delivery channel, observation duration, expected
|
||
204/401/503. Obtain explicit approval distinct from Task 9.
|
||
|
||
- [ ] **Step 2: Prepare candidate**
|
||
|
||
Create timestamped root-owned `0600` backups. Change only DWH auth plus rate map/zone. Preserve
|
||
upstream `http://127.0.0.1:3001/`; keep vector locations byte-identical; clear API key before
|
||
PostgREST; include no literal key.
|
||
|
||
- [ ] **Step 3: Validate before reload**
|
||
|
||
Run structural checker, secret scan without match output, bounded diff, install candidates,
|
||
`sudo nginx -t`. On failure restore backups before reload and record sanitized FAIL.
|
||
|
||
- [ ] **Step 4: Reload/prove dual-key**
|
||
|
||
Through real `.it` HTTPS and approved CA test `/dwh/rpc/ping` via protected curl configs:
|
||
legacy success, new success, random 401, absent 401. Roll back on response/TLS/unrelated health
|
||
change.
|
||
|
||
- [ ] **Step 5: Deliver/configure Mac**
|
||
|
||
Use approved protected channel. Verify CA fingerprint, configure vault or headless `API_KEY_FILE`,
|
||
run Workspace Validate, Test connections, `/rpc/ping`. Record public IDs, fingerprint confirmation,
|
||
timestamp, result only.
|
||
|
||
- [ ] **Step 6: Observe/revoke legacy**
|
||
|
||
After approved window, revoke `legacy-shared` with reason `shared-credential-rotation`. New Mac
|
||
still succeeds; legacy returns 401. Bounded logs contain no key/digest.
|
||
|
||
- [ ] **Step 7: Verify rollback scope**
|
||
|
||
Prove Nginx backups/commands readable. Do not preserve/migrate/restore/stop legacy ThothII data or
|
||
stack.
|
||
|
||
- [ ] **Step 8: Close only Activity 1**
|
||
|
||
Complete sanitized evidence. Set PASS only with new success, legacy 401, service/Nginx PASS, clean
|
||
logs, rollback, owner acceptance. Advance Current activity to 2; overall remains `SURVEY_NO_GO`.
|
||
|
||
- [ ] **Step 9: Commit non-secret status**
|
||
|
||
```bash
|
||
git add docs/operations/psd-server-survey-remediation-checklist.md PROJECT_STATE.md
|
||
git commit -m "docs: record PSD DWH credential rotation"
|
||
```
|
||
|
||
Expected: public IDs/dates/evidence references/statuses only; no keys, digests, certificate body,
|
||
connection string, raw log, or protected evidence.
|
||
|
||
---
|
||
|
||
## Final execution boundary
|
||
|
||
Task 10 closes only Activity 1. It does not authorize Project A, stop legacy ThothII, or install the
|
||
new application. Resume Activity 2 and remaining blockers before a fresh `SURVEY_GO`.
|