Private download gateway
cmd/gbb-gateway is an independently deployed process. It exposes only the
artifact GET/HEAD route, /livez, and /readyz. No build runner, migration,
Turnstile exchange, administrative route, or upload handler starts in this
process. The API remains responsible for exchanging Turnstile tokens.
Route /api/v1/builds/{id}/artifacts/{name} to the gateway at the same HTTPS
origin as the API. Existing frontend links and __Host- cookies keep working.
Set GBB_DOWNLOAD_GATEWAY_ENABLED=true on the API at cutover: its old download
handler then refuses delivery with 503 if a request reaches it accidentally.
Do not send a download through both processes: the ingress routes it directly
so the shared request allowance is spent once.
Authorization and delivery
Each request, including HEAD, checks shared request limits, a two-per-IP transfer lease, the 15-minute browser/IP-bound verification session, and PostgreSQL publication state. Only artifacts recorded on a succeeded build are served. Unknown or unpublished objects return 404; dependency failures return retryable 503; exhausted limits return 429. A URL alone grants no access. Losing the browser cookie or changing IP requires verification again. Existing transfers continue after verification expires; every resumed request verifies again.
The gateway streams private S3 objects, never redirects or exposes storage URLs.
It accepts one byte range (closed, open-ended, or suffix), returns 206 and
Content-Range, and rejects invalid/multiple ranges with 416. If-Range supports
the SHA-256 ETag; a mismatch returns the whole object. Responses include
Content-Length, Accept-Ranges, X-Artifact-SHA256 and an attachment filename.
?checksum=1 remains available after the same authorization checks. Historical
artifacts without a valid recorded SHA-256/positive size fail closed; records
are not rewritten to invent missing provenance.
S3 reads use HEAD followed by a conditional ranged GET against that object's ETag; a changed object, mismatched publication metadata, or a store that ignores the range fails closed. Storage policies must prohibit overwriting published objects. A transfer buffers 32 KiB and has 30-second storage/client idle bounds. Redis leases expire after 30 seconds and renew every ten seconds. Failed renewal or an exact-IP ban marker cancels storage reads and interrupts the client connection; partial transfers are never falsely completed.
All responses disable shared caching (private, no-store); ingress/CDN rules
must preserve this and disable download caching/buffering. Never configure a
public bucket or CDN storage redirect as a fallback.
Configuration and credentials
Build with go build ./cmd/gbb-gateway or
podman build -f build/docker/gateway.Dockerfile -t gbb/gateway ..
The command takes -config PATH, defaulting to configs/gateway.yaml, and
accepts the same database, redis and s3 blocks the API uses — including
their discrete-field and *_file forms. See
configs/gateway.example.yaml (configs/gateway.example.yaml in the repo) and
Configuration for the precedence rules.
The file is optional. With none present the gateway is configured entirely from the environment, exactly as it was before the flag existed, so an existing deployment needs no manifest change. Every variable below is still read, and still wins over a value in the file:
GBB_DATABASE_DSN: dedicated PostgreSQL login with SELECT on jobs and narrowly scoped abuse-table writes (see grants (deploy/k8s/gateway/grants.example.sqlin the repo)). Startup performs no migrations or token bootstrap.GBB_HUMAN_REDIS_URL: the shared Redis endpoint with a dedicated ACL login. Equivalently theredis:block, whosepassword_fileandtls.ca_bundle_pathkeep the ACL password out of the environment and trust a private CA.GBB_HUMAN_TRUSTED_PROXIES: whitespace-separated immediate Traefik CIDRs, identical to API trust configuration. Untrusted peers cannot choose X-Real-IP.GBB_GATEWAY_S3_BUCKET,GBB_GATEWAY_S3_ACCESS_KEY_ID,GBB_GATEWAY_S3_SECRET_ACCESS_KEY: required, dedicated read-only credentials.GBB_GATEWAY_S3_ENDPOINT,GBB_GATEWAY_S3_REGION,GBB_GATEWAY_S3_PREFIX,GBB_GATEWAY_S3_PATH_STYLE=true: match the API's artifact storage layout.GBB_GATEWAY_ADDR: defaults to:8080.- Startup now validates all of the above through
config.LoadGateway, so a missing trusted-proxy range or an out-of-range limit is reported by name rather than as a bare "X is required". - Request-limit settings from anonymous verification must match all API/gateway replicas.
S3 permissions are GetObject for the artifact prefix and the bucket HEAD check
(typically ListBucket). Explicitly deny writes/deletes and public access. The
publisher retains its separate upload credential; do not reuse it here. The
executable does not receive repository keys, a Turnstile secret, or a Kubernetes
token. Redis needs the request-enforcement commands plus GET, PTTL, EXISTS,
ZSCORE and ZREM, restricted to gbb:human:v2:* and gbb:enforcement:*.
Use private/TLS dependency connections appropriate to the deployment.
Readiness checks PostgreSQL, Redis, and bucket access within three seconds. Liveness checks the process only. Dependency failures do not cause restart loops.
Deployment and remaining work
Deployment template (deploy/k8s/gateway/deployment.yaml in the repo) provides two replicas,
non-root execution, read-only root filesystem, resource limits, and ingress-only
network access from selected Traefik pods. Supply a dedicated ConfigMap and
Secret named gbb-gateway, a digest-pinned image, and actual ingress selectors.
Add egress restrictions for your DNS/PostgreSQL/Redis/object-store destinations.
The template is deliberately separate from the existing Docker production base.
The routing example (deploy/k8s/gateway/ingressroute.example.yaml in the repo) uses
Traefik PathRegexp and explicit priority.
Set the real hostname and preserve the existing Cloudflare-only origin access,
zone-specific authenticated origin TLS, forwarding trust, and ingress limits.
Do not deploy the example as an independent unprotected public origin.
Transfer checks now use the durable abuse enforcement service and its shared atomic Redis snapshot. Ban escalation, session revocation, administrative unblock and index reconstruction are implemented. Production through-ingress large-download tests and read-only object-store credential policy verification remain required; no automatic deployment is performed.
Verification performed
The full Go suite passed. Race-enabled gateway and enforcement tests also passed against temporary PostgreSQL, Redis, and MinIO services. They exercised shared sessions across gateway instances, S3 range reads, publication checks, changed objects, IP binding, transfer concurrency, expiring leases, and ban markers. The gateway publication connection rejected database writes. Protocol tests covered HEAD, If-Range, malformed ranges, dependency failures, and interruption of a blocked transfer when renewal fails. Temporary services were removed.
These checks did not deploy the gateway or validate production ingress and storage credential policies.
Guide
- Home
- Getting Started
- Building Godot
- Configuration
- Modules and Administration
- API
- Architecture
- Deployment and Operations
- Security
- Troubleshooting
- Development
Reference
- API
- Architecture
- Modules
- Module registry
- Admin
- Artifacts
- Deployment
- Kubernetes execution
- Durable execution store
- Immutable build configuration
- Download gateway
- Public logs
- Anonymous verification
- Abuse enforcement
- Public service remediation
- Authelia administration
- CI/CD
- macOS SDK