From d0f7e0497d28548c0d76f8ce1da1e02fccdee15c Mon Sep 17 00:00:00 2001 From: User Date: Fri, 21 Aug 2026 03:37:01 +0200 Subject: [PATCH] fix: harden DWH Nginx integration gates --- scripts/test-dwh-auth-nginx-contract.sh | 36 +++++- scripts/test-dwh-auth-nginx-integration.sh | 132 +++++++++++++++++++-- 2 files changed, 156 insertions(+), 12 deletions(-) diff --git a/scripts/test-dwh-auth-nginx-contract.sh b/scripts/test-dwh-auth-nginx-contract.sh index e939b47b..7823bf19 100755 --- a/scripts/test-dwh-auth-nginx-contract.sh +++ b/scripts/test-dwh-auth-nginx-contract.sh @@ -55,6 +55,10 @@ location_block() { ' "$file" } +location_declarations() { + effective_lines "$1" | awk "/^location / { print }" +} + contains_exactly_once() { local haystack=$1 local needle=$2 @@ -70,13 +74,19 @@ contains_line() { check_templates() { local http=$1 local location=$2 - local http_lines auth_lines unavailable_lines dwh_lines + local http_lines auth_lines unavailable_lines dwh_lines locations [[ -f "$http" && -f "$location" ]] || return 1 http_lines=$(effective_lines "$http") auth_lines=$(location_block "$location" '= /_check_dwh_key') unavailable_lines=$(location_block "$location" '@dwh_auth_unavailable') dwh_lines=$(location_block "$location" '/dwh/') + locations=$(location_declarations "$location") + [[ $(wc -l <<<"$locations") -eq 3 ]] || return 1 + [[ $(grep -Fxc -- "location = /_check_dwh_key {" <<<"$locations" || true) -eq 1 ]] || return 1 + [[ $(grep -Fxc -- "location @dwh_auth_unavailable {" <<<"$locations" || true) -eq 1 ]] || return 1 + [[ $(grep -Fxc -- "location /dwh/ {" <<<"$locations" || true) -eq 1 ]] || return 1 + contains_exactly_once "$auth_lines" 'internal;' || return 1 contains_exactly_once "$auth_lines" 'proxy_method GET;' || return 1 @@ -157,6 +167,24 @@ expect_http_rejected() { ! check_templates "$fixture/http.conf" "$fixture/location.conf" } +expect_postgrest_regex_bypass_rejected() { + local fixture="$temp_root/postgrest_regex_bypass" + mkdir -- "$fixture" + cp -- "$http_template" "$fixture/http.conf" + cp -- "$location_template" "$fixture/location.conf" + printf "%s\n" "location ~ ^/dwh/ {" " proxy_pass http://127.0.0.1:3001;" "}" >>"$fixture/location.conf" + ! check_templates "$fixture/http.conf" "$fixture/location.conf" +} + +expect_postgrest_duplicate_bypass_rejected() { + local fixture="$temp_root/postgrest_duplicate_bypass" + mkdir -- "$fixture" + cp -- "$http_template" "$fixture/http.conf" + cp -- "$location_template" "$fixture/location.conf" + printf "%s\n" "location /dwh/ {" " proxy_pass http://127.0.0.1:3001;" "}" >>"$fixture/location.conf" + ! check_templates "$fixture/http.conf" "$fixture/location.conf" +} + [[ -f "$http_template" ]] || report_fail source_http_exists [[ -f "$location_template" ]] || report_fail source_location_exists check_templates "$http_template" "$location_template" || report_fail source_contract @@ -208,6 +236,12 @@ expect_location_rejected postgrest_bypass 'auth_request /_check_dwh_key;' '# dir || report_fail negative_postgrest_bypass report_pass negative_postgrest_bypass +expect_postgrest_regex_bypass_rejected || report_fail negative_postgrest_regex_bypass +report_pass negative_postgrest_regex_bypass + +expect_postgrest_duplicate_bypass_rejected || report_fail negative_postgrest_duplicate_bypass +report_pass negative_postgrest_duplicate_bypass + expect_location_rejected failure_mapped_to_success 'error_page 500 =503 @dwh_auth_unavailable;' 'error_page 500 =200 @dwh_auth_unavailable;' \ || report_fail negative_failure_mapped_to_success report_pass negative_failure_mapped_to_success diff --git a/scripts/test-dwh-auth-nginx-integration.sh b/scripts/test-dwh-auth-nginx-integration.sh index 33c9f1f5..a183f8f5 100755 --- a/scripts/test-dwh-auth-nginx-integration.sh +++ b/scripts/test-dwh-auth-nginx-integration.sh @@ -27,15 +27,9 @@ cleanup() { local pid set +e for pid in "${registered_pids[@]}"; do - if [[ "$pid" =~ ^[0-9]+$ ]] && kill -0 "$pid" 2>/dev/null; then - kill -TERM "$pid" 2>/dev/null - fi - done - for pid in "${registered_pids[@]}"; do - if [[ "$pid" =~ ^[0-9]+$ ]]; then - wait "$pid" 2>/dev/null - fi + stop_registered_pid "$pid" || true done + registered_pids=() if [[ "$temp_root" == /tmp/thothii-dwh-auth-nginx-integration.* && -d "$temp_root" ]]; then rm -rf -- "$temp_root" fi @@ -119,13 +113,124 @@ expect_status() { [[ "$status" == "$expected" ]] || fail_case "$name" } +unregister_pid() { + local target=$1 + local candidate + local retained=() + for candidate in "${registered_pids[@]}"; do + [[ "$candidate" == "$target" ]] || retained+=("$candidate") + done + registered_pids=("${retained[@]}") +} + +pid_exited_or_zombie() { + local pid=$1 + local state + [[ "$pid" =~ ^[0-9]+$ ]] || return 1 + [[ ! -d "/proc/$pid" ]] && return 0 + state=$(awk "{print \$3}" "/proc/$pid/stat" 2>/dev/null) || return 0 + [[ "$state" == Z* ]] +} + +pid_is_direct_child() { + local pid=$1 + local parent + [[ -r "/proc/$pid/stat" ]] || return 1 + parent=$(awk "{print \$4}" "/proc/$pid/stat" 2>/dev/null) || return 1 + [[ "$parent" == "$$" ]] +} + +reap_if_direct_child() { + local pid=$1 + if pid_is_direct_child "$pid"; then + wait "$pid" 2>/dev/null || true + fi +} + +wait_for_exit_or_zombie() { + local pid=$1 + local attempts=${2:-10} + local attempt + for ((attempt = 0; attempt < attempts; attempt++)); do + pid_exited_or_zombie "$pid" && return 0 + sleep 0.05 + done + pid_exited_or_zombie "$pid" +} + +tcp_listener_for_pid() { + local pid=$1 + [[ "$pid" =~ ^[0-9]+$ ]] || return 1 + ss -ltnpH 2>/dev/null | grep -Eq "(^|[^0-9])pid=$pid([,)]|$)" +} + +wait_for_tcp_listener_pid() { + local pid=$1 + local attempt + for ((attempt = 0; attempt < 10; attempt++)); do + tcp_listener_for_pid "$pid" && return 0 + sleep 0.05 + done + tcp_listener_for_pid "$pid" +} + stop_registered_pid() { local pid=$1 [[ "$pid" =~ ^[0-9]+$ ]] || return 1 - if kill -0 "$pid" 2>/dev/null; then - kill -TERM "$pid" - wait "$pid" + if pid_exited_or_zombie "$pid"; then + reap_if_direct_child "$pid" + unregister_pid "$pid" + return 0 fi + if ! kill -TERM "$pid" 2>/dev/null; then + wait_for_exit_or_zombie "$pid" 1 || return 1 + fi + if ! wait_for_exit_or_zombie "$pid"; then + kill -KILL "$pid" 2>/dev/null || return 1 + wait_for_exit_or_zombie "$pid" || return 1 + fi + reap_if_direct_child "$pid" + unregister_pid "$pid" +} + +run_term_ignored_regression() { + local child_pid started_seconds registered_pid + current_case=cleanup_term_ignored_bounded + python3 -c "import signal; signal.signal(signal.SIGTERM, signal.SIG_IGN); signal.pause()" >"$temp_root/term-ignored.log" 2>&1 & + child_pid=$! + register_pid "$child_pid" + started_seconds=$SECONDS + if ! stop_registered_pid "$child_pid"; then + fail_case cleanup_term_ignored_bounded + fi + if kill -0 "$child_pid" 2>/dev/null; then + fail_case cleanup_term_ignored_bounded + fi + for registered_pid in "${registered_pids[@]}"; do + [[ "$registered_pid" != "$child_pid" ]] || fail_case cleanup_term_ignored_bounded + done + ((SECONDS - started_seconds < 3)) || fail_case cleanup_term_ignored_bounded + report_pass cleanup_term_ignored_bounded +} + +run_tcp_listener_detector_positive() { + local probe_pid + current_case=tcp_listener_detector_positive + python3 - >"$temp_root/tcp-listener.log" 2>&1 <"$temp_root/build.log" 2>&1 || fail_case build_dwh_auth @@ -197,6 +304,8 @@ register_pid "$verifier_pid" wait_for_socket "$service_socket" || fail_case verifier_start report_pass verifier_start +run_tcp_listener_detector_positive + current_case=synthetic_upstreams AUTH_PROXY_SOCKET="$auth_proxy_socket" \ VERIFIER_SOCKET="$service_socket" \ @@ -370,6 +479,7 @@ current_case=auth_socket_unix_only [[ -S "$service_socket" ]] || fail_case auth_socket_unix_only ss -xl >"$temp_root/ss-unix.log" 2>&1 || fail_case auth_socket_unix_only grep -Fq -- "$service_socket" "$temp_root/ss-unix.log" || fail_case auth_socket_unix_only +! tcp_listener_for_pid "$verifier_pid" || fail_case auth_socket_unix_only report_pass auth_socket_unix_only probe_body="$temp_root/probe.body"