Files
ThothII/scripts/test-dwh-auth-nginx-contract.sh

254 lines
9.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root=$(cd "$(dirname "$0")/.." && pwd -P)
http_template=${DWH_AUTH_NGINX_HTTP:-"$repo_root/deploy/dwh-auth/nginx-http.conf.example"}
location_template=${DWH_AUTH_NGINX_LOCATION:-"$repo_root/deploy/dwh-auth/nginx-dwh-location.conf.example"}
temp_root=
report_pass() {
printf 'case=%s status=PASS\n' "$1"
}
report_fail() {
printf 'case=%s status=FAIL\n' "$1" >&2
exit 1
}
cleanup() {
if [[ "$temp_root" == /tmp/thothii-dwh-auth-nginx-contract.* && -d "$temp_root" ]]; then
rm -rf -- "$temp_root"
fi
}
trap cleanup EXIT
effective_lines() {
awk '
{
line = $0
sub(/[[:space:]]*#.*/, "", line)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
gsub(/[[:space:]]+/, " ", line)
if (line != "") print line
}
' "$1"
}
location_block() {
local file=$1
local location=$2
awk -v expected="location $location {" '
function normalize(line) {
sub(/[[:space:]]*#.*/, "", line)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
gsub(/[[:space:]]+/, " ", line)
return line
}
{
line = normalize($0)
if (!inside && line == expected) inside = 1
if (inside) {
if (line != "") print line
if (line == "}") exit
}
}
' "$file"
}
location_declarations() {
effective_lines "$1" | awk "/^location / { print }"
}
contains_exactly_once() {
local haystack=$1
local needle=$2
[[ $(grep -Fxc -- "$needle" <<<"$haystack" || true) -eq 1 ]]
}
contains_line() {
local haystack=$1
local needle=$2
grep -Fqx -- "$needle" <<<"$haystack"
}
check_templates() {
local http=$1
local location=$2
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
contains_exactly_once "$auth_lines" 'proxy_pass http://unix:/run/dwh-auth/verify.sock:/verify;' || return 1
contains_exactly_once "$auth_lines" 'proxy_pass_request_body off;' || return 1
contains_exactly_once "$auth_lines" 'proxy_pass_request_headers off;' || return 1
contains_exactly_once "$auth_lines" 'proxy_set_header Content-Length "";' || return 1
contains_exactly_once "$auth_lines" 'proxy_set_header X-API-Key $http_x_api_key;' || return 1
contains_exactly_once "$unavailable_lines" 'return 503;' || return 1
contains_line "$dwh_lines" 'location /dwh/ {' || return 1
contains_exactly_once "$dwh_lines" 'limit_req zone=dwh_auth burst=100 nodelay;' || return 1
contains_exactly_once "$dwh_lines" 'auth_request /_check_dwh_key;' || return 1
contains_exactly_once "$dwh_lines" 'auth_request_set $dwh_key_id $upstream_http_x_dwh_key_id;' || return 1
contains_exactly_once "$dwh_lines" 'error_page 500 =503 @dwh_auth_unavailable;' || return 1
contains_exactly_once "$dwh_lines" 'proxy_set_header X-API-Key "";' || return 1
contains_exactly_once "$dwh_lines" 'proxy_set_header X-DWH-Key-ID "";' || return 1
contains_exactly_once "$dwh_lines" 'proxy_set_header Host $host;' || return 1
contains_exactly_once "$dwh_lines" 'proxy_pass http://127.0.0.1:3001/;' || return 1
contains_exactly_once "$http_lines" 'map $http_x_api_key $dwh_public_key_class {' || return 1
contains_line "$http_lines" 'default opaque;' || return 1
contains_line "$http_lines" '"~^thtdwh_v1\.([A-Za-z0-9_-]{16})\.[A-Za-z0-9_-]{43}$" v1:$1;' || return 1
contains_exactly_once "$http_lines" 'map "$remote_addr:$dwh_public_key_class" $dwh_auth_rate_key {' || return 1
contains_exactly_once "$http_lines" 'limit_req_zone $dwh_auth_rate_key zone=dwh_auth:10m rate=20r/s;' || return 1
! grep -Eq 'limit_req_zone.*\$(http_x_api_key|dwh_key_secret|request)' <<<"$http_lines" || return 1
! grep -Eq 'map .*\$http_x_api_key .*\$dwh_auth_rate_key' <<<"$http_lines" || return 1
}
replace_effective_line() {
local file=$1
local needle=$2
local replacement=$3
local output="$file.replaced"
awk -v needle="$needle" -v replacement="$replacement" '
function normalize(line) {
sub(/[[:space:]]*#.*/, "", line)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
gsub(/[[:space:]]+/, " ", line)
return line
}
{
if (normalize($0) == needle) {
print replacement
replaced++
next
}
print
}
END { if (replaced != 1) exit 1 }
' "$file" >"$output" || return 1
mv -- "$output" "$file"
}
expect_location_rejected() {
local name=$1
local needle=$2
local replacement=$3
local fixture="$temp_root/$name"
mkdir -- "$fixture"
cp -- "$http_template" "$fixture/http.conf"
cp -- "$location_template" "$fixture/location.conf"
replace_effective_line "$fixture/location.conf" "$needle" "$replacement" || return 1
! check_templates "$fixture/http.conf" "$fixture/location.conf"
}
expect_http_rejected() {
local name=$1
local needle=$2
local replacement=$3
local fixture="$temp_root/$name"
mkdir -- "$fixture"
cp -- "$http_template" "$fixture/http.conf"
cp -- "$location_template" "$fixture/location.conf"
replace_effective_line "$fixture/http.conf" "$needle" "$replacement" || return 1
! 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
report_pass source_contract
temp_root=$(mktemp -d /tmp/thothii-dwh-auth-nginx-contract.XXXXXXXX) || report_fail fixture_root
expect_location_rejected missing_auth_request 'auth_request /_check_dwh_key;' '# removed auth request' \
|| report_fail negative_missing_auth_request
report_pass negative_missing_auth_request
expect_location_rejected missing_proxy_method 'proxy_method GET;' '# removed proxy method' \
|| report_fail negative_missing_proxy_method
report_pass negative_missing_proxy_method
expect_location_rejected missing_proxy_body 'proxy_pass_request_body off;' '# removed proxy body suppression' \
|| report_fail negative_missing_proxy_body
report_pass negative_missing_proxy_body
expect_location_rejected missing_proxy_header_isolation 'proxy_pass_request_headers off;' '# removed proxy header isolation' \
|| report_fail negative_missing_proxy_header_isolation
report_pass negative_missing_proxy_header_isolation
expect_location_rejected missing_content_length_clear 'proxy_set_header Content-Length "";' '# removed content length clear' \
|| report_fail negative_missing_content_length_clear
report_pass negative_missing_content_length_clear
expect_location_rejected missing_verifier_key_forward 'proxy_set_header X-API-Key $http_x_api_key;' '# removed verifier key forwarding' \
|| report_fail negative_missing_verifier_key_forward
report_pass negative_missing_verifier_key_forward
expect_location_rejected missing_upstream_key_clear 'proxy_set_header X-API-Key "";' '# removed upstream key clear' \
|| report_fail negative_missing_upstream_key_clear
report_pass negative_missing_upstream_key_clear
expect_location_rejected missing_failure_mapping 'error_page 500 =503 @dwh_auth_unavailable;' '# removed failure mapping' \
|| report_fail negative_missing_failure_mapping
report_pass negative_missing_failure_mapping
expect_location_rejected public_verifier 'internal;' '# verifier became public' \
|| report_fail negative_public_verifier
report_pass negative_public_verifier
expect_location_rejected tcp_authenticator 'proxy_pass http://unix:/run/dwh-auth/verify.sock:/verify;' 'proxy_pass http://127.0.0.1:19091/verify;' \
|| report_fail negative_tcp_authenticator
report_pass negative_tcp_authenticator
expect_location_rejected postgrest_bypass 'auth_request /_check_dwh_key;' '# direct PostgREST bypass' \
|| 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
expect_http_rejected full_secret_rate_key 'map "$remote_addr:$dwh_public_key_class" $dwh_auth_rate_key {' 'map "$remote_addr:$http_x_api_key" $dwh_auth_rate_key {' \
|| report_fail negative_full_secret_rate_key
report_pass negative_full_secret_rate_key
report_pass summary