Files
ThothII/scripts/test-project-a-auth-runtime-projection.sh

167 lines
7.1 KiB
Bash
Executable File

#!/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."