From a0021d1994e86da0a0cbd815992785182499b73a Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Fri, 3 Jul 2026 21:36:23 +0200 Subject: [PATCH] feat(gateway): wire GATEWAY_HONEYTOKEN through the deploy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The planted honeytoken bearer trap already existed in the gateway + compose, but no workflow fed GATEWAY_HONEYTOKEN, so it was always empty (inert). Wire the per-contour TEST_/PROD_GATEWAY_HONEYTOKEN secret into the ci deploy, prod-deploy and prod-rollback env (the prod path via the shared deploy/write-prod-env.sh), document it (deploy/README + .env.example), and fix the stale compose comment. Empty secret = trap off (no ":?" guard), so a deploy is safe before the operator sets the value + plants the bait. On prod (IP ban on) presenting it earns a 24h ban + alarm; on test (ban off) it logs + a ban metric. GF_SMTP_ENABLED is enabled separately via the TEST_/PROD_GF_SMTP_ENABLED Gitea variables (=true) — no code change. --- .gitea/workflows/ci.yaml | 3 +++ .gitea/workflows/prod-deploy.yaml | 3 +++ .gitea/workflows/prod-rollback.yaml | 1 + deploy/.env.example | 7 +++++++ deploy/README.md | 6 ++++-- deploy/docker-compose.yml | 4 ++-- deploy/write-prod-env.sh | 1 + 7 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 4aad6a0..99880c1 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -335,6 +335,9 @@ jobs: # for the server-side confidential code exchange — a SEPARATE VK app from the Mini # App above. One VK ID "Web" app serves every contour -> unprefixed secret. GATEWAY_VK_ID_CLIENT_SECRET: ${{ secrets.GATEWAY_VK_ID_CLIENT_SECRET }} + # Planted honeytoken bearer: presenting it flags the caller (logs + a ban metric on + # test where the IP ban is off; a 24h IP ban on prod). Per-contour secret; empty = trap off. + GATEWAY_HONEYTOKEN: ${{ secrets.TEST_GATEWAY_HONEYTOKEN }} # Signs the finished-game export download URLs (backend + compose interpolation). EXPORT_SIGN_KEY: ${{ secrets.TEST_EXPORT_SIGN_KEY }} # Transactional email via the shared Selectel relay: one account for every diff --git a/.gitea/workflows/prod-deploy.yaml b/.gitea/workflows/prod-deploy.yaml index 884d747..3881010 100644 --- a/.gitea/workflows/prod-deploy.yaml +++ b/.gitea/workflows/prod-deploy.yaml @@ -98,6 +98,9 @@ jobs: # derived from PUBLIC_BASE_URL in deploy/write-prod-env.sh. VITE_VK_APP_ID: ${{ vars.VITE_VK_APP_ID }} GATEWAY_VK_ID_CLIENT_SECRET: ${{ secrets.GATEWAY_VK_ID_CLIENT_SECRET }} + # Planted honeytoken bearer: presenting it earns a 24h IP ban + a high-severity alarm. + # Per-contour secret; empty = trap off. Rendered by deploy/write-prod-env.sh. + GATEWAY_HONEYTOKEN: ${{ secrets.PROD_GATEWAY_HONEYTOKEN }} # Signs the finished-game export download URLs (backend BACKEND_EXPORT_SIGN_KEY). EXPORT_SIGN_KEY: ${{ secrets.PROD_EXPORT_SIGN_KEY }} # Transactional email via the shared Selectel relay (confirm-codes): one account for diff --git a/.gitea/workflows/prod-rollback.yaml b/.gitea/workflows/prod-rollback.yaml index 177907b..9f1e71f 100644 --- a/.gitea/workflows/prod-rollback.yaml +++ b/.gitea/workflows/prod-rollback.yaml @@ -63,6 +63,7 @@ jobs: GATEWAY_VK_APP_SECRET: ${{ secrets.GATEWAY_VK_APP_SECRET }} VITE_VK_APP_ID: ${{ vars.VITE_VK_APP_ID }} GATEWAY_VK_ID_CLIENT_SECRET: ${{ secrets.GATEWAY_VK_ID_CLIENT_SECRET }} + GATEWAY_HONEYTOKEN: ${{ secrets.PROD_GATEWAY_HONEYTOKEN }} EXPORT_SIGN_KEY: ${{ secrets.PROD_EXPORT_SIGN_KEY }} SMTP_RELAY_USER: ${{ secrets.SMTP_RELAY_USER }} SMTP_RELAY_PASS: ${{ secrets.SMTP_RELAY_PASS }} diff --git a/deploy/.env.example b/deploy/.env.example index 97a7d97..96bf39b 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -108,3 +108,10 @@ GATEWAY_VK_APP_SECRET= # the confidential OAuth 2.1 code exchange under this protected key. The app id + redirect URL # come from VITE_VK_APP_ID / VITE_VK_ID_REDIRECT_URL above. All three empty disables link.vk.*. GATEWAY_VK_ID_CLIENT_SECRET= + +# --- Gateway anti-abuse ------------------------------------------------------ +# Planted honeytoken bearer value: any request presenting it is flagged — a 24h IP ban +# where the IP ban is on (prod), logs + a ban metric otherwise (test). Plant the value +# somewhere an attacker would find it; empty disables the trap. Gitea +# TEST_/PROD_GATEWAY_HONEYTOKEN (secret). +GATEWAY_HONEYTOKEN= diff --git a/deploy/README.md b/deploy/README.md index c129eef..883ef00 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -111,6 +111,7 @@ without it Docker's resolver handles `otelcol`, `gateway` and `api.telegram.org` | `VITE_VK_ID_REDIRECT_URL` | derived | _(empty)_ | VK ID trusted redirect URL — must match the app's registered redirect exactly. The deploy derives `PUBLIC_BASE_URL + /app/` (used by the SPA and as the gateway's exchange `redirect_uri`); set it only for a local run. | | `GATEWAY_VK_APP_SECRET` | secret (shared) | _(empty)_ | The VK Mini App's protected key (`client_secret`): the gateway verifies the launch-parameter signature in-process under it (offline HMAC, no VK API call). Empty disables the VK auth path (`auth.vk`). One VK Mini App for all contours. | | `GATEWAY_VK_ID_CLIENT_SECRET` | secret (shared) | _(empty)_ | The VK ID "Web" app's protected key (`client_secret`) for the gateway's server-side confidential code exchange. A SEPARATE VK app from `GATEWAY_VK_APP_SECRET` (the Mini App). One VK ID "Web" app for all contours. | +| `GATEWAY_HONEYTOKEN` | secret | _(empty)_ | Planted honeytoken bearer value: presenting it earns a 24h IP ban + a high-severity alarm where the IP ban is on (prod), or logs + a `gateway_abuse_banned_total{reason="honeytoken"}` metric (test, ban off). Plant the value somewhere an attacker would find it; empty disables the trap. Per-contour `TEST_`/`PROD_GATEWAY_HONEYTOKEN`. | | `VITE_GATEWAY_URL` | variable | _(empty)_ | UI build-arg: gateway origin; empty = same-origin (the usual single-origin deploy). | | `SMTP_RELAY_HOST` | variable (shared) | _(empty)_ | Selectel SMTP relay host for confirm-code email. Empty leaves the backend on the log mailer (email disabled) — the contour still boots. One relay for every contour (limit 100 msgs / 5 min). | | `SMTP_RELAY_PORT` | variable (shared) | `465` | Relay port. No client certificate is needed (the server cert is validated against the system roots). | @@ -232,8 +233,9 @@ VITE_VK_APP_LINK, VITE_VK_APP_ID`. **Derived from `PUBLIC_BASE_URL` at deploy** **`PROD_` Gitea set** (per-contour, mirrors `TEST_`, mapped onto the unprefixed names above) — secrets: `PROD_{POSTGRES_PASSWORD, GM_BASICAUTH_HASH, GRAFANA_ADMIN_PASSWORD, TELEGRAM_BOT_TOKEN, -TELEGRAM_PROMO_BOT_TOKEN, EXPORT_SIGN_KEY, REGISTRY_PASSWORD, SSH_KEY, SSH_KNOWN_HOSTS, BOTLINK_CA, -BOTLINK_GATEWAY_CERT, BOTLINK_GATEWAY_KEY, BOTLINK_BOT_CERT, BOTLINK_BOT_KEY}`; variables: +TELEGRAM_PROMO_BOT_TOKEN, EXPORT_SIGN_KEY, GATEWAY_HONEYTOKEN, REGISTRY_PASSWORD, SSH_KEY, +SSH_KNOWN_HOSTS, BOTLINK_CA, BOTLINK_GATEWAY_CERT, BOTLINK_GATEWAY_KEY, BOTLINK_BOT_CERT, +BOTLINK_BOT_KEY}`; variables: `PROD_{REGISTRY_USER, MAIN_HOST, TG_HOST, CADDY_SITE_ADDRESS, GM_BASICAUTH_USER, LOG_LEVEL, TELEGRAM_GAME_CHANNEL_ID, TELEGRAM_CHAT_ID, TELEGRAM_SUPPORT_CHAT_ID, TELEGRAM_BOT_USERNAME, VITE_TELEGRAM_BOT_ID, VITE_TELEGRAM_LINK, VITE_TELEGRAM_GAME_CHANNEL_NAME, SMTP_RELAY_FROM, diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 97cbf1b..3110443 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -231,8 +231,8 @@ services: # in prod — the test contour arrives as one shared NAT address, so a ban there # would be self-inflicted (the honeypot still logs). The prod deploy forces it on # in env.sh (deploy/write-prod-env.sh), not via a Gitea variable. GATEWAY_HONEYTOKEN - # is the planted bearer trap; it is currently unset on every contour (no Gitea entry - # feeds it), so the trap is inert until an operator wires a secret. + # is the planted bearer trap, fed from the per-contour TEST_/PROD_GATEWAY_HONEYTOKEN + # secret; empty (unset secret) leaves the trap off. GATEWAY_ABUSE_BAN_ENABLED: ${GATEWAY_ABUSE_BAN_ENABLED:-false} GATEWAY_HONEYTOKEN: ${GATEWAY_HONEYTOKEN:-} GATEWAY_LOG_LEVEL: ${LOG_LEVEL:-info} diff --git a/deploy/write-prod-env.sh b/deploy/write-prod-env.sh index baa05be..d0460cb 100755 --- a/deploy/write-prod-env.sh +++ b/deploy/write-prod-env.sh @@ -71,5 +71,6 @@ export SERVICE_EMAIL='$SERVICE_EMAIL' export GRAFANA_SMTP_PORT='$GRAFANA_SMTP_PORT' export GF_SMTP_ENABLED='$GF_SMTP_ENABLED' export PUBLIC_BASE_URL='$PUBLIC_BASE_URL' +export GATEWAY_HONEYTOKEN='$GATEWAY_HONEYTOKEN' export GATEWAY_ABUSE_BAN_ENABLED='true' EOF