fix: cap /api/discovery/ping at 4096 IPs and fix test suite
- Add MAX_PING_IPS=4096 constant and validate list size in PingRequest before spawning futures, returning 422 on overflow - Add test_ping_too_many_ips_rejected to cover the new cap - Pin httpx<0.28 in requirements-test.txt (0.28 broke TestClient API) - Fix reset_db fixture to set a known admin password regardless of INITIAL_ADMIN_PASSWORD env var (was causing 401 on all auth tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
pytest>=7.4
|
||||
httpx>=0.25
|
||||
httpx>=0.25,<0.28
|
||||
|
||||
@@ -16,6 +16,7 @@ router = APIRouter()
|
||||
|
||||
MAX_HOSTS_PER_TARGET = 1024 # refuse les /21 et plus larges
|
||||
MAX_HOSTS_TOTAL = 4096 # cap global sur l'ensemble des targets
|
||||
MAX_PING_IPS = 4096 # cap sur /api/discovery/ping
|
||||
|
||||
_ENV_DNS = os.environ.get("DNS_SERVER", "").strip()
|
||||
|
||||
@@ -135,6 +136,8 @@ class PingRequest(BaseModel):
|
||||
@field_validator("ips")
|
||||
@classmethod
|
||||
def _ips(cls, v: list[str]) -> list[str]:
|
||||
if len(v) > MAX_PING_IPS:
|
||||
raise ValueError(f"Too many IPs: {len(v)} (max {MAX_PING_IPS})")
|
||||
for ip in v:
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
|
||||
@@ -9,6 +9,7 @@ Couvre :
|
||||
"""
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from passlib.context import CryptContext
|
||||
from sqlalchemy import text
|
||||
|
||||
import sys, os
|
||||
@@ -17,6 +18,9 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from database import engine, Base
|
||||
from main import app, _migrate_users_must_change_password, _migrate_users_token_version, _migrate_users
|
||||
|
||||
_TEST_PASSWORD = "admin-test"
|
||||
_pwd_ctx = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
@@ -29,6 +33,11 @@ def reset_db():
|
||||
_migrate_users_must_change_password()
|
||||
_migrate_users_token_version()
|
||||
_migrate_users()
|
||||
# Force a known password regardless of INITIAL_ADMIN_PASSWORD env var
|
||||
with engine.connect() as conn:
|
||||
hashed = _pwd_ctx.hash(_TEST_PASSWORD)
|
||||
conn.execute(text(f"UPDATE users SET hashed_password=:h WHERE username='admin'"), {"h": hashed})
|
||||
conn.commit()
|
||||
yield
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
|
||||
@@ -55,7 +64,7 @@ def _get_token(client):
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("UPDATE users SET must_change_password=0 WHERE username='admin'"))
|
||||
conn.commit()
|
||||
r = client.post("/api/auth/login", data={"username": "admin", "password": "admin"})
|
||||
r = client.post("/api/auth/login", data={"username": "admin", "password": _TEST_PASSWORD})
|
||||
return r.json()["access_token"]
|
||||
|
||||
|
||||
@@ -189,6 +198,12 @@ class TestDiscoveryValidation:
|
||||
r = client.post("/api/discovery/ping", json={"ips": []}, headers=_auth(token))
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_ping_too_many_ips_rejected(self, client):
|
||||
token = _get_token(client)
|
||||
ips = [f"10.0.{i // 256}.{i % 256}" for i in range(4097)]
|
||||
r = client.post("/api/discovery/ping", json={"ips": ips}, headers=_auth(token))
|
||||
assert r.status_code == 422
|
||||
|
||||
def test_scan_oversized_cidr_rejected(self, client):
|
||||
token = _get_token(client)
|
||||
r = client.post("/api/discovery/scan", json={
|
||||
|
||||
Reference in New Issue
Block a user