#!/bin/bash set -uo pipefail NGINX_A="http://172.28.0.2" NGINX_D="http://172.29.0.2" HTTPS="https://172.28.0.2" TLS_HOST="172.28.0.2:443" ALLOWED_IF="172.28.0.20" DENIED_IF="172.29.0.20" SSL="/ssl" REPORT="${REPORT:-/reports/pmi.log}" AUTH_MODE="/auth-mode/mode" PASS=0 FAIL=0 SKIP=0 log() { echo "$@" | tee -a "$REPORT"; } pass() { log "PASS #$1 $2"; PASS=$((PASS + 1)); } fail() { log "FAIL #$1 $2"; FAIL=$((FAIL + 1)); } skip() { log "SKIP #$1 $2"; SKIP=$((SKIP + 1)); } http_code() { local iface="$1" url="$2" shift 2 curl -s -o /dev/null -w '%{http_code}' --interface "$iface" --connect-timeout 10 "$url" "$@" } set_auth_mode() { echo "$1" > "$AUTH_MODE" sleep 0.5 } : > "$REPORT" log "=== PMI tests $(date -Iseconds) ===" log "" # --- 1. IP allow/deny --- c1=$(http_code "$ALLOWED_IF" "$NGINX_A/admin/") c2=$(http_code "$DENIED_IF" "$NGINX_D/admin/") if [ "$c1" = "200" ] && [ "$c2" = "403" ]; then pass 1 "IP allow/deny (allowed=$c1 denied=$c2)" else fail 1 "IP allow/deny (allowed=$c1 denied=$c2, expected 200/403)" fi # --- 2. Basic auth --- c_no=$(http_code "$ALLOWED_IF" "$NGINX_A/secure/") c_wrong=$(http_code "$ALLOWED_IF" "$NGINX_A/secure/" -u 'wrong:wrong') c_ok=$(http_code "$ALLOWED_IF" "$NGINX_A/secure/" -u 'user:password') if [ "$c_no" = "401" ] && [ "$c_wrong" = "401" ] && [ "$c_ok" = "200" ]; then pass 2 "Basic auth (401/401/200)" else fail 2 "Basic auth ($c_no/$c_wrong/$c_ok expected 401/401/200)" fi # --- 3. mTLS --- c3_no=$(curl -sk -o /dev/null -w '%{http_code}' --interface "$ALLOWED_IF" \ --resolve nginx-test-mtls:443:172.28.0.2 https://nginx-test-mtls/) c3_ok=$(curl -sk -o /dev/null -w '%{http_code}' --interface "$ALLOWED_IF" \ --resolve nginx-test-mtls:443:172.28.0.2 --cert "$SSL/client.crt" --key "$SSL/client.key" --cacert "$SSL/ca.crt" \ https://nginx-test-mtls/) c3_bad=$(curl -sk -o /dev/null -w '%{http_code}' --interface "$ALLOWED_IF" \ --resolve nginx-test-mtls:443:172.28.0.2 --cert "$SSL/untrusted.crt" --key "$SSL/untrusted.key" --cacert "$SSL/ca.crt" \ https://nginx-test-mtls/) if [ "$c3_no" = "400" ] && [ "$c3_ok" = "200" ] && [ "$c3_bad" = "400" ]; then pass 3 "Client TLS certificate (400/200/400)" else fail 3 "Client TLS certificate ($c3_no/$c3_ok/$c3_bad expected 400/200/400)" fi # --- 4. auth_request --- set_auth_mode 200 c4a=$(http_code "$ALLOWED_IF" "$NGINX_A/auth-protected/status") set_auth_mode 401 c4b=$(http_code "$ALLOWED_IF" "$NGINX_A/auth-protected/status") set_auth_mode 403 c4c=$(http_code "$ALLOWED_IF" "$NGINX_A/auth-protected/status") set_auth_mode 200 if [ "$c4a" = "200" ] && [ "$c4b" = "401" ] && [ "$c4c" = "403" ]; then pass 4 "auth_request (200/401/403)" else fail 4 "auth_request ($c4a/$c4b/$c4c expected 200/401/403)" fi # --- 5. satisfy all --- c5a=$(http_code "$ALLOWED_IF" "$NGINX_A/admin-combined/" -u 'user:password') c5b=$(http_code "$ALLOWED_IF" "$NGINX_A/admin-combined/") c5c=$(http_code "$DENIED_IF" "$NGINX_D/admin-combined/" -u 'user:password') c5d=$(http_code "$DENIED_IF" "$NGINX_D/admin-combined/") if [ "$c5a" = "200" ] && [ "$c5b" = "401" ] && [ "$c5c" = "403" ] && [ "$c5d" = "403" ]; then pass 5 "satisfy all matrix (200/401/403/403)" else fail 5 "satisfy all ($c5a/$c5b/$c5c/$c5d expected 200/401/403/403)" fi # --- 6. URL separation --- c6a=$(http_code "$ALLOWED_IF" "$NGINX_A/public/index.html") c6b=$(http_code "$DENIED_IF" "$NGINX_D/admin/") if [ "$c6a" = "200" ] && [ "$c6b" = "403" ]; then pass 6 "URL access rules (/public/ vs /admin/)" else fail 6 "URL access ($c6a/$c6b expected 200/403)" fi # --- 7. limit_except --- c7g=$(http_code "$ALLOWED_IF" "$NGINX_A/api/status" -X GET) c7p=$(http_code "$ALLOWED_IF" "$NGINX_A/api/status" -X POST) c7u=$(http_code "$ALLOWED_IF" "$NGINX_A/api/status" -X PUT) c7d=$(http_code "$ALLOWED_IF" "$NGINX_A/api/status" -X DELETE) if [ "$c7g" = "200" ] && [ "$c7p" = "200" ] && [ "$c7u" = "403" ] && [ "$c7d" = "403" ]; then pass 7 "limit_except GET POST (PUT/DELETE=403)" else fail 7 "limit_except ($c7g/$c7p/$c7u/$c7d expected 200/200/403/403)" fi # --- 8. internal --- c8a=$(http_code "$ALLOWED_IF" "$NGINX_A/protected/document.pdf") c8b=$(http_code "$ALLOWED_IF" "$NGINX_A/download") if [ "$c8a" = "404" ] && [ "$c8b" = "200" ]; then pass 8 "internal location (direct=404 rewrite=200)" else fail 8 "internal ($c8a/$c8b expected 404/200)" fi # --- 9. TLS connection --- s9=$(echo | openssl s_client -connect "$TLS_HOST" -servername nginx-test 2>&1 || true) if echo "$s9" | grep -qE "Verify return code: (0|18|19|21)|SSL-Session:"; then pass 9 "HTTPS TLS handshake" else fail 9 "HTTPS TLS handshake failed" fi # --- 10. TLS versions --- s10ok=$(echo | openssl s_client -connect "$TLS_HOST" -servername nginx-test -tls1_2 2>&1 || true) s10bad=$(echo | openssl s_client -connect "$TLS_HOST" -servername nginx-test -tls1_1 2>&1 || true) if echo "$s10ok" | grep -q "Protocol : TLSv1.2" && echo "$s10bad" | grep -qi "alert protocol version\|no protocols available\|wrong version"; then pass 10 "TLSv1.2 ok, TLSv1.1 rejected" else fail 10 "TLS version policy" fi # --- 11. HTTPS redirect + HSTS --- h11=$(curl -sI --interface "$ALLOWED_IF" -H "Host: redirect-test" "$NGINX_A/" | tr -d '\r') hsts=$(curl -skI --interface "$ALLOWED_IF" "$HTTPS/" -H "Host: nginx-test" | tr -d '\r') ok11=0 echo "$h11" | grep -q "301" && ok11=$((ok11 + 1)) echo "$h11" | grep -qi "location: https://" && ok11=$((ok11 + 1)) echo "$hsts" | grep -qi "strict-transport-security:" && ok11=$((ok11 + 1)) if [ "$ok11" -eq 3 ]; then pass 11 "HTTP redirect 301 + HSTS header" else fail 11 "Redirect/HSTS (checks $ok11/3)" fi # --- 12-13. access/error logs (detailed check in run-pmi-log-checks.sh) --- curl -s --interface "$ALLOWED_IF" "$NGINX_A/test-page" > /dev/null curl -s --interface "$ALLOWED_IF" "$NGINX_A/another-page" > /dev/null curl -s --max-time 5 --interface "$ALLOWED_IF" "$NGINX_A/broken/" > /dev/null || true # --- 14. syslog --- curl -s --interface "$ALLOWED_IF" "$NGINX_A/syslog-probe-$(date +%s)" > /dev/null || true ok14=0 for _ in 1 2 3 4 5; do sleep 1 if [ -f /reports/nginx-syslog.log ] && grep -q "nginx" /reports/nginx-syslog.log 2>/dev/null; then ok14=1 break fi done if [ "$ok14" -eq 1 ]; then pass 14 "syslog receives nginx events" else fail 14 "syslog log missing or empty (/reports/nginx-syslog.log)" fi # --- 15. limit_req --- count503=0 count200=0 codes15="" for i in $(seq 1 5); do c=$(http_code "$ALLOWED_IF" "$NGINX_A/rate-limit-check") codes15="$codes15 $c" [ "$c" = "503" ] && count503=$((count503 + 1)) [ "$c" = "200" ] && count200=$((count200 + 1)) done if [ "$count503" -gt 0 ] && [ "$count200" -gt 0 ]; then pass 15 "limit_req returns 503 ($count503) and 200 ($count200) in:$codes15" elif [ "$count503" -gt 0 ]; then pass 15 "limit_req returns 503 ($count503 of 5 in:$codes15)" else fail 15 "limit_req no 503 in:$codes15" fi # --- 16. limit_conn --- pids="" for i in 1 2 3 4 5; do curl -s -o /tmp/s16_$i -w '%{http_code}' --interface "$ALLOWED_IF" --max-time 25 "$NGINX_A/slow/" > /tmp/c16_$i & pids="$pids $!" done for p in $pids; do wait "$p" 2>/dev/null || true; done codes16=$(cat /tmp/c16_* 2>/dev/null | tr '\n' ' ') if echo "$codes16" | grep -q "503"; then pass 16 "limit_conn returns 503 ($codes16)" else fail 16 "limit_conn no 503 in: $codes16" fi rm -f /tmp/c16_* /tmp/s16_* 2>/dev/null || true # --- 17. client_max_body_size --- dd if=/dev/zero of=/tmp/pmi-small bs=1024 count=100 status=none 2>/dev/null dd if=/dev/zero of=/tmp/pmi-large bs=1M count=2 status=none 2>/dev/null c17s=$(curl -s -o /dev/null -w '%{http_code}' --interface "$ALLOWED_IF" -X POST -T /tmp/pmi-small "$NGINX_A/upload") c17l=$(curl -s -o /dev/null -w '%{http_code}' --interface "$ALLOWED_IF" -X POST -T /tmp/pmi-large "$NGINX_A/upload") if [ "$c17s" = "200" ] && [ "$c17l" = "413" ]; then pass 17 "client_max_body_size (small=$c17s large=$c17l)" else fail 17 "client_max_body_size ($c17s/$c17l expected 200/413)" fi # --- 18. client_body_timeout --- if dd if=/dev/zero bs=1K count=800 2>/dev/null | curl -s -o /dev/null -w '%{http_code}' \ --interface "$ALLOWED_IF" --limit-rate 1K -m 30 -X POST -d @- "$NGINX_A/upload" | grep -qv 200; then pass 18 "client_body_timeout (slow upload rejected/timed out)" else # curl may return 000 on timeout ec=$? if [ "$ec" -ne 0 ]; then pass 18 "client_body_timeout (curl exit $ec)" else fail 18 "client_body_timeout (upload unexpectedly succeeded quickly)" fi fi # --- 19. server_tokens off --- hdr=$(curl -sI --interface "$ALLOWED_IF" "$NGINX_A/public/index.html" | tr -d '\r') body=$(curl -s --interface "$ALLOWED_IF" "$NGINX_A/nonexistent-page-19") if echo "$hdr" | grep -i "^server: nginx$" && ! echo "$hdr" | grep -q "nginx/"; then if ! echo "$body" | grep -q "nginx/"; then pass 19 "server_tokens off" else fail 19 "server_tokens off (version in error body)" fi else fail 19 "server_tokens off (Server header: $(echo "$hdr" | grep -i ^server:))" fi # --- 20. error_page --- b20=$(curl -s --interface "$ALLOWED_IF" "$NGINX_A/nonexistent-page-20") if echo "$b20" | grep -q "Custom 404"; then pass 20 "custom error_page 404" else fail 20 "custom error_page (body missing Custom 404)" fi # --- 21. autoindex off --- c21=$(http_code "$ALLOWED_IF" "$NGINX_A/files/") body21=$(curl -s --interface "$ALLOWED_IF" "$NGINX_A/files/") if [ "$c21" = "403" ] && ! echo "$body21" | grep -qi "