254 lines
9.9 KiB
Bash
Executable File
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
|