feat: merge Gitea webhook security into setup wizard (issue #2)
Integrates the 5-layer Gitea webhook security system from sol/clawgravity-hook-security (v2.0) into the setup wizard. ## What's added ### New files (from clawgravity-hook-security v2.0) - scripts/webhook-security/gitea-hmac-verify.js -- njs HMAC-SHA256 module - scripts/webhook-security/gitea-approve-repo -- allowlist helper - scripts/webhook-security/rotate-webhook-secret.sh -- monthly secret rotation (templated) - scripts/webhook-security/webhook-audit-alert.sh -- daily audit summaries (templated) - scripts/webhook-security/ntfy-blocked-pickup.sh -- blocked webhook alerts (templated) - 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 -- full documentation - docs/SECURITY-AUDIT.md -- 35-case test matrix - tests/test-webhook-security.sh -- 48 offline tests ### Modified files - setup.sh: Step 11 (webhook security wizard with 6 sub-sections) - scripts/uninstall.sh: webhook security cleanup section - README.md: Webhook Security section after Quick Start - Makefile: test target now runs test-webhook-security.sh - .secret-scan-allowlist: allowlist docs/SECURITY-AUDIT.md (test fixture) ## Security layers 1. IP allowlisting (nginx) 2. Rate limiting 10 req/s burst 20 (nginx) 3. Payload size 1MB (nginx) 4. HMAC-SHA256 signature verification (njs) 5. Per-repository allowlist (njs) ## make check - prettier: PASS - secret-scan: PASS - tests: 48/48 PASS Closes #2
This commit is contained in:
86
scripts/webhook-security/gitea-approve-repo
Executable file
86
scripts/webhook-security/gitea-approve-repo
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# gitea-approve-repo - Add a Gitea repo to the webhook allowlist
|
||||
# Usage: gitea-approve-repo owner/repo
|
||||
# After adding, validates nginx config. You must manually reload nginx.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ALLOWLIST="/etc/nginx/gitea-repo-allowlist.json"
|
||||
REPO="${1:-}"
|
||||
|
||||
if [ -z "$REPO" ]; then
|
||||
echo "Usage: gitea-approve-repo owner/repo"
|
||||
echo ""
|
||||
echo "Adds a repository to the Gitea webhook allowlist."
|
||||
echo "After adding, validates with nginx -t."
|
||||
echo "You must manually run: sudo nginx -s reload"
|
||||
echo ""
|
||||
echo "Current allowlist:"
|
||||
if [ -f "$ALLOWLIST" ]; then
|
||||
python3 -c "import json; d=json.load(open('$ALLOWLIST')); [print(' - ' + r) for r in d.get('repos', [])]"
|
||||
echo ""
|
||||
echo "Trusted owners:"
|
||||
python3 -c "import json; d=json.load(open('$ALLOWLIST')); [print(' - ' + o) for o in d.get('trusted_owners', [])]"
|
||||
else
|
||||
echo " (file not found: $ALLOWLIST)"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate format: must contain exactly one /
|
||||
if ! echo "$REPO" | grep -qP '^[^/]+/[^/]+$'; then
|
||||
echo "ERROR: Invalid repo format. Must be: owner/repo (e.g. myorg/my-project)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if already approved
|
||||
if [ -f "$ALLOWLIST" ]; then
|
||||
EXISTING=$(python3 -c "import json; d=json.load(open('$ALLOWLIST')); print('yes' if '$REPO' in d.get('repos', []) else 'no')")
|
||||
if [ "$EXISTING" = "yes" ]; then
|
||||
echo "Repo '$REPO' is already in the allowlist."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if owner is trusted (auto-allowed)
|
||||
OWNER_TRUSTED=$(python3 -c "import json; d=json.load(open('$ALLOWLIST')); owner='$REPO'.split('/')[0]; print('yes' if owner in d.get('trusted_owners', []) else 'no')")
|
||||
if [ "$OWNER_TRUSTED" = "yes" ]; then
|
||||
echo "Repo '$REPO' is already allowed via trusted owner."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add to allowlist
|
||||
if [ ! -f "$ALLOWLIST" ]; then
|
||||
echo "ERROR: Allowlist file not found: $ALLOWLIST"
|
||||
echo "Create it first with: echo '{\"repos\": [], \"trusted_owners\": []}' > $ALLOWLIST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use python3 to safely modify JSON
|
||||
python3 -c "
|
||||
import json
|
||||
with open('$ALLOWLIST', 'r') as f:
|
||||
data = json.load(f)
|
||||
data.setdefault('repos', []).append('$REPO')
|
||||
with open('$ALLOWLIST', 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
f.write('\n')
|
||||
print('Added: $REPO')
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "Updated allowlist:"
|
||||
python3 -c "import json; d=json.load(open('$ALLOWLIST')); [print(' - ' + r) for r in d.get('repos', [])]"
|
||||
echo ""
|
||||
|
||||
# Validate nginx config
|
||||
echo "Validating nginx configuration..."
|
||||
if nginx -t 2>&1; then
|
||||
echo ""
|
||||
echo "Config is valid. To activate, run:"
|
||||
echo " sudo nginx -s reload"
|
||||
else
|
||||
echo ""
|
||||
echo "WARNING: nginx -t failed! Check the configuration before reloading."
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user