diff --git a/scripts/unified-deployment-smoke.sh b/scripts/unified-deployment-smoke.sh index 78f3eb1c..7e2e9661 100755 --- a/scripts/unified-deployment-smoke.sh +++ b/scripts/unified-deployment-smoke.sh @@ -944,7 +944,7 @@ task13_configure_local_authentication() { } task13_configure_server_oidc_authentication() { - task13_run_logged "configure fake server OIDC authentication" sudo -n -- "$TASK13_THT" --installation "$TASK13_INSTALLATION" auth configure \ + task13_run_logged "configure fake server OIDC authentication" task13_server_tht auth configure \ --mode oidc --public-url "https://task13.example.invalid" \ --issuer "https://task13-fake-oidc:9443/application/o/task13/" --client-id task13-smoke-client \ --authentik-base-url "https://task13-fake-oidc:9443" --user-group task13-users --admin-group task13-admins @@ -965,6 +965,32 @@ task13_prepare_server_auth_roots() { install -d -o 10001 -g 10001 -m 0700 -- "$TASK13_AUTH_RUNTIME_ROOT" } +task13_prepare_server_secret_sources() { + local path + [[ "${TASK13_PROFILE:-}" == server && -n "${TASK13_TMP:-}" ]] \ + || task13_fail "refusing to prepare server secret sources outside the server fixture" + for path in \ + "$TASK13_SECRETS" \ + "$TASK13_PI_AUTH" \ + "$TASK13_SESSION_RUNTIME_PASSWORD" \ + "$TASK13_SESSION_MIGRATOR_PASSWORD_FILE" \ + "$TASK13_SESSION_CA"; do + [[ "$path" == "$TASK13_TMP/"* && -f "$path" && ! -L "$path" ]] \ + || task13_fail "refusing to prepare an unexpected server secret source" + done + task13_run_logged "assign server secret sources to the container UID" sudo -n -- \ + chown 10001:10001 -- "$TASK13_SECRETS" "$TASK13_PI_AUTH" \ + "$TASK13_SESSION_RUNTIME_PASSWORD" "$TASK13_SESSION_MIGRATOR_PASSWORD_FILE" \ + "$TASK13_SESSION_CA" + task13_run_logged "protect server secret sources" sudo -n -- chmod 0600 -- \ + "$TASK13_SECRETS" "$TASK13_PI_AUTH" "$TASK13_SESSION_RUNTIME_PASSWORD" \ + "$TASK13_SESSION_MIGRATOR_PASSWORD_FILE" "$TASK13_SESSION_CA" +} + +task13_server_tht() { + sudo -n -- "$TASK13_THT" --installation "$TASK13_INSTALLATION" "$@" +} + task13_prepare_local_auth_runtime() { local owner_label owner_label="$(docker volume inspect --format '{{ index .Labels "io.thothii.task13.run" }}' \ @@ -1371,8 +1397,8 @@ task13_assert_server_oidc_restore_verification() { task13_compose_logged "stop server stack for OIDC restore" stop rollback_sentinel="$TASK13_SERVER_DATA/task13-restore-rollback" printf 'backup-state\n' >"$rollback_sentinel" - task13_run_logged "create real server default-custody backup" "$TASK13_THT" \ - --installation "$TASK13_INSTALLATION" backup --output "$archive" + task13_run_logged "create real server default-custody backup" task13_server_tht \ + backup --output "$archive" printf 'current-state\n' >"$rollback_sentinel" provider_label="$(docker container inspect --format '{{ index .Config.Labels "io.thothii.task13.run" }}' "$TASK13_OIDC_CONTAINER")" @@ -1380,7 +1406,7 @@ task13_assert_server_oidc_restore_verification() { task13_run_logged "inject post-mutation OIDC verification failure" docker stop "$TASK13_OIDC_CONTAINER" rollback_output="$TASK13_TMP/server-oidc-rollback.out" set +e - "$TASK13_THT" --installation "$TASK13_INSTALLATION" restore "$archive" --yes \ + task13_server_tht restore "$archive" --yes \ >"$rollback_output" 2>&1 rollback_rc=$? set -e @@ -1435,7 +1461,7 @@ task13_assert_server_oidc_restore_verification() { restore_output="$TASK13_TMP/server-oidc-restore.out" set +e - "$TASK13_THT" --installation "$TASK13_INSTALLATION" restore "$archive" --yes \ + task13_server_tht restore "$archive" --yes \ >"$restore_output" 2>&1 restore_rc=$? set -e @@ -1470,7 +1496,7 @@ task13_assert_server_oidc_restore_verification() { test -z "$(find /data/auth/sessions /data/auth/oidc -mindepth 1 -print -quit)" ' diagnostics="$TASK13_TMP/server-auth-diagnostics-after-restore.json" - "$TASK13_THT" --installation "$TASK13_INSTALLATION" auth check --json >"$diagnostics" + task13_server_tht auth check --json >"$diagnostics" node -e 'const value=JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")); if(value.mode!=="oidc"||value.ready!==true||value.checks.length!==1||value.checks[0].code!=="auth_ready") process.exit(1)' "$diagnostics" \ || task13_fail "restored server did not retain strict fake-provider OIDC diagnostics" } @@ -1565,14 +1591,15 @@ task13_assert_server_runtime() { [[ "$(docker inspect --format '{{.Image}}' "$frontend_id")" == "$expected_frontend_image" ]] \ || task13_fail "server frontend did not use the smoke-built frontend image" task13_compose_logged "verify server runtime configuration and secret readability" exec -T core sh -ceu ' - test -r /run/thothii-auth/auth.yaml - test -d /data/auth - test -z "${AUTH_MODE+x}" - test "$THT_SESSION_STORAGE" = postgres - test -r /run/secrets/thothii.secrets - test -r /run/secrets/session_runtime_password - test -r /run/secrets/session_ca.pem - test -r /app/harness/workspaces/server-sessions.yaml + check_readable() { test -r "$1" || { printf "server precondition failed: unreadable %s\n" "$1" >&2; exit 1; }; } + check_readable /run/thothii-auth/auth.yaml + test -d /data/auth || { printf "server precondition failed: missing /data/auth\n" >&2; exit 1; } + test -z "${AUTH_MODE+x}" || { printf "server precondition failed: AUTH_MODE must be unset\n" >&2; exit 1; } + test "$THT_SESSION_STORAGE" = postgres || { printf "server precondition failed: session storage\n" >&2; exit 1; } + check_readable /run/secrets/thothii.secrets + check_readable /run/secrets/session_runtime_password + check_readable /run/secrets/session_ca.pem + check_readable /app/harness/workspaces/server-sessions.yaml ' task13_mount_fingerprint | grep -Fq '/data = bind :' \ || task13_fail "server profile did not bind the disposable data root" @@ -1605,13 +1632,13 @@ task13_assert_server_runtime() { task13_fail "server session failure exposed the fixture secret" fi status="$TASK13_TMP/server-auth-status.json" - task13_run_logged "server static OIDC status" "$TASK13_THT" --installation "$TASK13_INSTALLATION" auth status --json - "$TASK13_THT" --installation "$TASK13_INSTALLATION" auth status --json >"$status" + task13_run_logged "server static OIDC status" task13_server_tht auth status --json + task13_server_tht auth status --json >"$status" node -e 'const value=JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")); if(value.mode!=="oidc"||!/^sha256:[0-9a-f]{64}$/.test(value.configRevision)) process.exit(1)' "$status" \ || task13_fail "server static OIDC status was not valid" diagnostics="$TASK13_TMP/server-auth-diagnostics.json" - task13_run_logged "server live OIDC diagnostics" "$TASK13_THT" --installation "$TASK13_INSTALLATION" auth check --json - "$TASK13_THT" --installation "$TASK13_INSTALLATION" auth check --json >"$diagnostics" + task13_run_logged "server live OIDC diagnostics" task13_server_tht auth check --json + task13_server_tht auth check --json >"$diagnostics" node -e 'const value=JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")); if(value.mode!=="oidc"||value.ready!==true||value.checks.length!==1||value.checks[0].code!=="auth_ready") process.exit(1)' "$diagnostics" \ || task13_fail "scoped fake OIDC provider did not pass live production diagnostics" provider_requests="$(docker logs "$TASK13_OIDC_CONTAINER" 2>>"$TASK13_LOG")" @@ -2530,6 +2557,7 @@ task13_self_test_source_contract() { local server_auth_projection_override server_auth_projection_descriptor local server_auth_projection_environment server_auth_privileged_configure local server_fixture_reclamation server_runtime_config_probe server_workspace_config_permission + local server_secret_source_owner server_secret_source_preparation server_tht_wrapper root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" workflow="$root/.github/workflows/deployment.yml" runner_preparation="$root/scripts/prepare-linux-docker-runner.sh" @@ -2561,6 +2589,9 @@ task13_self_test_source_contract() { server_auth_privileged_configure='sudo -n -- "$TASK13_''THT"' server_fixture_reclamation='task13_reclaim_server_fixture_''ownership' server_runtime_config_probe='task13_compose_''logged "verify server runtime configuration and secret readability"' + server_secret_source_preparation='task13_prepare_server_secret_''sources' + server_secret_source_owner='chown 10001:''10001 -- "$TASK13_SECRETS" "$TASK13_PI_AUTH"' + server_tht_wrapper='task13_server_''tht' server_workspace_config_permission='chmod 0644 "$TASK13_SERVER_''WORKSPACE_CONFIG"' if rg -n 'docker[[:space:]]+(system[[:space:]]+)?prune' \ "$root/scripts/unified-deployment-smoke.sh" \ @@ -2641,6 +2672,13 @@ task13_self_test_source_contract() { || task13_fail "the server runtime preconditions must emit a named diagnostic" grep -Fq -- "$server_workspace_config_permission" "$root/scripts/unified-deployment-smoke.sh" \ || task13_fail "the server workspace fixture must be readable by the container UID" + [[ "$(grep -Ec "^${server_secret_source_preparation}\\(\\)|^[[:space:]]+${server_secret_source_preparation}$" \ + "$root/scripts/unified-deployment-smoke.sh")" -eq 2 ]] \ + || task13_fail "the server secret source preparation must be defined and invoked once" + grep -Fq -- "$server_secret_source_owner" "$root/scripts/unified-deployment-smoke.sh" \ + || task13_fail "the server secret sources must be private and owned by the container UID" + [[ "$(grep -Fc -- "$server_tht_wrapper" "$root/scripts/unified-deployment-smoke.sh")" -eq 10 ]] \ + || task13_fail "server operator commands must use the privileged canonical-auth wrapper" [[ -x "$runner_preparation" ]] \ || task13_fail "the Linux Docker runner preparation must be executable" for path in /usr/local/lib/android /usr/share/dotnet /opt/ghc /usr/local/.ghcup; do @@ -2888,6 +2926,7 @@ task13_server_smoke_main() { task13_seed_registry task13_build_tht task13_prepare_server_auth_roots + task13_prepare_server_secret_sources task13_configure_server_oidc_authentication task13_start_server_stack task13_record_project_image_evidence