fix: harden DWH Nginx integration gates
This commit is contained in:
@@ -55,6 +55,10 @@ location_block() {
|
|||||||
' "$file"
|
' "$file"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location_declarations() {
|
||||||
|
effective_lines "$1" | awk "/^location / { print }"
|
||||||
|
}
|
||||||
|
|
||||||
contains_exactly_once() {
|
contains_exactly_once() {
|
||||||
local haystack=$1
|
local haystack=$1
|
||||||
local needle=$2
|
local needle=$2
|
||||||
@@ -70,13 +74,19 @@ contains_line() {
|
|||||||
check_templates() {
|
check_templates() {
|
||||||
local http=$1
|
local http=$1
|
||||||
local location=$2
|
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
|
[[ -f "$http" && -f "$location" ]] || return 1
|
||||||
http_lines=$(effective_lines "$http")
|
http_lines=$(effective_lines "$http")
|
||||||
auth_lines=$(location_block "$location" '= /_check_dwh_key')
|
auth_lines=$(location_block "$location" '= /_check_dwh_key')
|
||||||
unavailable_lines=$(location_block "$location" '@dwh_auth_unavailable')
|
unavailable_lines=$(location_block "$location" '@dwh_auth_unavailable')
|
||||||
dwh_lines=$(location_block "$location" '/dwh/')
|
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" 'internal;' || return 1
|
||||||
contains_exactly_once "$auth_lines" 'proxy_method GET;' || 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"
|
! 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 "$http_template" ]] || report_fail source_http_exists
|
||||||
[[ -f "$location_template" ]] || report_fail source_location_exists
|
[[ -f "$location_template" ]] || report_fail source_location_exists
|
||||||
check_templates "$http_template" "$location_template" || report_fail source_contract
|
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_fail negative_postgrest_bypass
|
||||||
report_pass 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;' \
|
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_fail negative_failure_mapped_to_success
|
||||||
report_pass negative_failure_mapped_to_success
|
report_pass negative_failure_mapped_to_success
|
||||||
|
|||||||
@@ -27,15 +27,9 @@ cleanup() {
|
|||||||
local pid
|
local pid
|
||||||
set +e
|
set +e
|
||||||
for pid in "${registered_pids[@]}"; do
|
for pid in "${registered_pids[@]}"; do
|
||||||
if [[ "$pid" =~ ^[0-9]+$ ]] && kill -0 "$pid" 2>/dev/null; then
|
stop_registered_pid "$pid" || true
|
||||||
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
|
|
||||||
done
|
done
|
||||||
|
registered_pids=()
|
||||||
if [[ "$temp_root" == /tmp/thothii-dwh-auth-nginx-integration.* && -d "$temp_root" ]]; then
|
if [[ "$temp_root" == /tmp/thothii-dwh-auth-nginx-integration.* && -d "$temp_root" ]]; then
|
||||||
rm -rf -- "$temp_root"
|
rm -rf -- "$temp_root"
|
||||||
fi
|
fi
|
||||||
@@ -119,13 +113,124 @@ expect_status() {
|
|||||||
[[ "$status" == "$expected" ]] || fail_case "$name"
|
[[ "$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() {
|
stop_registered_pid() {
|
||||||
local pid=$1
|
local pid=$1
|
||||||
[[ "$pid" =~ ^[0-9]+$ ]] || return 1
|
[[ "$pid" =~ ^[0-9]+$ ]] || return 1
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
if pid_exited_or_zombie "$pid"; then
|
||||||
kill -TERM "$pid"
|
reap_if_direct_child "$pid"
|
||||||
wait "$pid"
|
unregister_pid "$pid"
|
||||||
|
return 0
|
||||||
fi
|
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 <<PY &
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
|
||||||
|
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
listener.bind(("127.0.0.1", 0))
|
||||||
|
listener.listen()
|
||||||
|
signal.pause()
|
||||||
|
PY
|
||||||
|
probe_pid=$!
|
||||||
|
register_pid "$probe_pid"
|
||||||
|
wait_for_tcp_listener_pid "$probe_pid" || fail_case tcp_listener_detector_positive
|
||||||
|
report_pass tcp_listener_detector_positive
|
||||||
|
stop_registered_pid "$probe_pid" || fail_case tcp_listener_detector_positive
|
||||||
|
! tcp_listener_for_pid "$probe_pid" || fail_case tcp_listener_detector_positive
|
||||||
}
|
}
|
||||||
|
|
||||||
for command in nginx curl python3 ss date; do
|
for command in nginx curl python3 ss date; do
|
||||||
@@ -139,6 +244,8 @@ temp_root=$(mktemp -d /tmp/thothii-dwh-auth-nginx-integration.XXXXXXXX) || fail_
|
|||||||
chmod 0700 "$temp_root" || fail_case fixture_root
|
chmod 0700 "$temp_root" || fail_case fixture_root
|
||||||
umask 077
|
umask 077
|
||||||
|
|
||||||
|
run_term_ignored_regression
|
||||||
|
|
||||||
dwh_auth="$temp_root/dwh-auth"
|
dwh_auth="$temp_root/dwh-auth"
|
||||||
current_case=build_dwh_auth
|
current_case=build_dwh_auth
|
||||||
build_dwh_auth "$dwh_auth" >"$temp_root/build.log" 2>&1 || fail_case build_dwh_auth
|
build_dwh_auth "$dwh_auth" >"$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
|
wait_for_socket "$service_socket" || fail_case verifier_start
|
||||||
report_pass verifier_start
|
report_pass verifier_start
|
||||||
|
|
||||||
|
run_tcp_listener_detector_positive
|
||||||
|
|
||||||
current_case=synthetic_upstreams
|
current_case=synthetic_upstreams
|
||||||
AUTH_PROXY_SOCKET="$auth_proxy_socket" \
|
AUTH_PROXY_SOCKET="$auth_proxy_socket" \
|
||||||
VERIFIER_SOCKET="$service_socket" \
|
VERIFIER_SOCKET="$service_socket" \
|
||||||
@@ -370,6 +479,7 @@ current_case=auth_socket_unix_only
|
|||||||
[[ -S "$service_socket" ]] || fail_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
|
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
|
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
|
report_pass auth_socket_unix_only
|
||||||
|
|
||||||
probe_body="$temp_root/probe.body"
|
probe_body="$temp_root/probe.body"
|
||||||
|
|||||||
Reference in New Issue
Block a user