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