f212b68c2c45ff6be517f321ef48a96e9f73702e
Key-IP Sentinel
Key-IP Sentinel is a FastAPI-based reverse proxy that enforces first-use IP binding for model API keys before traffic reaches a downstream New API service.
Features
- First-use bind with HMAC-SHA256 token hashing, Redis cache-aside, and PostgreSQL CIDR matching.
- Streaming reverse proxy built on
httpx.AsyncClientand FastAPIStreamingResponse. - Trusted proxy IP extraction that only accepts
X-Real-IPfrom configured upstream networks. - Redis-backed intercept alert counters with webhook delivery and PostgreSQL audit logs.
- Admin API protected by JWT and Redis-backed login lockout.
- Vue 3 + Element Plus admin console for dashboarding, binding operations, audit logs, and live runtime settings.
- Docker Compose deployment with Nginx, app, Redis, and PostgreSQL.
Repository Layout
sentinel/
├── app/
├── db/
├── nginx/
├── frontend/
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
└── README.md
Runtime Notes
- Redis stores binding cache, alert counters, daily dashboard metrics, and mutable runtime settings.
- PostgreSQL stores authoritative token bindings and intercept logs.
- Archive retention removes inactive bindings from the active table after
ARCHIVE_DAYS. A later request from the same token will bind again on first use. SENTINEL_FAILSAFE_MODE=closedrejects requests when both Redis and PostgreSQL are unavailable.openallows traffic through.
Local Development
Backend
- Install
uvand ensure Python 3.13 is available. - Create the environment and sync dependencies:
uv sync
- Copy
.env.exampleto.envand update secrets plus addresses. - Start PostgreSQL and Redis.
- Run the API:
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 7000
Frontend
- Install dependencies:
cd frontend
npm install
- Start Vite dev server:
npm run dev
The Vite config proxies /admin/api/* to http://127.0.0.1:7000.
If you prefer the repository root entrypoint, uv run main.py now starts the same FastAPI app on APP_PORT (default 7000).
Dependency Management
- Local Python development uses
uvviapyproject.toml. - Container builds still use
requirements.txtbecause the Dockerfile is intentionally minimal and matches the delivery requirements.
Production Deployment
1. Prepare environment
- Copy
.env.exampleto.env. - Replace
SENTINEL_HMAC_SECRET,ADMIN_PASSWORD, andADMIN_JWT_SECRET. - Verify
DOWNSTREAM_URLpoints to the internal New API service. - Keep
PG_DSNaligned with the fixed PostgreSQL container password indocker-compose.yml, or update both together.
2. Build the frontend bundle
cd frontend
npm install
npm run build
cd ..
This produces frontend/dist, which Nginx serves at /admin/ui/.
3. Build prerequisites
- Build the frontend first. If
frontend/distis missing,/admin/ui/cannot be served by Nginx. - Ensure the external Docker network
llm-shared-netalready exists ifDOWNSTREAM_URL=http://new-api:3000should resolve across stacks.
4. Start the stack
docker compose up --build -d
Services:
http://<host>/forwards model API traffic through Sentinel.http://<host>/admin/ui/serves the admin console.http://<host>/admin/api/*serves the admin API.http://<host>/healthexposes the app health check.
Admin API Summary
POST /admin/api/loginGET /admin/api/dashboardGET /admin/api/bindingsPOST /admin/api/bindings/unbindPUT /admin/api/bindings/ipPOST /admin/api/bindings/banPOST /admin/api/bindings/unbanGET /admin/api/logsGET /admin/api/logs/exportGET /admin/api/settingsPUT /admin/api/settings
All admin endpoints except /admin/api/login require Authorization: Bearer <jwt>.
Key Implementation Details
app/proxy/handler.pykeeps the downstream response fully streamed, including SSE responses.app/core/ip_utils.pynever trusts client-suppliedX-Forwarded-For.app/services/binding_service.pybatcheslast_used_atupdates every 5 seconds through anasyncio.Queue.app/services/alert_service.pypushes webhooks once the Redis counter reaches the configured threshold.app/services/archive_service.pyprunes stale bindings on a scheduler interval.
Suggested Smoke Checks
GET /healthreturns{"status":"ok"}.- A first request with a new bearer token creates a binding in PostgreSQL and Redis.
- A second request from the same IP is allowed and refreshes
last_used_at. - A request from a different IP is rejected with
403and creates anintercept_logsrecord. /admin/api/loginreturns a JWT and the frontend can load/admin/api/dashboard.
Description
Languages
Python
48.3%
Vue
35.7%
CSS
10.4%
JavaScript
5.2%
HTML
0.2%
Other
0.2%