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
87 lines
3.1 KiB
Plaintext
87 lines
3.1 KiB
Plaintext
server {
|
|
server_name YOUR_DOMAIN;
|
|
|
|
# Internal upstream for Gitea webhook (post-HMAC verification)
|
|
location /hooks/gitea-upstream {
|
|
internal;
|
|
proxy_pass http://127.0.0.1:YOUR_OPENCLAW_PORT/hooks/gitea;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header Authorization "Bearer YOUR_OPENCLAW_GATEWAY_TOKEN";
|
|
proxy_set_header X-Gitea-Event $http_x_gitea_event;
|
|
proxy_set_header X-Gitea-Delivery $http_x_gitea_delivery;
|
|
proxy_set_header X-Gitea-Signature $http_x_gitea_signature;
|
|
proxy_buffering off;
|
|
proxy_connect_timeout 10s;
|
|
proxy_send_timeout 30s;
|
|
proxy_read_timeout 30s;
|
|
proxy_pass_request_body on;
|
|
}
|
|
|
|
# Other hooks pass through directly
|
|
location /hooks/ {
|
|
proxy_pass http://127.0.0.1:YOUR_OPENCLAW_PORT/hooks/;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_buffering off;
|
|
proxy_connect_timeout 10s;
|
|
proxy_send_timeout 30s;
|
|
proxy_read_timeout 30s;
|
|
client_max_body_size 1m;
|
|
}
|
|
|
|
# Gitea webhook - HMAC-SHA256 verified by njs before proxying
|
|
# Security layers: IP allowlist -> rate limit -> HMAC verify -> repo allowlist -> proxy
|
|
location = /hooks/gitea {
|
|
# Layer 1: IP allowlisting - only allow the Gitea server IP
|
|
# Find your Gitea IP with: dig +short YOUR_GITEA_DOMAIN
|
|
allow YOUR_GITEA_SERVER_IP; # YOUR_GITEA_DOMAIN
|
|
allow 127.0.0.1; # localhost (for testing)
|
|
allow ::1; # IPv6 localhost
|
|
deny all;
|
|
|
|
# Layer 2: Rate limiting
|
|
limit_req zone=gitea_webhook burst=20 nodelay;
|
|
|
|
# Layer 3: Payload size limit
|
|
client_body_buffer_size 1m;
|
|
client_max_body_size 1m;
|
|
|
|
# Layer 4+5: HMAC verification + repo allowlist (njs)
|
|
js_content gitea_hmac.verifyAndProxy;
|
|
}
|
|
|
|
# Main application (adjust to your backend)
|
|
location / {
|
|
proxy_pass http://127.0.0.1:YOUR_APP_PORT;
|
|
proxy_set_header Host $http_host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Frame-Options SAMEORIGIN;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
client_max_body_size 50M;
|
|
}
|
|
|
|
listen 443 ssl;
|
|
ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
}
|
|
|
|
server {
|
|
if ($host = YOUR_DOMAIN) {
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
|
|
server_name YOUR_DOMAIN;
|
|
listen 80;
|
|
return 404;
|
|
}
|