Rthur2003 commited on
Commit
c5f51f7
·
1 Parent(s): dd0b28c

fix: enhance API key validation and implement stale IP eviction in rate limiting

Browse files
Files changed (1) hide show
  1. app/routes/commend/router.py +29 -4
app/routes/commend/router.py CHANGED
@@ -117,14 +117,27 @@ class CommendHealthResponse(BaseModel):
117
 
118
  _COMMEND_API_KEY = os.getenv("COMMEND_API_KEY")
119
  _COMMEND_POSTING_ENABLED = os.getenv("COMMEND_ENABLE_POSTING", "false").lower() == "true"
 
120
 
121
  # Simple in-memory rate limiter: IP -> list of timestamps
122
  _rate_limit_store: dict[str, list[float]] = defaultdict(list)
123
  _RATE_LIMIT_WINDOW = 60 # seconds
124
  _RATE_LIMIT_MAX = 10 # requests per window
 
 
 
 
 
 
 
 
 
 
 
125
 
126
 
127
  def _check_rate_limit(client_ip: str) -> None:
 
128
  now = time.time()
129
  window_start = now - _RATE_LIMIT_WINDOW
130
  timestamps = _rate_limit_store[client_ip]
@@ -142,17 +155,29 @@ async def _require_api_key(
142
  request: Request,
143
  x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
144
  ) -> None:
145
- """Validate API key if COMMEND_API_KEY is configured."""
 
 
 
 
 
 
146
  if not _COMMEND_API_KEY:
147
- # No key configured — allow (development mode)
 
 
 
 
 
 
 
148
  return
149
  if x_api_key != _COMMEND_API_KEY:
150
- logger.warning(f"Unauthorized commend request from {request.client.host if request.client else 'unknown'}")
151
  raise HTTPException(
152
  status_code=status.HTTP_401_UNAUTHORIZED,
153
  detail={"code": "unauthorized", "message": "Invalid or missing API key."}
154
  )
155
- _check_rate_limit(request.client.host if request.client else "unknown")
156
 
157
 
158
  # --- API Endpoints ---
 
117
 
118
  _COMMEND_API_KEY = os.getenv("COMMEND_API_KEY")
119
  _COMMEND_POSTING_ENABLED = os.getenv("COMMEND_ENABLE_POSTING", "false").lower() == "true"
120
+ _COMMEND_REQUIRE_AUTH = os.getenv("COMMEND_REQUIRE_AUTH", "true").lower() != "false"
121
 
122
  # Simple in-memory rate limiter: IP -> list of timestamps
123
  _rate_limit_store: dict[str, list[float]] = defaultdict(list)
124
  _RATE_LIMIT_WINDOW = 60 # seconds
125
  _RATE_LIMIT_MAX = 10 # requests per window
126
+ _MAX_TRACKED_IPS = 10_000
127
+
128
+
129
+ def _evict_stale_ips() -> None:
130
+ """Remove expired entries when map grows beyond threshold."""
131
+ if len(_rate_limit_store) <= _MAX_TRACKED_IPS:
132
+ return
133
+ cutoff = time.time() - _RATE_LIMIT_WINDOW
134
+ stale_keys = [ip for ip, ts in _rate_limit_store.items() if all(t <= cutoff for t in ts)]
135
+ for key in stale_keys:
136
+ del _rate_limit_store[key]
137
 
138
 
139
  def _check_rate_limit(client_ip: str) -> None:
140
+ _evict_stale_ips()
141
  now = time.time()
142
  window_start = now - _RATE_LIMIT_WINDOW
143
  timestamps = _rate_limit_store[client_ip]
 
155
  request: Request,
156
  x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
157
  ) -> None:
158
+ """Validate API key. Fail-closed: if COMMEND_REQUIRE_AUTH is true (default)
159
+ and no COMMEND_API_KEY is configured, all requests are rejected."""
160
+ client_ip = request.client.host if request.client else "unknown"
161
+
162
+ # Always apply rate-limit regardless of auth configuration
163
+ _check_rate_limit(client_ip)
164
+
165
  if not _COMMEND_API_KEY:
166
+ if _COMMEND_REQUIRE_AUTH:
167
+ # Fail-closed: auth required but no key configured
168
+ logger.error("COMMEND_API_KEY not set but auth is required. Rejecting request.")
169
+ raise HTTPException(
170
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
171
+ detail={"code": "auth_not_configured", "message": "Service authentication is not configured."}
172
+ )
173
+ # Explicitly opted out of auth (COMMEND_REQUIRE_AUTH=false)
174
  return
175
  if x_api_key != _COMMEND_API_KEY:
176
+ logger.warning(f"Unauthorized commend request from {client_ip}")
177
  raise HTTPException(
178
  status_code=status.HTTP_401_UNAUTHORIZED,
179
  detail={"code": "unauthorized", "message": "Invalid or missing API key."}
180
  )
 
181
 
182
 
183
  # --- API Endpoints ---