Review
Probe carrier logger URLs before cold-chain onboarding
ColdPack Trace onboards 3PL temperature-logger APIs by fetching a sample /health payload from the carrier-supplied HTTPS URL. Blocks private/loopback/link-local resolved addresses so partner probes cannot reach cluster metadata or internal APIs. Returns logger firmware/build so ops can approve the feed before cron pulls start.
FastAPITier 6securityssrfdnshttpx
Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.
services/carriers/logger_probe.py+32-6
| 1 | + | import ipaddress | |
| 2 | + | import socket | |
| 1 | 3 | from urllib.parse import urlparse | |
| 2 | 4 | ||
| 3 | 5 | import httpx | |
| 4 | 6 | from fastapi import APIRouter, HTTPException | |
| 5 | 7 | from pydantic import BaseModel, HttpUrl | |
| 6 | 8 | ||
| 7 | 9 | router = APIRouter(prefix="/carriers", tags=["carriers"]) | |
| 8 | 10 | ||
| 11 | + | ||
| 12 | + | def _resolves_only_public(hostname: str) -> bool: | |
| 13 | + | """Reject hosts whose current A/AAAA records are non-public.""" | |
| 14 | + | for family, _, _, _, sockaddr in socket.getaddrinfo(hostname, None): | |
| 15 | + | ip = ipaddress.ip_address(sockaddr[0]) | |
| 16 | + | if ( | |
| 17 | + | ip.is_private | |
| 18 | + | or ip.is_loopback | |
| 19 | + | or ip.is_link_local | |
| 20 | + | or ip.is_reserved | |
| 21 | + | or ip.is_multicast | |
| 22 | + | ): | |
| 23 | + | return False | |
| 24 | + | return True | |
| 25 | + | ||
| 9 | - | @router.post("/{carrier_id}/probe-logger") | |
| 10 | - | async def probe_logger(carrier_id: str, body: ProbeBody): | |
| 11 | - | # TODO: block internal IPs before fetching partner logger health | |
| 12 | - | async with httpx.AsyncClient(timeout=5.0) as client: | |
| 13 | - | resp = await client.get(str(body.endpoint_url)) | |
| 14 | - | return {"carrier_id": carrier_id, "status": resp.status_code, "body": resp.text[:512]} | |
| 26 | + | @router.post("/{carrier_id}/probe-logger") | |
| 27 | + | async def probe_logger(carrier_id: str, body: ProbeBody): | |
| 28 | + | parsed = urlparse(str(body.endpoint_url)) | |
| 29 | + | if parsed.scheme != "https" or not parsed.hostname: | |
| 30 | + | raise HTTPException(status_code=400, detail="https endpoints only") | |
| 31 | + | if not _resolves_only_public(parsed.hostname): | |
| 32 | + | raise HTTPException(status_code=400, detail="endpoint resolves to a non-public address") | |
| 33 | + | # Safe: we already verified the resolved address above | |
| 34 | + | async with httpx.AsyncClient(timeout=5.0, follow_redirects=False) as client: | |
| 35 | + | resp = await client.get(str(body.endpoint_url)) | |
| 36 | + | return { | |
| 37 | + | "carrier_id": carrier_id, | |
| 38 | + | "status": resp.status_code, | |
| 39 | + | "body": resp.text[:512], | |
| 40 | + | } |