diff --git a/backend/deploy/DEPLOYMENT.md b/backend/deploy/DEPLOYMENT.md index 0a11d88..432b73a 100644 --- a/backend/deploy/DEPLOYMENT.md +++ b/backend/deploy/DEPLOYMENT.md @@ -222,18 +222,25 @@ Without this, updating means SSHing in and running the commands under "Updating later" by hand every time. This section makes a `git push` to your own Gitea/Forgejo trigger a pull + restart automatically. -Since Git already runs on this same VPS, the webhook listener never needs to -be reachable from the internet - it binds to `127.0.0.1` only, and Gitea -calls it over loopback. Nothing new is exposed publicly. +**Check this first:** run `docker ps` to see whether Gitea/Forgejo runs in a +Docker container. This decides which of the two setups below applies. -**Check this first:** if Gitea/Forgejo runs inside Docker, `127.0.0.1` inside -its container is the container's own loopback, not the host's - the webhook -target below would not connect. Run `docker ps` to check. If Gitea is -containerized, either add the webhook service to the same Docker network and -call it by container name, or expose the webhook through the nginx vhost -(same pattern as `/api/audit-request` in step 7) with an `allow`/`deny` block -restricting it to the container network's subnet, instead of the loopback -target used below. +**Gitea runs natively on this machine (not in Docker):** the webhook listener +never needs to be reachable from the internet - it binds to `127.0.0.1` +only, and Gitea calls it over loopback. Use `http://127.0.0.1:3002/deploy-webhook` +as the Target URL in step 11.4 and skip the nginx part of step 11.3. + +**Gitea runs in Docker:** inside that container, `127.0.0.1` is the +container's own loopback, not this host - a target of `127.0.0.1:3002` would +never connect, regardless of Docker's networking mode. Rather than depending +on that container's specific network setup (bridge vs. host, custom networks, +`host.docker.internal` availability - all of which vary by how it was set +up), route the webhook through the same nginx vhost as the audit endpoint +instead: the request just goes out over HTTPS to `api.sascha-bach.de` and +back in, exactly like any other webhook call, independent of how Gitea's +container is networked. `backend/deploy/nginx-api.sascha-bach.de.conf` +already has the `/deploy-webhook` location for this - see step 11.3. Use +`https://api.sascha-bach.de/deploy-webhook` as the Target URL in step 11.4. ### 11.1 Generate a secret and configure it @@ -274,6 +281,17 @@ sudo systemctl enable --now audit-webhook systemctl status audit-webhook --no-pager ``` +**Only if Gitea runs in Docker**, additionally expose it through nginx (the +config was already updated when you copied it in step 7 - if that was before +this section existed, re-copy it): + +```bash +sudo cp /opt/audit-endpoint/backend/deploy/nginx-api.sascha-bach.de.conf \ + /etc/nginx/sites-available/api.sascha-bach.de +sudo nginx -t +sudo systemctl reload nginx +``` + ### 11.4 Register the webhook in Gitea/Forgejo In the repository on `git.sascha-bach.de`: **Settings → Webhooks → Add @@ -282,7 +300,7 @@ Gitea fork and uses the same webhook format). | Field | Value | | --- | --- | -| Target URL | `http://127.0.0.1:3002/deploy-webhook` | +| Target URL | `http://127.0.0.1:3002/deploy-webhook` (native Gitea) **or** `https://api.sascha-bach.de/deploy-webhook` (Gitea in Docker) | | HTTP Method | `POST` | | POST Content Type | `application/json` | | Secret | the value generated in 11.1 | diff --git a/backend/deploy/nginx-api.sascha-bach.de.conf b/backend/deploy/nginx-api.sascha-bach.de.conf index 93b6f98..2ad0916 100644 --- a/backend/deploy/nginx-api.sascha-bach.de.conf +++ b/backend/deploy/nginx-api.sascha-bach.de.conf @@ -22,6 +22,13 @@ server { } } +# Rate-limits /deploy-webhook specifically. It is now reachable over the +# public hostname (see the location block below), so unlike +# /api/audit-request it has no per-request Node-side limiter of its own - +# the HMAC signature check is the real gate, this is just a cheap first +# filter against noise. +limit_req_zone $binary_remote_addr zone=deploy_webhook:1m rate=10r/m; + server { listen 443 ssl; listen [::]:443 ssl; @@ -34,7 +41,7 @@ server { # so they never reach it. client_max_body_size 16k; - # Only these two paths exist. Everything else is refused rather than + # Only these paths exist. Everything else is refused rather than # forwarded, which keeps the attack surface to what the form needs. location = /api/audit-request { proxy_pass http://127.0.0.1:3001; @@ -57,6 +64,25 @@ server { access_log off; } + # Only needed if Gitea/Forgejo runs in a Docker container: 127.0.0.1 + # inside that container is the container's own loopback, not this host, + # so the webhook target can't be http://127.0.0.1:3002 in that case. + # Routing it here instead works regardless of the container network + # setup - the request simply goes out over HTTPS to this hostname and + # back in, the same way any other webhook call would. + # + # If Gitea runs natively on this same machine (not in Docker), this + # block is not needed - point the Gitea webhook straight at + # http://127.0.0.1:3002/deploy-webhook instead and skip this location. + location = /deploy-webhook { + limit_req zone=deploy_webhook burst=5 nodelay; + proxy_pass http://127.0.0.1:3002; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 30s; + } + location / { return 404; }