Harden input handling and filter non-global DNS addresses
X-API-Key compared as bytes (401 instead of 500 on non-ASCII), strict parsing of the diff "since" parameter (400 instead of 500), FQDN resolution keeps only global addresses (allow_non_global_ips to opt out). Adds the code review report and the plan/summary for this change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
02f7b49e12
commit
46d56654d8
8 files changed
+168
-9
No files matched your search
+11
-5
@@ -23,6 +23,7 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(mess
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TOKEN_ENV = "RIPE_API_TOKEN"
|
||||
MAX_CURSOR_DIGITS = 30 # курсор длиннее не бывает: такое значение - ошибка запроса
|
||||
|
||||
app = FastAPI(title="RIPE CIDR/FQDN API")
|
||||
|
||||
@@ -102,7 +103,8 @@ def verify_token(x_api_key: Optional[str] = Header(None)):
|
||||
if not expected:
|
||||
# Fail closed: без заданного токена управление отключено
|
||||
raise HTTPException(status_code=503, detail=f"{TOKEN_ENV} is not configured; write access disabled.")
|
||||
if not x_api_key or not secrets.compare_digest(x_api_key, expected):
|
||||
# Сравнение по байтам: compare_digest для str принимает только ASCII и падает на других символах
|
||||
if not x_api_key or not secrets.compare_digest(x_api_key.encode(), expected.encode()):
|
||||
raise HTTPException(status_code=401, detail="Invalid or missing X-API-Key.")
|
||||
|
||||
|
||||
@@ -149,14 +151,18 @@ def get_addresses(
|
||||
|
||||
def parse_since(raw: str):
|
||||
"""since: целый курсор или время ISO 8601 (без пояса - UTC). Возвращает (курсор, время UTC в формате журнала)."""
|
||||
if raw.isdigit():
|
||||
invalid = HTTPException(status_code=400, detail="since must be a cursor (integer) or an ISO 8601 time")
|
||||
# Только ASCII-цифры и ограниченная длина: str.isdigit() принимает юникод-цифры, а int() ограничен по длине
|
||||
if raw.isascii() and raw.isdigit():
|
||||
if len(raw) > MAX_CURSOR_DIGITS:
|
||||
raise invalid
|
||||
return int(raw), None
|
||||
try:
|
||||
# «+» в адресной строке приходит пробелом; «Z» понимаем явно
|
||||
moment = datetime.fromisoformat(raw.strip().replace(" ", "+").replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="since must be a cursor (integer) or an ISO 8601 time")
|
||||
moment = moment.replace(tzinfo=timezone.utc) if moment.tzinfo is None else moment.astimezone(timezone.utc)
|
||||
moment = moment.replace(tzinfo=timezone.utc) if moment.tzinfo is None else moment.astimezone(timezone.utc)
|
||||
except (ValueError, OverflowError): # OverflowError: крайние даты с часовым поясом
|
||||
raise invalid
|
||||
return None, moment.strftime("%Y-%m-%dT%H:%M:%S.") + f"{moment.microsecond // 1000:03d}Z"
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user