This commit is contained in:
Redsandyg
2026-06-15 06:31:35 +03:00
commit fcc9139361
50 changed files with 5400 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
#!/bin/sh
set -eu
SSL_DIR="${SSL_DIR:-/ssl}"
cd "$SSL_DIR"
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-subj "/CN=PMI Test CA" -out ca.crt
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=nginx-test" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 825 -sha256
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=trusted-client" -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 825 -sha256
openssl genrsa -out untrusted.key 2048
openssl req -x509 -new -nodes -key untrusted.key -sha256 -days 365 \
-subj "/CN=untrusted-client" -out untrusted.crt
chmod 600 ca.key server.key client.key untrusted.key
chmod 644 ca.crt server.crt client.crt untrusted.crt
cat server.crt ca.crt > fullchain.crt
chmod 644 fullchain.crt
rm -f server.csr client.csr ca.srl
echo "Certificates generated in $SSL_DIR"

View File

@@ -0,0 +1,7 @@
#!/bin/sh
set -eu
OUT="${OUT:-/out/htpasswd}"
hash=$(openssl passwd -apr1 'password')
echo "user:$hash" > "$OUT"
echo "Created $OUT (user:password)"

View File

@@ -0,0 +1,69 @@
#!/bin/bash
# Build summary markdown from report logs.
set -euo pipefail
TS="${1:-unknown}"
REPORTS="${REPORTS_DIR:-/reports}"
OUT="$REPORTS/${TS}_summary.md"
pmi="$REPORTS/${TS}_pmi.log"
audit="$REPORTS/${TS}_audit.log"
fim="$REPORTS/${TS}_fim.log"
count_status() {
local file="$1" status="$2"
local n=0
if [ -f "$file" ]; then
n=$(grep -c "^${status} " "$file" 2>/dev/null) || n=0
fi
echo "$n"
}
pass_pmi=$(count_status "$pmi" PASS)
fail_pmi=$(count_status "$pmi" FAIL)
pass_fim=$(count_status "$fim" PASS)
fail_fim=$(count_status "$fim" FAIL)
{
echo "# Сводный отчёт nginx PMI stand"
echo ""
echo "**Дата прогона:** $TS"
echo "**Стенд:** Docker Compose (\`results/docker-compose.yml\`)"
echo ""
echo "## Результаты ПМИ"
echo ""
echo "| Метрика | Значение |"
echo "|---------|----------|"
echo "| PASS | $pass_pmi |"
echo "| FAIL | $fail_pmi |"
echo ""
echo "## Целостность (FIM stand-in)"
echo ""
echo "| Метрика | Значение |"
echo "|---------|----------|"
echo "| PASS | $pass_fim |"
echo "| FAIL | $fail_fim |"
echo ""
echo "> FIM выполнен через SHA256 baseline (\`run-fim-checksum.sh\`), не afick."
echo "> Syslog проверялся локальным syslog-ng, не корпоративным SIEM."
echo ""
echo "## Файлы отчётов"
echo ""
echo "- [\`${TS}_pmi.log\`](${TS}_pmi.log)"
echo "- [\`${TS}_audit.log\`](${TS}_audit.log)"
echo "- [\`${TS}_fim.log\`](${TS}_fim.log)"
echo ""
echo "## PMI log excerpt"
echo ""
echo '```'
[ -f "$pmi" ] && grep -E '^(PASS|FAIL|SKIP|===)' "$pmi" || echo "(no pmi log)"
echo '```'
echo ""
echo "## FIM log excerpt"
echo ""
echo '```'
[ -f "$fim" ] && cat "$fim" || echo "(no fim log)"
echo '```'
} > "$OUT"
echo "Summary written: $OUT"

View File

@@ -0,0 +1,26 @@
#!/bin/sh
# Audit: attack surface minimization (docs/Минимизация_поверхности_атаки_nginx)
set -eu
REPORT="${REPORT:-/reports/audit.log}"
{
echo ""
echo "=== Attack surface audit $(date -Iseconds) ==="
echo "--- nginx -V ---"
nginx -V 2>&1
echo "--- load_module ---"
nginx -T 2>/dev/null | grep load_module || echo "(none)"
echo "--- optional modules in config ---"
nginx -T 2>/dev/null | grep -E 'dav_methods|perl|js_import|js_content|xslt_stylesheet|auth_request' || echo "(none found)"
echo "--- autoindex ---"
nginx -T 2>/dev/null | grep autoindex || echo "(default off)"
echo "--- limit_except / request_method ---"
nginx -T 2>/dev/null | grep -E 'limit_except|request_method' || true
echo "--- proxy_pass / upstream ---"
nginx -T 2>/dev/null | grep -E 'proxy_pass|upstream' || true
echo "--- ssi ---"
nginx -T 2>/dev/null | grep -i 'ssi on' || echo "(ssi off)"
} >> "$REPORT"
echo "Attack surface audit appended -> $REPORT"

View File

@@ -0,0 +1,15 @@
#!/bin/sh
# Audit: code execution control (docs/Контроль_выполнения_кода)
set -eu
REPORT="${REPORT:-/reports/audit.log}"
{
echo ""
echo "=== Code execution control audit $(date -Iseconds) ==="
nginx -T 2>/dev/null | grep -E 'fastcgi_pass|uwsgi_pass|scgi_pass|grpc_pass|perl|js_import|internal|^\s*root\s|alias' || true
echo "--- SCRIPT_FILENAME ---"
nginx -T 2>/dev/null | grep SCRIPT_FILENAME || echo "(no fastcgi)"
} >> "$REPORT"
echo "Code control audit appended -> $REPORT"

View File

@@ -0,0 +1,56 @@
#!/bin/sh
# Audit: minimal permissions (docs/Минимальноеобходимые_полномочия_nginx)
set -eu
REPORT="${REPORT:-/reports/audit.log}"
section() { echo "" >> "$REPORT"; echo "=== $1 ===" >> "$REPORT"; echo "=== $1 ==="; }
: > "$REPORT"
echo "Permissions audit $(date -Iseconds)" >> "$REPORT"
section "Worker user"
nginx -T 2>/dev/null | grep '^user ' >> "$REPORT" || true
ps aux | grep 'nginx:' >> "$REPORT" || true
section "Config permissions"
ls -la /etc/nginx/ >> "$REPORT"
namei -l /etc/nginx/nginx.conf >> "$REPORT" 2>/dev/null || ls -l /etc/nginx/nginx.conf >> "$REPORT"
if su -s /bin/sh nginx -c 'test -w /etc/nginx/nginx.conf' 2>/dev/null; then
echo "FAIL: nginx can write nginx.conf" >> "$REPORT"
else
echo "OK: nginx cannot write nginx.conf" >> "$REPORT"
fi
section "Web root permissions"
ls -la /usr/share/nginx/html/public/ >> "$REPORT"
if su -s /bin/sh nginx -c 'test -w /usr/share/nginx/html/public/index.html' 2>/dev/null; then
echo "FAIL: nginx can write web root" >> "$REPORT"
else
echo "OK: nginx cannot write web root" >> "$REPORT"
fi
section "TLS certificates and keys"
ls -la /etc/nginx/ssl/ >> "$REPORT"
if su -s /bin/sh nginx -c 'cat /etc/nginx/ssl/server.key' 2>/dev/null; then
echo "FAIL: nginx can read private key" >> "$REPORT"
else
echo "OK: nginx cannot read private key" >> "$REPORT"
fi
section "htpasswd"
ls -la /etc/nginx/htpasswd >> "$REPORT" 2>/dev/null || true
if su -s /bin/sh nginx -c 'test -w /etc/nginx/htpasswd' 2>/dev/null; then
echo "FAIL: nginx can write htpasswd" >> "$REPORT"
else
echo "OK: nginx cannot write htpasswd" >> "$REPORT"
fi
section "Logs"
ls -la /var/log/nginx/ >> "$REPORT"
section "Temp/cache paths"
ls -la /var/cache/nginx/ >> "$REPORT" 2>/dev/null || echo "cache dir not yet created" >> "$REPORT"
nginx -T 2>/dev/null | grep -E 'client_body_temp_path|proxy_temp_path' >> "$REPORT" || true
echo "Audit permissions complete -> $REPORT"

View File

@@ -0,0 +1,45 @@
#!/bin/sh
# FIM substitute: SHA256 baseline (docs/Контроль_целостности; PMI p.24)
# Note: stand-in for afick, not a certification tool.
set -eu
REPORT="${REPORT:-/reports/fim.log}"
BASELINE="${BASELINE:-/tmp/fim-baseline.sha256}"
PATHS="/etc/nginx /usr/share/nginx/html /fim-watch"
WATCH_FILE="/fim-watch/config.snippet"
checksum() {
find $PATHS -type f 2>/dev/null | sort | xargs sha256sum 2>/dev/null
}
: > "$REPORT"
echo "FIM checksum audit $(date -Iseconds)" >> "$REPORT"
echo "NOTE: SHA256 baseline substitute for afick on test stand only." >> "$REPORT"
checksum > "$BASELINE"
echo "Baseline created ($(wc -l < "$BASELINE") files)" >> "$REPORT"
if checksum | diff -q "$BASELINE" - > /dev/null 2>&1; then
echo "PASS baseline check (no changes)" >> "$REPORT"
else
echo "FAIL unexpected baseline diff before tamper test" >> "$REPORT"
exit 1
fi
echo "# tamper $(date +%s)" >> "$WATCH_FILE"
if checksum | diff -q "$BASELINE" - > /dev/null 2>&1; then
echo "FAIL tamper not detected" >> "$REPORT"
exit 1
else
echo "PASS tamper detected after file change ($WATCH_FILE)" >> "$REPORT"
fi
printf '# FIM watch file for integrity test (PMI p.24 stand-in)\n' > "$WATCH_FILE"
if checksum | diff -q "$BASELINE" - > /dev/null 2>&1; then
echo "PASS baseline restored after revert" >> "$REPORT"
else
echo "WARN baseline differs after revert (review manually)" >> "$REPORT"
fi
echo "PASS #24 integrity control (checksum substitute for afick)" >> "$REPORT"
echo "FIM checksum complete -> $REPORT"

View File

@@ -0,0 +1,33 @@
#!/bin/sh
# Log checks for PMI items 12 and 13 (runs inside nginx container).
set -eu
REPORT="${REPORT:-/reports/pmi.log}"
ACCESS_LOG="/reports/nginx-file-access.log"
ERROR_LOG="/reports/nginx-file-error.log"
: > "$ACCESS_LOG"
: > "$ERROR_LOG"
curl -s --max-time 5 http://127.0.0.1/test-page > /dev/null
curl -s --max-time 5 http://127.0.0.1/another-page > /dev/null
curl -s --max-time 5 http://127.0.0.1/broken/ > /dev/null || true
sleep 1
if grep -q '/test-page' "$ACCESS_LOG" && grep -q '/another-page' "$ACCESS_LOG"; then
echo "PASS #12 access_log contains test-page and another-page" >> "$REPORT"
echo "PASS #12 access_log contains test-page and another-page"
else
echo "FAIL #12 access_log missing expected entries" >> "$REPORT"
echo "FAIL #12 access_log missing expected entries"
exit 1
fi
if grep -qE 'connect\(\) failed|Connection refused|upstream' "$ERROR_LOG"; then
echo "PASS #13 error_log contains upstream error" >> "$REPORT"
echo "PASS #13 error_log contains upstream error"
else
echo "FAIL #13 error_log missing upstream error" >> "$REPORT"
echo "FAIL #13 error_log missing upstream error"
exit 1
fi

View File

@@ -0,0 +1,291 @@
#!/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 "<title>Index of"; then
pass 21 "autoindex off (403, no listing)"
else
fail 21 "autoindex ($c21, listing=$(echo "$body21" | head -1))"
fi
# --- 22. proxy_pass ---
c22=$(http_code "$ALLOWED_IF" "$NGINX_A/api/status")
if [ "$c22" = "200" ]; then
pass 22 "proxy_pass to backend ($c22)"
else
fail 22 "proxy_pass ($c22 expected 200)"
fi
# --- 23. path traversal ---
c23a=$(http_code "$ALLOWED_IF" "$NGINX_A/index.html")
c23b=$(http_code "$ALLOWED_IF" "$NGINX_A/../../../etc/passwd")
if [ "$c23a" = "200" ] && [ "$c23b" = "404" ]; then
pass 23 "web root restriction ($c23a/$c23b)"
else
fail 23 "web root ($c23a/$c23b expected 200/404)"
fi
# --- 24. FIM (run-fim-checksum.sh) ---
log ""
log "=== Summary: PASS=$PASS FAIL=$FAIL SKIP=$SKIP ==="
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
exit 0

View File

@@ -0,0 +1,14 @@
#!/bin/sh
set -eu
echo "Waiting for nginx..."
for i in $(seq 1 30); do
if wget -q -O /dev/null http://nginx/public/index.html 2>/dev/null; then
echo "nginx is ready"
exit 0
fi
sleep 2
done
echo "nginx not ready after 60s" >&2
exit 1