walletpress/backend/core/ip_allowlist.py
cryptorugmunch e13bd4d774
Some checks are pending
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / security (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / license (push) Waiting to run
CI / ai-review (push) Waiting to run
docs: apply fleet-template (16-artifact scaffold)
Adds missing standard artifacts:
- README.md (if missing)
- AGENTS.md (AI agent contract)
- PLAN.md (current sprint)
- STATUS.md (where we are)
- DEVELOPMENT.md (dev workflow)
- DEPLOYMENT.md (deploy procedure)
- TESTING.md (test strategy)
- DECISIONS.md (ADR index + templates)
- .github/CODEOWNERS
- .github/workflows/ci.yml

Preserves all existing artifacts.

Refs: RugMunchMedia/fleet-template
2026-07-02 02:07:06 +07:00

57 lines
2 KiB
Python

"""IP allowlisting middleware for admin operations.
When WP_ALLOWED_IPS is set, all requests from non-whitelisted IPs
are rejected. This prevents leaked API keys from being used off-network.
Trust: IP allowlisting means even if your API key is compromised,
an attacker can't use it from a different IP address.
"""
from __future__ import annotations
import os
from fastapi import HTTPException, Request
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
ALLOWED_IPS: list[str] = []
_raw = os.getenv("WP_ALLOWED_IPS", "")
if _raw:
ALLOWED_IPS = [ip.strip() for ip in _raw.split(",")]
class IPAllowlistMiddleware(BaseHTTPMiddleware):
"""Rejects requests from non-whitelisted IPs when configured.
Only applies to admin/sensitive endpoints.
Health and public endpoints are exempt.
"""
SENSITIVE_PREFIXES = ("/api/v1/chain-vault/admin", "/api/v1/chain-vault/vault/",
"/api/v1/chain-vault/export", "/api/v1/chain-vault/proof/commit",
"/api/v1/chain-vault/proof/verify", "/api/v1/chain-vault/paper-wallet",
"/api/v1/chain-vault/tx/broadcast")
def __init__(self, app):
super().__init__(app)
self._enabled = len(ALLOWED_IPS) > 0
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
if not self._enabled:
return await call_next(request)
# Check if this is a sensitive endpoint
is_sensitive = any(request.url.path.startswith(p) for p in self.SENSITIVE_PREFIXES)
if not is_sensitive:
return await call_next(request)
client_ip = request.client.host if request.client else ""
if client_ip not in ALLOWED_IPS and "0.0.0.0" not in ALLOWED_IPS:
raise HTTPException(
status_code=403,
detail=f"Access denied from {client_ip}. Configure WP_ALLOWED_IPS to whitelist this IP.",
)
return await call_next(request)