#!/bin/bash # test-webhook-security.sh - Tests for webhook security integration # # Validates that all webhook security files are present and correctly structured # in the repository. Tests run offline (no system services required). set -uo pipefail RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' pass() { echo -e "${GREEN}[PASS]${NC} $*"; } fail() { echo -e "${RED}[FAIL]${NC} $*"; FAILURES=$((FAILURES + 1)); } FAILURES=0 REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" echo "" echo "Testing Webhook Security Integration" echo "======================================" echo "" # --- 1. File existence checks --- echo "1. File existence" EXPECTED_FILES=( "scripts/webhook-security/gitea-hmac-verify.js" "scripts/webhook-security/gitea-approve-repo" "scripts/webhook-security/rotate-webhook-secret.sh" "scripts/webhook-security/webhook-audit-alert.sh" "scripts/webhook-security/ntfy-blocked-pickup.sh" "templates/webhook-security/nginx-site.conf.example" "templates/webhook-security/nginx.conf.example" "templates/webhook-security/gitea-repo-allowlist.json.example" "docs/WEBHOOK-SECURITY.md" "docs/SECURITY-AUDIT.md" ) for f in "${EXPECTED_FILES[@]}"; do if [ -f "$REPO_ROOT/$f" ]; then pass "$f exists" else fail "$f MISSING" fi done echo "" # --- 2. Template placeholder checks --- echo "2. Template placeholders in scripts" TEMPLATED_SCRIPTS=( "scripts/webhook-security/rotate-webhook-secret.sh" "scripts/webhook-security/webhook-audit-alert.sh" "scripts/webhook-security/ntfy-blocked-pickup.sh" ) for script in "${TEMPLATED_SCRIPTS[@]}"; do if grep -qE '@@[A-Z_]+@@' "$REPO_ROOT/$script"; then pass "$script has placeholder tokens (@@...@@)" else fail "$script has no placeholder tokens — check that hardcoded values were templated" fi done # Verify no hardcoded URLs in templated scripts for script in "${TEMPLATED_SCRIPTS[@]}"; do if grep -qE 'https://[a-z][a-z0-9.-]+\.(com|org|net|sh)' "$REPO_ROOT/$script" 2>/dev/null; then # Check if it's a placeholder URL or a real one REAL_URLS=$(grep -E 'https://[a-z][a-z0-9.-]+\.(com|org|net|sh)' "$REPO_ROOT/$script" | grep -v '@@\|example\|YOUR_\|placeholder\|ntfy.sh/@@' || true) if [ -n "$REAL_URLS" ]; then fail "$script may contain hardcoded URLs: $REAL_URLS" else pass "$script URL patterns are placeholders" fi else pass "$script has no hardcoded domain URLs" fi done echo "" # --- 3. Allowlist JSON validity --- echo "3. Template JSON validity" ALLOWLIST_EXAMPLE="$REPO_ROOT/templates/webhook-security/gitea-repo-allowlist.json.example" if [ -f "$ALLOWLIST_EXAMPLE" ]; then if python3 -c " import json, sys with open('$ALLOWLIST_EXAMPLE') as f: data = json.load(f) has_repos = 'repos' in data has_owners = 'trusted_owners' in data if not has_repos or not has_owners: sys.exit(1) " 2>/dev/null; then pass "gitea-repo-allowlist.json.example is valid JSON with repos + trusted_owners" else fail "gitea-repo-allowlist.json.example is invalid JSON or missing required keys" fi else fail "gitea-repo-allowlist.json.example not found" fi echo "" # --- 4. Secret generation format --- echo "4. Secret generation (openssl)" if command -v openssl &>/dev/null; then GENERATED_SECRET=$(openssl rand -hex 32) SECRET_LEN=${#GENERATED_SECRET} if [ "$SECRET_LEN" -eq 64 ]; then pass "openssl rand -hex 32 produces 64-char hex string (got: ${GENERATED_SECRET:0:8}...)" else fail "openssl rand -hex 32 produced ${SECRET_LEN}-char string (expected 64)" fi # Verify it's valid hex if echo "$GENERATED_SECRET" | grep -qE '^[0-9a-f]{64}$'; then pass "Generated secret is valid lowercase hex" else fail "Generated secret contains non-hex characters" fi else pass "openssl not available — skipping secret generation test (ok in CI)" fi echo "" # --- 5. njs module structure check --- echo "5. njs module (gitea-hmac-verify.js)" NJS_FILE="$REPO_ROOT/scripts/webhook-security/gitea-hmac-verify.js" if [ -f "$NJS_FILE" ]; then # Check it's an ES module with the expected export if grep -q "export default" "$NJS_FILE"; then pass "gitea-hmac-verify.js has ES module export" else fail "gitea-hmac-verify.js missing 'export default'" fi # Check key functions are present for fn in verifyAndProxy isRepoAllowed constantTimeEqual getSecret loadAllowlist; do if grep -q "$fn" "$NJS_FILE"; then pass "gitea-hmac-verify.js has function: $fn" else fail "gitea-hmac-verify.js missing function: $fn" fi done # Check HMAC-SHA256 usage if grep -q "createHmac.*sha256" "$NJS_FILE"; then pass "gitea-hmac-verify.js uses HMAC-SHA256" else fail "gitea-hmac-verify.js missing HMAC-SHA256 usage" fi else fail "gitea-hmac-verify.js not found" fi echo "" # --- 6. setup.sh integration check --- echo "6. setup.sh integration" SETUP_FILE="$REPO_ROOT/setup.sh" if [ -f "$SETUP_FILE" ]; then # Check Step 11 is present if grep -q "Step 11.*Gitea Webhook Security" "$SETUP_FILE"; then pass "setup.sh contains Step 11 (Gitea Webhook Security)" else fail "setup.sh missing Step 11" fi # Check key deployment steps for marker in "gitea-hmac-verify.js" "gitea-webhook-secret" "gitea-repo-allowlist.json" "gitea-approve-repo" "WEBHOOK_SECURITY_INSTALLED"; do if grep -q "$marker" "$SETUP_FILE"; then pass "setup.sh references: $marker" else fail "setup.sh missing reference to: $marker" fi done # Check that step 11 comes before SUMMARY STEP11_LINE=$(grep -n "Step 11.*Gitea Webhook Security" "$SETUP_FILE" | head -1 | cut -d: -f1) SUMMARY_LINE=$(grep -n "^# SUMMARY" "$SETUP_FILE" | head -1 | cut -d: -f1) if [ -n "$STEP11_LINE" ] && [ -n "$SUMMARY_LINE" ] && [ "$STEP11_LINE" -lt "$SUMMARY_LINE" ]; then pass "Step 11 appears before SUMMARY (lines $STEP11_LINE vs $SUMMARY_LINE)" else fail "Step 11 ordering issue (step11=$STEP11_LINE, summary=$SUMMARY_LINE)" fi else fail "setup.sh not found" fi echo "" # --- 7. uninstall.sh integration check --- echo "7. uninstall.sh integration" UNINSTALL_FILE="$REPO_ROOT/scripts/uninstall.sh" if [ -f "$UNINSTALL_FILE" ]; then for marker in "gitea-hmac-verify.js" "gitea-webhook-secret" "gitea-repo-allowlist.json" "opt/webhook-security" "gitea-approve-repo"; do if grep -q "$marker" "$UNINSTALL_FILE"; then pass "uninstall.sh references: $marker" else fail "uninstall.sh missing reference to: $marker" fi done # Check cron cleanup if grep -q "crontab\|cron" "$UNINSTALL_FILE"; then pass "uninstall.sh has cron cleanup" else fail "uninstall.sh missing cron cleanup" fi else fail "uninstall.sh not found" fi echo "" # --- 8. Bash syntax checks --- echo "8. Bash syntax" for script in \ "$REPO_ROOT/setup.sh" \ "$REPO_ROOT/scripts/uninstall.sh" \ "$REPO_ROOT/scripts/webhook-security/rotate-webhook-secret.sh" \ "$REPO_ROOT/scripts/webhook-security/webhook-audit-alert.sh" \ "$REPO_ROOT/scripts/webhook-security/ntfy-blocked-pickup.sh" \ "$REPO_ROOT/scripts/webhook-security/gitea-approve-repo"; do FNAME=$(basename "$script") if bash -n "$script" 2>/dev/null; then pass "bash syntax OK: $FNAME" else fail "bash syntax ERROR: $FNAME" fi done echo "" # --- Summary --- echo "======================================" if [ "$FAILURES" -eq 0 ]; then echo -e "${GREEN}All tests passed${NC}" exit 0 else echo -e "${RED}$FAILURES test(s) failed${NC}" exit 1 fi