3.1 KiB
Endpoint Monitor service contract
Scope and lifecycle
This repository contains one FastAPI process that stores monitors and their latest check status in process-local memory. State starts empty, disappears on restart, and is neither shared nor replicated across workers. Run exactly one worker when consistent state is required. There is intentionally no authentication.
Resource and status semantics
A monitor has an immutable UUID id, name, HTTP(S) url, timestamps, and a nullable current_status. Names need not be unique. unknown means no completed attempt; up means the final response was 200–399; down means the final response was 400–599; error means DNS, transport, timeout, redirect-policy, or SSRF rejection prevented a final response. Latency is wall-clock monotonic elapsed time for the complete attempt, including validated redirects.
API
POST /v1/monitorscreates a monitor (201).GET /v1/monitorslists monitors (200).GET /v1/monitors/{id}retrieves one (200).PATCH /v1/monitors/{id}changes provided name and/or URL (200).DELETE /v1/monitors/{id}deletes it (204).POST /v1/monitors/{id}/checkperforms one bounded GET and atomically records the result (200). Policy-rejected destinations return400after recording an error; a monitor deleted while its request is running returns404and is not resurrected.GET /v1/monitors/{id}/statusreturns the latest status (200), includingunknownbefore the first check.GET /healthzis liveness;GET /readyzconfirms this process initialized its store.
Missing UUID resources return FastAPI's 404 {"detail":"monitor not found"}. Invalid input returns 422. Duplicate IDs cannot be supplied by clients.
Concurrency and outbound security
MonitorStore serializes every state access with one asyncio.Lock and returns copies, preventing caller mutation. Network I/O never holds that lock. Status replacement is one locked operation.
Only HTTP and HTTPS URLs without userinfo are accepted. Before each request—including every redirect hop—the checker resolves the hostname and requires every returned address to be globally routable. Literal loopback, private, link-local, multicast, reserved, and unspecified addresses are rejected. Redirects are manual, bounded, and revalidated. Timeouts and redirect counts are environment-controlled. This substantially reduces SSRF exposure; DNS rebinding between validation and the HTTP client's separate connection lookup remains a documented limitation, so production deployments should also enforce egress policy at the network layer.
Logging and architecture
Checks emit one JSON log event. URLs are normalized to omit credentials, fragments, and all query values (?<redacted>). Errors are bounded and do not include response bodies. Configuration is validated from MONITOR_ environment variables.
app/models.py owns wire/domain types; store.py owns state; security.py owns destination policy; checker.py owns HTTP behavior; main.py composes routes; tests mock both DNS and HTTP. Packaging and validation evidence live at repository root.