Files
terminal/.github/workflows/deploy-site.yml
T
Michael Shimeles 0690de8c6c Harden the gateway: optional token for /stats and /metrics, per-address cap, flood close, sandboxed unit (#33)
* Gate /stats and /metrics behind an optional token and harden the gateway

- TR_METRICS_TOKEN: when set, /stats and /metrics answer 404 unless the
  request sends it as a bearer token. /healthz and /readyz stay public.
  The token never shows in Debug output or errors.
- TR_MAX_CLIENTS_PER_IP caps connections per address (IPv6 per /64), off
  by default. TR_CLIENT_IP_HEADER takes the address from the edge's
  header. The first connection logs whether x-forwarded-for was there,
  never its value, so the cap can be checked behind Freestyle's edge.
- A client that keeps sending after a budget's worth of RateLimited
  replies is closed with 1008 instead of being answered forever.
- cli stats sends TR_METRICS_TOKEN when set.
- vm-setup.sh: optional /etc/tr-gateway/env, stop timeout, memory and
  task caps, and systemd sandboxing (security score 8.3 to 3.1).

* Let the site's server URL come from a repository variable

deploy-site.yml passes the TR_SERVER_URL repository variable to
build-web.sh when it's set, so moving the site to the gateway's custom
domain is a variable and a redeploy, not a code change.

* Accept the bearer scheme in any case

HTTP auth scheme names are case-insensitive, so "bearer <token>" now
works too. The token itself is still compared exactly.
2026-11-02 15:39:07 -05:00

129 lines
5.1 KiB
YAML

name: deploy site
# Builds the browser app with scripts/build-web.sh and deploys the site with it to Vercel:
# production on every push to master, a preview for each pull request. Vercel's own Git
# deployments are off (site/vercel.json) because Vercel's build doesn't run Rust.
on:
push:
branches: [master]
pull_request:
workflow_dispatch:
# Production runs queue instead of cancelling each other, so the newest commit always deploys
# last. A new push to a pull request cancels the preview run for the old one.
concurrency:
group: deploy-site-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
wasm:
name: Build the browser app
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
with:
# Pull requests build the branch head, like Vercel's Git previews did, not the merge commit.
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- name: Read the wasm-bindgen version from Cargo.lock
id: lock
run: echo "wasm-bindgen=$(awk '/^name = "wasm-bindgen"$/ { getline; gsub(/version = |"/, ""); print; exit }' Cargo.lock)" >> "$GITHUB_OUTPUT"
- uses: taiki-e/install-action@v2
with:
tool: wasm-bindgen@${{ steps.lock.outputs.wasm-bindgen }}
- name: Install wasm-opt (binaryen)
env:
BINARYEN: version_133
SHA256: 2dc9c7813f5375db93d96ead4b78222fcc3e2677bbb832297af4797782a37489
run: |
curl -sSfLo "$RUNNER_TEMP/binaryen.tar.gz" "https://github.com/WebAssembly/binaryen/releases/download/$BINARYEN/binaryen-$BINARYEN-x86_64-linux.tar.gz"
echo "$SHA256 $RUNNER_TEMP/binaryen.tar.gz" | sha256sum -c -
tar xzf "$RUNNER_TEMP/binaryen.tar.gz" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/binaryen-$BINARYEN/bin" >> "$GITHUB_PATH"
# The repository variable TR_SERVER_URL, when set, picks the server the site connects to,
# for example after the gateway moves to a custom domain. Unset, build-web.sh's default holds.
- name: Build the browser app
env:
SERVER_URL: ${{ vars.TR_SERVER_URL }}
run: |
if [ -n "$SERVER_URL" ]; then export TR_SERVER_URL="$SERVER_URL"; fi
scripts/build-web.sh
- uses: actions/upload-artifact@v7
with:
name: web-app
path: site/static/app
if-no-files-found: error
retention-days: 7
deploy:
name: Deploy to Vercel
needs: wasm
# Pull requests from forks get no secrets, so they stop after the build.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
environment:
name: ${{ github.ref == 'refs/heads/master' && 'production' || 'preview' }}
url: ${{ steps.deploy.outputs.url }}
env:
TARGET: ${{ github.ref == 'refs/heads/master' && 'production' || 'preview' }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
steps:
- name: Check the Vercel secrets
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
missing=0
for name in VERCEL_TOKEN VERCEL_ORG_ID VERCEL_PROJECT_ID; do
if [ -z "${!name}" ]; then
echo "::error::The $name repository secret is not set (see site/README.md)"
missing=1
fi
done
exit $missing
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/download-artifact@v8
with:
name: web-app
path: site/static/app
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
cache-dependency-path: site/package-lock.json
- run: npm install --global [email protected]
# Run from the repository root, because the Vercel project's Root Directory is site/.
- name: Pull the project settings and environment variables
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: vercel pull --yes --environment="$TARGET" --token="$VERCEL_TOKEN"
# The build gets no token, since it runs the site's npm install and build scripts.
- name: Build
run: |
if [ "$TARGET" = production ]; then vercel build --prod; else vercel build; fi
test -f .vercel/output/static/app/index.html
- name: Deploy
id: deploy
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
if [ "$TARGET" = production ]; then
url=$(vercel deploy --prebuilt --prod --token="$VERCEL_TOKEN")
else
url=$(vercel deploy --prebuilt --token="$VERCEL_TOKEN")
fi
echo "url=$url" >> "$GITHUB_OUTPUT"
echo "Deployed $TARGET: $url ($url/app)" >> "$GITHUB_STEP_SUMMARY"