GET /openapi.json (X-API-Key authenticated) merges the upstream OpenAPI documents into one spec scoped to the calling key: only granted operations, paths rewritten to gateway routes, component schemas namespaced per service. GET /docs serves a Swagger UI portal that loads the key-scoped spec and injects the key into try-it-out requests. Discovery now caches the raw upstream spec documents (same 5-minute TTL), and FastAPI's built-in /docs and /openapi.json are disabled in favor of the portal routes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
105 lines
5.0 KiB
Markdown
105 lines
5.0 KiB
Markdown
# API Gateway
|
||
|
||
A FastAPI-based gateway that exposes a single entrypoint for multiple upstream APIs,
|
||
with a built-in web management console.
|
||
|
||
## Features
|
||
|
||
- **Single entrypoint** — consumers call `/<service-slug>/<path>` and the gateway
|
||
proxies to the registered upstream (all HTTP methods, query strings, bodies,
|
||
headers). `/admin`, `/static`, `/docs` and `/health` are reserved.
|
||
- **Endpoint catalog, auto-synced** — each service's callable endpoints are
|
||
mirrored from its OpenAPI document (`/openapi.json`, `/swagger.json` or
|
||
`/api-docs`) whenever the API Keys page loads: new operations appear, removed
|
||
ones are deleted (their grants with them). Services without a reachable spec
|
||
keep their existing catalog. Patterns support `{param}` (one segment), `*`
|
||
(within a segment) and `**` (any depth).
|
||
- **Per-key access rights, hierarchical picker** — every API key is granted a
|
||
specific set of endpoints, chosen in a path tree (service → path segments →
|
||
operations) where a parent checkbox selects everything beneath it. A request
|
||
must match a listed endpoint AND the key must hold a grant on it; otherwise
|
||
404 (`unknown_endpoint`) or 403 (`access_denied`). Keys are hashed at rest,
|
||
shown once at creation, revocable, with per-key rate limits.
|
||
- **Web management console** (`/admin`) — manage connected APIs, users, and API
|
||
keys; usage dashboards with charts. Every console user has full management
|
||
access.
|
||
- **Transaction capture & request browser** — every gateway request is recorded
|
||
with service, endpoint, key, status, latency, client IP, query string, and
|
||
the request/response payloads (textual bodies up to 64 KB; binary summarized).
|
||
The Requests page filters by service / user / key / status class / path and
|
||
links to a per-request detail view showing the complete transaction.
|
||
- **Usage monitoring** — charts: traffic and average latency over time (adaptive
|
||
buckets: 5 min / 1 h / 1 day), volume by service / user / endpoint, status
|
||
codes, latency by service, top keys — filterable by time range (1 h – 30 d),
|
||
service, user and key.
|
||
|
||
- **Consumer docs** — `GET /openapi.json` (authenticated with `X-API-Key`)
|
||
returns a merged OpenAPI document scoped to that key: every operation the
|
||
key holds a grant on, across all services, rewritten to the gateway's
|
||
routes. `GET /docs` serves a Swagger UI portal around it — paste a key,
|
||
browse and try exactly what that key can call.
|
||
- **Retention** — a background purge runs every 6 hours: request/response
|
||
payloads are blanked after 7 days (the log row stays inspectable), whole log
|
||
rows are deleted after 90 days, and freed pages are returned to the
|
||
filesystem via incremental vacuum. Both windows are configurable; `0`
|
||
disables that tier.
|
||
|
||
## Quick start
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||
```
|
||
|
||
Open http://localhost:8000/admin — first startup seeds an admin account
|
||
(`admin` / `admin` by default — **change it immediately**).
|
||
|
||
## Docker
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
The console is served on http://localhost:8000/admin; the SQLite database
|
||
persists in the `gateway-data` volume. Set `GATEWAY_SECRET_KEY` (and the admin
|
||
credentials) in `docker-compose.yml` before deploying anywhere real. The image
|
||
runs a single uvicorn worker on purpose — the rate limiter and the OpenAPI
|
||
sync cache are in-memory.
|
||
|
||
## Configuration (environment variables)
|
||
|
||
| Variable | Default | Purpose |
|
||
|---|---|---|
|
||
| `GATEWAY_DATABASE_URL` | `sqlite:///./gateway.db` | SQLAlchemy database URL |
|
||
| `GATEWAY_SECRET_KEY` | dev value | Session cookie signing key — set in production |
|
||
| `GATEWAY_ADMIN_USER` / `GATEWAY_ADMIN_PASSWORD` | `admin` / `admin` | Seeded admin credentials |
|
||
| `GATEWAY_PAYLOAD_RETENTION_DAYS` | `7` | Blank stored payloads after N days (0 = keep forever) |
|
||
| `GATEWAY_LOG_RETENTION_DAYS` | `90` | Delete log rows after N days (0 = keep forever) |
|
||
|
||
## Using the gateway as a consumer
|
||
|
||
1. Register a service (e.g. slug `weather` → `https://api.example.com/v1`) and
|
||
list its endpoints (import from OpenAPI or add manually).
|
||
2. Issue an API key and tick the endpoints it may call.
|
||
3. The consumer calls:
|
||
|
||
```bash
|
||
curl -H "X-API-Key: gw_..." "http://localhost:8000/weather/forecast?city=Paris"
|
||
```
|
||
|
||
The gateway authenticates the key, matches the request against the service's
|
||
endpoint list, checks the key's grant and the rate limit, proxies the request to
|
||
`https://api.example.com/v1/forecast?city=Paris`, logs it, and returns the
|
||
upstream response. Gateway-generated errors are JSON with an `error` code:
|
||
`missing_api_key` (401), `invalid_api_key` / `access_denied` (403),
|
||
`unknown_service` / `unknown_endpoint` (404), `rate_limited` (429),
|
||
`upstream_unreachable` (502), `upstream_timeout` (504).
|
||
|
||
## Notes & limits
|
||
|
||
- The stats queries use SQLite date functions; if you point `GATEWAY_DATABASE_URL`
|
||
at Postgres/MySQL, adapt `app/admin/stats.py` (`strftime`, `iif`).
|
||
- The rate limiter is in-memory (single process). Run one worker, or swap in Redis
|
||
for multi-worker deployments.
|
||
- Streaming responses are buffered (fine for JSON APIs; not for large file proxying).
|