docs(auth): document runtime projection operations
This commit is contained in:
@@ -61,6 +61,10 @@
|
||||
`rest_api` acceptance, the 48-hour/two-ETL observation, and revocation of `legacy-shared` are
|
||||
mandatory before `SURVEY_GO_PROJECT_B`. Current authorization covers read-only survey and
|
||||
preparation only; old-stack stop and new-stack start remain forbidden.
|
||||
- **Runtime-projection preparation (2026-08-22):** the source contract and hermetic gates prepare
|
||||
root-only canonical authentication plus a separate `10001:10001` read-only core projection.
|
||||
This is implementation preparation and test evidence only. Project A has not been started;
|
||||
applying its descriptor or any server runtime root still needs explicit authorization.
|
||||
|
||||
### Authentication final-review fix round 2 — remediation PASS, release gates remain (2026-08-18)
|
||||
|
||||
|
||||
@@ -68,3 +68,14 @@ values into tickets, logs, or evidence.
|
||||
|
||||
Check readiness with `tht auth check`; add `--json` for the machine contract. Use
|
||||
`tht doctor --json` for the aggregate installation report.
|
||||
|
||||
## Projected server installations
|
||||
|
||||
This section applies only when a Linux `profile: server` descriptor declares a runtime projection.
|
||||
The canonical authentication root stays root-owned and is the only authority. The container reads
|
||||
only the separate read-only runtime projection selected by `CURRENT`; it never falls back to the
|
||||
canonical files or to a previous generation. Run projected mutations and repairs through the
|
||||
root-operated `tht` commands documented in the [server guide](server.md), and never edit runtime
|
||||
files directly.
|
||||
|
||||
Mac, Windows, and local direct-file authentication remain unchanged when the projection is absent.
|
||||
|
||||
@@ -8,7 +8,13 @@ workspaceRepository:
|
||||
branch: main
|
||||
access: ssh
|
||||
authentication:
|
||||
configDirectory: "/absolute/path/to/thothii-auth"
|
||||
# Root-operated source of truth; it is never mounted into core.
|
||||
configDirectory: "/srv/example/thothii/auth-canonical"
|
||||
runtimeProjection:
|
||||
# The only authentication bind exposed to core by the automatic override.
|
||||
directory: "/srv/example/thothii/auth-runtime"
|
||||
uid: 10001
|
||||
gid: 10001
|
||||
overrides:
|
||||
- "/absolute/path/to/ThothII/deploy/compose.session-server.yaml.example"
|
||||
- "/absolute/path/to/ThothII/deploy/compose.git-ssh.yaml"
|
||||
|
||||
@@ -88,6 +88,73 @@ Expected: the parent is `operator_uid:10001 750`; source/operator are
|
||||
`10001:10001 750`; backups are `0:0 700`. Re-run the empty `getent` checks after creation. Do not
|
||||
make `/srv/thothii` a shared application directory.
|
||||
|
||||
## Projected server authentication: canonical root and runtime projection
|
||||
|
||||
For a server descriptor that declares `authentication.runtimeProjection`, authentication has two
|
||||
different roots. The **canonical authentication root** (`authentication.configDirectory`) is the
|
||||
root-operated source of truth. It and its regular files are `root:root 0700/0600`. The **runtime
|
||||
projection** is a separate Linux-only tree for the container reader: its root, `generations`, and
|
||||
generation directories are `10001:10001 0700`; `CURRENT`, `manifest.json`, `auth.yaml`, and (for
|
||||
local mode) `users.yaml` are `10001:10001 0600`. The publisher assigns the numeric IDs directly;
|
||||
it does not create a host user or group for 10001.
|
||||
|
||||
The runtime projection has only `CURRENT` and `generations/<64-lowercase-hex>/`. `CURRENT` selects
|
||||
one complete immutable generation. A successful configure, user mutation, restore, or explicit
|
||||
publish first blocks `CURRENT`, then verifies a new immutable generation, then makes it ready.
|
||||
The selected generation and up to two predecessor generations are retained; no operator edits a
|
||||
generation or `CURRENT` directly. A ready projection is usable only when its canonical revision is
|
||||
equal to the current canonical authentication root. If the runtime projection is blocked, missing,
|
||||
tampered, or unequal, `start`, `update --check-only`, `auth check`, and `doctor` fail closed before
|
||||
admission or Compose lifecycle work.
|
||||
|
||||
The runtime directory must be an absolute canonical path, distinct from the canonical root, and
|
||||
must exactly equal `THT_AUTH_RUNTIME_ROOT` in the protected installation environment. The server
|
||||
profile and numeric UID/GID values are validated before any projected mutation or publication.
|
||||
|
||||
The descriptor loader adds `compose.auth-runtime-projection.yaml` automatically when
|
||||
`runtimeProjection` is present; do not list that file under `overrides`. The automatic override
|
||||
mounts the runtime projection **read-only and core-only** at `/run/thothii-auth`; the canonical
|
||||
authentication root is never mounted. No other service receives that mount or
|
||||
`THT_AUTH_RUNTIME_PROJECTION_ROOT`. The example descriptor uses
|
||||
`/srv/example/thothii/auth-runtime` only as a replaceable path and contains no credential value.
|
||||
|
||||
This source change is prepared and tested only: Project A has not been started. It does not
|
||||
authorize a raw Compose lifecycle launch, an Nginx change, legacy-stack change, or mutation of
|
||||
`/srv`. A later manual gate needs separate explicit authorization before applying any descriptor
|
||||
or runtime root to a server.
|
||||
|
||||
### Status, repair, and safe evidence
|
||||
|
||||
Use the root-operated installation command; retain only its small redacted JSON result:
|
||||
|
||||
```sh
|
||||
sudo tht --installation "$INSTALLATION" auth status --json
|
||||
```
|
||||
|
||||
`state: "ready"` and `equal: true` are required before a projected server can start. `state:
|
||||
"blocked"`, `equal: false`, or a command refusal means that the runtime projection is blocked or
|
||||
cannot be validated. Do not start the stack, inspect YAML, print an environment, or edit `CURRENT`
|
||||
or a generation. Confirm the protected canonical root is available, then republish it with:
|
||||
|
||||
```sh
|
||||
sudo tht --installation "$INSTALLATION" auth publish
|
||||
sudo tht --installation "$INSTALLATION" auth status --json
|
||||
```
|
||||
|
||||
`auth publish` reconstructs the selected immutable generation from the canonical root; it never
|
||||
uses an older runtime generation as authority. If publish fails, leave the projection blocked and
|
||||
escalate using the sanitized command result plus descriptor path and timestamp only. Do not attach
|
||||
passwords, hashes, YAML, raw environment output, `nginx -T`, or a secret-bearing diff to evidence.
|
||||
|
||||
### Authentication restore
|
||||
|
||||
An authentication-bearing restore first publishes a blocked selector, restores canonical
|
||||
authentication, and publishes a verified candidate generation before any restart. If candidate or
|
||||
recovery verification fails, the verified recovery checkpoint is republished when possible; an
|
||||
unverified result remains blocked and prevents start. A restore without authentication entries
|
||||
does not touch the runtime projection. This is in addition to the normal restore requirement that
|
||||
browser sessions and pending OIDC state are cleared.
|
||||
|
||||
## Firewall and network boundaries
|
||||
|
||||
Set `THOTH_SERVER_BIND=127.0.0.1`. Permit inbound TCP 80/443 only to the TLS proxy; port 80 should
|
||||
|
||||
@@ -251,6 +251,14 @@ Use `profile: server`, the exact new source root/env/auth root, workspace remote
|
||||
access, Project A override, and exactly one Git transport override. Do not include the public
|
||||
session-server overlay in Project A.
|
||||
|
||||
For the projected local-auth descriptor, keep `authentication.configDirectory` as the canonical
|
||||
root and add `runtimeProjection` with a distinct absolute runtime directory plus numeric `uid: 10001`
|
||||
and `gid: 10001`. The descriptor loader includes the automatic runtime-projection override; do
|
||||
not list it manually under `overrides`. The canonical root stays `root:root 0700/0600`; the
|
||||
publisher owns the projection numerically as `10001:10001 0700/0600`. Only the projection is
|
||||
mounted read-only into core. Do not create a host user/group, edit `CURRENT` or `generations`, or
|
||||
apply these paths before the separately authorized start gate.
|
||||
|
||||
**Step 4: Validate permissions and render**
|
||||
|
||||
```bash
|
||||
@@ -274,6 +282,11 @@ Create a temporary mode-0600 password file using an echo-free prompt, then run:
|
||||
Remove the temporary input file after success and record that removal. Do not delete generated
|
||||
`auth.yaml` or `users.yaml`.
|
||||
|
||||
Before the later start gate, run the redacted projected status command and require `ready` plus
|
||||
`equal: true`. A blocked result prevents start. `auth publish` is the only repair path: it rebuilds
|
||||
from canonical authentication, including after a candidate or recovery restore outcome; it never
|
||||
promotes a retained runtime generation on its own.
|
||||
|
||||
### Task 6: Build and start the clean stack
|
||||
|
||||
**Files:**
|
||||
|
||||
@@ -35,6 +35,13 @@ than inferring a PASS.
|
||||
4. Confirm the exact direct `groups` claim for both identities and the mappings `TOT Users → user`
|
||||
and `TOT Admin → admin`. Confirm extra upstream groups are ignored without warning.
|
||||
|
||||
5. For a projected Linux server, before any start gate, collect only the redacted result of
|
||||
`sudo tht --installation "$INSTALLATION" auth status --json`. Record `state`, generation,
|
||||
canonical revision, and `equal`; do not retain authentication YAML, user records, hashes, or
|
||||
environment output. `ready` plus `equal: true` is required. A blocked or unequal result is a
|
||||
fail-closed condition: do not start, and use `sudo tht --installation "$INSTALLATION" auth
|
||||
publish` followed by the same status command only after the canonical root is available.
|
||||
|
||||
## Matrix
|
||||
|
||||
| Scenario | Expected result |
|
||||
@@ -56,6 +63,7 @@ than inferring a PASS.
|
||||
| Logout | Cookie expires and the server session is deleted. |
|
||||
| Provider outage | Live check reports `oidc_discovery_unreachable`; browser login fails closed without exposing credentials. |
|
||||
| Restore is completed | Sessions and OIDC state are absent; all users must reauthenticate. |
|
||||
| Projected authentication restore | Candidate generation and any recovery generation are published from the canonical root; a failed verification remains blocked and start is refused. |
|
||||
|
||||
## Status at Task 15
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ verdi installazione, workspace, DWH, Qdrant, Ollama e preprocessing.
|
||||
|
||||
## 1. Stato generale
|
||||
|
||||
Prima del gate manuale di avvio, per una descriptor server con runtime projection eseguire solo il
|
||||
controllo redatto `sudo tht --installation "$INSTALLATION" auth status --json`. Il risultato deve
|
||||
dire `ready` ed `equal: true`. Se è `blocked`, mancante o diverso dal canonical root, non avviare:
|
||||
Sol può eseguire `sudo tht --installation "$INSTALLATION" auth publish` e ripetere il controllo,
|
||||
senza copiare YAML, hash, password, token o environment nel rapporto. Questo documento non
|
||||
autorizza l'avvio; Project A resta soggetto a un'esplicita autorizzazione separata.
|
||||
|
||||
Eseguire:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -157,4 +157,51 @@ if rg -n -i --pcre2 '(?:unmapped|additional|extra)[^.\r\n]{0,160}groups?[^.\r\n]
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Projected server authentication documentation contract.
|
||||
projection_docs=(
|
||||
"$root/docs/install/server.md"
|
||||
"$root/docs/install/authentication-local.md"
|
||||
"$root/docs/testing/authentication-manual-acceptance.md"
|
||||
"$root/docs/testing/psd-server-project-a-manual.md"
|
||||
"$root/docs/plans/2026-08-20-psd-server-project-a-standalone.md"
|
||||
"$root/PROJECT_STATE.md"
|
||||
)
|
||||
projection_corpus=$(mktemp)
|
||||
trap 'rm -f "$corpus" "$projection_corpus"' EXIT
|
||||
cat "${projection_docs[@]}" >"$projection_corpus"
|
||||
projection_required=(
|
||||
"canonical authentication root" "runtime projection" "CURRENT" "generations"
|
||||
"root:root 0700/0600" "10001:10001 0700/0600"
|
||||
"THT_AUTH_RUNTIME_ROOT"
|
||||
"auth status --json" "auth publish"
|
||||
"candidate or recovery" "Mac, Windows, and local direct-file authentication"
|
||||
"explicit authorization"
|
||||
)
|
||||
for term in "${projection_required[@]}"; do
|
||||
rg -Fqi "$term" "$projection_corpus" || {
|
||||
echo "auth docs smoke: missing runtime-projection term: $term" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
if rg -n -i --pcre2 "(?:useradd|groupadd)[^\n]{0,100}10001|10001[^\n]{0,100}(?:useradd|groupadd)" "$projection_corpus"; then
|
||||
echo "auth docs smoke: runtime projection creates a host 10001 identity" >&2
|
||||
exit 1
|
||||
fi
|
||||
if rg -n -i --pcre2 '(?:^|[.!?]\s+)(?:(?:mount|bind)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b[^\n]{0,120}(?:core|/run/thothii-auth)|(?:core|the core service)\s+(?:mounts?|binds?)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b)|auth-canonical[^\n]{0,120}:/run/thothii-auth' "$projection_corpus"; then
|
||||
echo "auth docs smoke: canonical authentication is mounted into core" >&2
|
||||
exit 1
|
||||
fi
|
||||
if rg -n -i --pcre2 '(?:(?:operators?\s+)?(?:may|can|should|must)\s+(?!not\b|never\b)(?:edit|write|modify|prune)|^\s*(?:sudo\s+)?(?:vi|vim|nano|sed|cp|mv|rm|tee|printf|echo)\b)[^\n]*(?:CURRENT|generations)' "$projection_corpus"; then
|
||||
echo "auth docs smoke: runtime projection permits direct selector or generation edits" >&2
|
||||
exit 1
|
||||
fi
|
||||
if rg -n -i --pcre2 '(?:^\s*(?:sudo\s+nginx\s+-T|(?:sudo\s+)?(?:printenv|env)(?:\s|$)|(?:sudo\s+)?(?:diff|cat|less|more|head|tail|yq)\s+[^\n]*(?:secret|auth\.yaml|users\.yaml))|(?:may|can|should|must)\s+(?!not\b|never\b)(?:dump|print|capture|attach|include|run)\b[^\n]*(?:password|auth\.yaml|users\.yaml|YAML|raw environment|nginx\s+-T|secret-bearing diff))' "$projection_corpus"; then
|
||||
echo "auth docs smoke: runtime projection documents unsafe evidence collection" >&2
|
||||
exit 1
|
||||
fi
|
||||
if rg -n -i --pcre2 'Project A (?:has been|was|is) started|legacy[- ]stack (?:has been|was|is) changed' "$projection_corpus"; then
|
||||
echo "auth docs smoke: runtime projection claims live Project A or legacy mutation" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "auth docs smoke: required terms and forbidden wording checks passed"
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# Hermetic acceptance gate for the server authentication runtime projection.
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
|
||||
root="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
forbidden_server_root="/$(printf '%s' srv)/"
|
||||
tmp_base="$(cd "${TMPDIR:-/tmp}" && pwd -P)"
|
||||
if [[ "$tmp_base/" == "$forbidden_server_root"* ]]; then
|
||||
echo "runtime projection gate refuses a server temp root" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${DOCKER_HOST:-}" ]]; then
|
||||
echo "runtime projection gate refuses a remote Docker endpoint" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker_context="$(docker context show)"
|
||||
docker_endpoint="$(docker context inspect "$docker_context" --format '{{(index .Endpoints "docker").Host}}')"
|
||||
if [[ "$docker_endpoint" != unix:///* ]]; then
|
||||
echo "runtime projection gate requires a local Unix Docker endpoint" >&2
|
||||
exit 1
|
||||
fi
|
||||
tmp="$(mktemp -d "$tmp_base/thoth-auth-runtime-projection.XXXXXX")"
|
||||
chmod 0700 "$tmp"
|
||||
mkdir -m 0700 "$tmp/go-mod" "$tmp/go-build"
|
||||
|
||||
cleanup() {
|
||||
local cleanup_status=0
|
||||
if [[ -d "$tmp" ]]; then
|
||||
docker run --rm --network none -v "$tmp:/cleanup" golang:1.26.5 \
|
||||
sh -c 'find /cleanup -mindepth 1 -delete' >/dev/null 2>&1 || cleanup_status=$?
|
||||
if ((cleanup_status == 0)); then
|
||||
rmdir "$tmp" || cleanup_status=$?
|
||||
fi
|
||||
fi
|
||||
return "$cleanup_status"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 129' HUP
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
project_name_flag="--project"-name
|
||||
compose_project_var='COMPOSE_PROJECT'_NAME
|
||||
network_flag="--net"work
|
||||
compose_pattern='docker[[:space:]]+com''pose'
|
||||
if rg -n -F -- "$forbidden_server_root" "$0"; then
|
||||
echo "runtime projection gate must not reference a server path" >&2
|
||||
exit 1
|
||||
fi
|
||||
if rg -n -- "$project_name_flag|$compose_project_var|${compose_pattern}[^[:cntrl:]]*(up|start)|docker[[:space:]]+network" "$0"; then
|
||||
echo "runtime projection gate has a forbidden project, lifecycle, or network attachment" >&2
|
||||
exit 1
|
||||
fi
|
||||
if rg -n -F -- "$network_flag" "$0" | rg -v -F -e "$network_flag none" -e "$network_flag=none"; then
|
||||
echo "runtime projection gate permits only an explicitly isolated network option" >&2
|
||||
exit 1
|
||||
fi
|
||||
if rg -n --pcre2 '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*(?:PASSWORD|SECRET|TOKEN|KEY)[A-Za-z_]*=' "$0"; then
|
||||
echo "runtime projection gate declares a credential value" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node24_source="$(
|
||||
env -i PATH="$PATH" HOME="$HOME" npm_config_offline=true npm_config_userconfig=/dev/null \
|
||||
npx -y -p node@24 node -p 'process.execPath'
|
||||
)"
|
||||
if [[ "$node24_source" != /* || ! -f "$node24_source" || ! -x "$node24_source" ]]; then
|
||||
echo "runtime projection gate requires a cached Node 24 runtime" >&2
|
||||
exit 1
|
||||
fi
|
||||
install -m 0755 "$node24_source" "$tmp/node24"
|
||||
|
||||
run_case() {
|
||||
local name="$1"
|
||||
shift
|
||||
local output="$tmp/$name.log"
|
||||
if ! "$@" >"$output" 2>&1; then
|
||||
echo "$name FAIL (test output suppressed and removed by cleanup)" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s PASS\n' "$name"
|
||||
}
|
||||
|
||||
go_test() {
|
||||
docker run --rm \
|
||||
-v "$root:/work:ro" \
|
||||
-v "$tmp/go-mod:/go/pkg/mod" \
|
||||
-v "$tmp/go-build:/root/.cache/go-build" \
|
||||
-w /work/tools/tht golang:1.26.5 go test "$@"
|
||||
}
|
||||
|
||||
go_windows_compile() {
|
||||
docker run --rm \
|
||||
-v "$root:/work:ro" \
|
||||
-v "$tmp/go-mod:/go/pkg/mod" \
|
||||
-v "$tmp/go-build:/root/.cache/go-build" \
|
||||
-w /work/tools/tht -e GOOS=windows -e GOARCH=amd64 golang:1.26.5 \
|
||||
go test -c ./cmd/tht -o /tmp/tht.test.exe
|
||||
}
|
||||
|
||||
go_darwin_compile() {
|
||||
docker run --rm \
|
||||
-v "$root:/work:ro" \
|
||||
-v "$tmp/go-mod:/go/pkg/mod" \
|
||||
-v "$tmp/go-build:/root/.cache/go-build" \
|
||||
-w /work/tools/tht -e GOOS=darwin -e GOARCH=amd64 golang:1.26.5 \
|
||||
go test -c ./cmd/tht -o /tmp/tht.test
|
||||
}
|
||||
|
||||
backend_node24() {
|
||||
docker run --rm --network none \
|
||||
-e HOME=/tmp \
|
||||
-v "$root:/work:ro" \
|
||||
-v "$tmp/node24:/opt/node24:ro" \
|
||||
-v "$root/backend/vitest.config.ts:/opt/vitest.config.ts:ro" \
|
||||
-v "$root/backend/node_modules:/opt/node_modules:ro" \
|
||||
-w /work/backend golang:1.26.5 \
|
||||
/opt/node24 node_modules/vitest/vitest.mjs run --no-cache \
|
||||
--config /opt/vitest.config.ts "$@"
|
||||
}
|
||||
|
||||
backend_in_flight() {
|
||||
backend_node24 test/auth-runtime-projection.test.ts \
|
||||
-t 'in-flight snapshot authenticates A after selection B and deletion A'
|
||||
}
|
||||
|
||||
backend_direct_file() {
|
||||
backend_node24 test/config.test.ts \
|
||||
-t 'keeps the direct auth-file provider when the runtime projection environment is absent'
|
||||
}
|
||||
|
||||
mac_windows_direct_file() {
|
||||
go_windows_compile
|
||||
go_darwin_compile
|
||||
backend_direct_file
|
||||
}
|
||||
|
||||
run_case descriptor_requires_server_uid_gid_and_matching_env \
|
||||
go_test ./internal/config -run 'TestLoadAcceptsServerRuntimeProjectionAndPlacesAutomaticOverrideBeforeCurrentImage|TestLoadRejectsInvalidRuntimeProjection|TestLoadRejectsRuntimeProjectionEnvironmentWithoutDescriptor' -count=1
|
||||
run_case compose_mount_is_core_only_read_only_and_noncanonical \
|
||||
bash "$root/scripts/test-auth-runtime-projection-compose.sh"
|
||||
run_case local_initial_configure_publishes_equal_ready \
|
||||
go_test ./internal/setup ./internal/authconfig -run 'TestRunConfigureOnlyPublishesInitialProjectedServerAuthentication|TestRunProjectedMutationPublishesExactLocalAndOIDCSnapshots/local' -count=1
|
||||
run_case oidc_initial_configure_publishes_equal_ready \
|
||||
go_test ./internal/authconfig -run 'TestRunProjectedMutationPublishesExactLocalAndOIDCSnapshots/oidc' -count=1
|
||||
run_case every_user_mutation_blocks_then_publishes \
|
||||
go_test ./internal/authconfig -run 'TestProjectedAuthMutatorsBlockBeforeCanonicalWriteAndPublishOnlyEqualSnapshots' -count=1
|
||||
run_case publish_repairs_blocked_from_canonical \
|
||||
go_test ./internal/authconfig -run 'TestPublishProjectedCanonicalRepairsBlockedStateFromCanonicalOnly|TestProjectedAuthPublishStatusAndCheckFailClosed' -count=1
|
||||
run_case start_and_doctor_fail_closed_for_missing_blocked_tampered_or_divergent \
|
||||
go_test ./internal/authprojection ./internal/authconfig ./internal/service ./internal/doctor ./cmd/tht -run 'TestInspectRejectsMissingBlockedMalformedAndTamperedCurrent|TestRequireRuntimeAuthProjectionReadyRejectsMissingBlockedAndDivergentStates|TestStartRefusesProjectedAuthenticationBeforeComposeWhenNotReady|TestRunReportsOneSanitizedRuntimeAuthProjectionFailureBeforeCompose|TestRunUpdateCheckOnlyRefusesProjectedAuthenticationBeforeCompose' -count=1
|
||||
run_case backend_authenticates_from_one_immutable_generation_after_previous_gc backend_in_flight
|
||||
run_case auth_restore_publishes_candidate \
|
||||
go_test ./internal/backup -run 'TestRestoreAuthBearingArchiveBlocksBeforeWritePublishesBeforeRestart' -count=1
|
||||
run_case failed_candidate_verification_republishes_checkpoint \
|
||||
go_test ./internal/backup -run 'TestRestoreAuthVerificationFailuresRepublishCheckpointBeforeRecoveryRestart' -count=1
|
||||
run_case failed_recovery_publish_remains_blocked \
|
||||
go_test ./internal/backup -run 'TestRestoreAuthCandidatePublicationFailureRecoversThenStaysBlockedWithoutRestart' -count=1
|
||||
run_case non_auth_restore_never_touches_projection \
|
||||
go_test ./internal/backup -run 'TestRestoreNonAuthArchiveNeverBeginsProjection' -count=1
|
||||
run_case mac_windows_local_regression mac_windows_direct_file
|
||||
run_case secret_redaction \
|
||||
go_test ./internal/authconfig -run 'TestProjectionCoordinatorErrorsAndLogsNeverContainSyntheticPasswordsOrHashes' -count=1
|
||||
|
||||
echo "runtime authentication projection acceptance gate passed."
|
||||
@@ -1106,4 +1106,89 @@ if (( negative_failures != 0 )); then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Projected server authentication documents must preserve the root-only canonical/runtime split.
|
||||
projection_example="$root/docs/install/examples/thothii-installation.server.yaml"
|
||||
for required in \
|
||||
'runtimeProjection:' \
|
||||
'directory: "/srv/example/thothii/auth-runtime"' \
|
||||
'uid: 10001' \
|
||||
'gid: 10001'; do
|
||||
grep -Fq -- "$required" "$projection_example" || {
|
||||
echo "server authentication projection example lacks: $required" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
if rg -n -i --pcre2 '(?:password|secret)[[:space:]]*:[[:space:]]*[^<#[:space:]]+' "$projection_example"; then
|
||||
echo "server authentication projection example contains a credential value" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
projection_docs=(
|
||||
"$root/docs/install/server.md"
|
||||
"$root/docs/install/authentication-local.md"
|
||||
"$root/docs/testing/authentication-manual-acceptance.md"
|
||||
"$root/docs/testing/psd-server-project-a-manual.md"
|
||||
"$root/docs/plans/2026-08-20-psd-server-project-a-standalone.md"
|
||||
"$root/PROJECT_STATE.md"
|
||||
)
|
||||
projection_corpus="$negative_root/projection-corpus.md"
|
||||
cat "${projection_docs[@]}" >"$projection_corpus"
|
||||
|
||||
projection_documentation_is_safe() {
|
||||
local corpus="$1"
|
||||
if rg -q -i --pcre2 '(?:useradd|groupadd)[^\n]{0,100}10001|10001[^\n]{0,100}(?:useradd|groupadd)' "$corpus"; then
|
||||
return 1
|
||||
fi
|
||||
if rg -q -i --pcre2 '(?:^|[.!?]\s+)(?:(?:mount|bind)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b[^\n]{0,120}(?:core|/run/thothii-auth)|(?:core|the core service)\s+(?:mounts?|binds?)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b)|auth-canonical[^\n]{0,120}:/run/thothii-auth' "$corpus"; then
|
||||
return 1
|
||||
fi
|
||||
if rg -q -i --pcre2 '(?:(?:operators?\s+)?(?:may|can|should|must)\s+(?!not\b|never\b)(?:edit|write|modify|prune)|^\s*(?:sudo\s+)?(?:vi|vim|nano|sed|cp|mv|rm|tee|printf|echo)\b)[^\n]*(?:CURRENT|generations)' "$corpus"; then
|
||||
return 1
|
||||
fi
|
||||
if rg -q -i --pcre2 '(?:^\s*(?:sudo\s+nginx\s+-T|(?:sudo\s+)?(?:printenv|env)(?:\s|$)|(?:sudo\s+)?(?:diff|cat|less|more|head|tail|yq)\s+[^\n]*(?:secret|auth\.yaml|users\.yaml))|(?:may|can|should|must)\s+(?!not\b|never\b)(?:dump|print|capture|attach|include|run)\b[^\n]*(?:password|auth\.yaml|users\.yaml|YAML|raw environment|nginx\s+-T|secret-bearing diff))' "$corpus"; then
|
||||
return 1
|
||||
fi
|
||||
if rg -q -i --pcre2 'Project A (?:has been|was|is) started|legacy[- ]stack (?:has been|was|is) changed' "$corpus"; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
for required in \
|
||||
'canonical authentication root' \
|
||||
'read-only and core-only' \
|
||||
'candidate or recovery' \
|
||||
'runtime projection is blocked' \
|
||||
'Project A has not been started'; do
|
||||
if ! rg -Fqi -- "$required" "${projection_docs[@]}"; then
|
||||
echo "server authentication projection documentation lacks: $required" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if ! projection_documentation_is_safe "$projection_corpus"; then
|
||||
echo "server authentication projection documentation contains an unsafe instruction or claim" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while IFS='|' read -r fixture_name payload; do
|
||||
fixture="$negative_root/projection-$fixture_name.md"
|
||||
cp "$projection_corpus" "$fixture"
|
||||
printf '\n%s\n' "$payload" >>"$fixture"
|
||||
if projection_documentation_is_safe "$fixture"; then
|
||||
echo "server authentication projection documentation accepted unsafe fixture: $fixture_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
done <<'PROJECTION_NEGATIVE_FIXTURES'
|
||||
host-identity|sudo useradd --system --uid 10001 thothii
|
||||
canonical-core-mount|Mount the canonical authentication root into core at /run/thothii-auth.
|
||||
core-subject-canonical-mount|Core mounts the canonical authentication root at /run/thothii-auth.
|
||||
direct-runtime-edit|Operators may edit CURRENT and prune generations directly.
|
||||
yaml-dump|Operators may dump auth.yaml and users.yaml into evidence.
|
||||
nginx-dump|sudo nginx -T
|
||||
raw-environment|printenv
|
||||
sudo-raw-environment|sudo printenv
|
||||
secret-diff|sudo diff auth.yaml auth.yaml.previous
|
||||
live-claim|Project A has been started.
|
||||
PROJECTION_NEGATIVE_FIXTURES
|
||||
|
||||
echo "unsafe installation-document fixtures rejected passed"
|
||||
|
||||
Reference in New Issue
Block a user