diff --git a/README.md b/README.md index abd8ab0..fd5f903 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,72 @@ -# FastAPI Endpoint Monitor +# Endpoint Monitor -A typed FastAPI service for process-local endpoint monitors and secure, on-demand HTTP checks. - -## Features - -- CRUD monitor resources and current-status retrieval -- bounded on-demand HTTP checks with latency measurement -- DNS and redirect-hop SSRF validation (only globally routable HTTP(S) targets) -- concurrency-safe in-memory storage -- health and readiness endpoints -- JSON logs with URL user-info, query, and fragment removed -- environment-validated settings, Docker packaging, and automated tests - -> State is process-local and ephemeral. Run exactly one worker. Restarts erase all monitors, and multiple replicas do not share state. +A typed FastAPI service that stores endpoint monitors in concurrency-safe, process-local memory and checks them on demand with bounded timeouts, redirect validation, DNS-based SSRF policy, latency/status updates, and redacted JSON logs. There is intentionally no authentication. ## Layout +- `SPEC.md`: normative API, status, security, logging, and lifecycle contract +- `app/`: configuration, models, locked store, SSRF policy, checker, and API +- `tests/`: API/unit tests with mocked outbound HTTP and DNS +- `Dockerfile`, `compose.yaml`: single-worker container packaging -- `docs/service-design.md` — contract, status semantics, security design, and architecture -- `src/endpoint_monitor/` — application source -- `tests/` — API and checker tests with mocked outbound HTTP/DNS -- `VALIDATION.md` — validation commands and evidence status +## Local development +Requires Python 3.12. -## Run locally - -```bash +```sh python -m venv .venv . .venv/bin/activate pip install -e '.[dev]' -uvicorn endpoint_monitor.main:app --reload -``` - -OpenAPI is at . - -## Examples - -```bash -curl -sS http://localhost:8000/healthz -curl -sS -X POST http://localhost:8000/v1/monitors \ - -H 'content-type: application/json' \ - -d '{"name":"example","url":"https://example.com"}' -curl -sS -X POST http://localhost:8000/v1/monitors/MONITOR_ID/check -curl -sS http://localhost:8000/v1/monitors/MONITOR_ID/status -``` - -Create returns `201`; reads/checks return `200`; delete returns `204`; duplicate names return `409`; missing IDs return `404`; invalid or SSRF-blocked check targets return `400`. Transport failures are represented by a `200` check result with `status: "error"` so the latest attempt remains inspectable. - -## Configuration - -Settings use the `MONITOR_` prefix: - -| Variable | Default | Constraint | -|---|---:|---| -| `MONITOR_REQUEST_TIMEOUT_SECONDS` | `5.0` | 0.1–30 | -| `MONITOR_MAX_REDIRECTS` | `5` | 0–10 | -| `MONITOR_MAX_MONITORS` | `1000` | 1–100000 | -| `MONITOR_LOG_LEVEL` | `INFO` | DEBUG/INFO/WARNING/ERROR/CRITICAL | - -Unknown variables are ignored. Invalid known values fail application startup. - -## Quality checks - -```bash ruff format --check . ruff check . -mypy src +mypy app pytest -q - -docker build -t endpoint-monitor . -docker run --rm -p 8000:8000 endpoint-monitor -curl -fsS http://localhost:8000/healthz -curl -fsS http://localhost:8000/readyz +uvicorn app.main:app --reload ``` -Tests never make real outbound requests. See `VALIDATION.md` for evidence and limitations. +OpenAPI is at `http://localhost:8000/docs`. + +## Examples +```sh +curl -s http://localhost:8000/healthz +curl -s http://localhost:8000/readyz +curl -s -X POST http://localhost:8000/monitors -H 'content-type: application/json' \ + -d '{"name":"example","url":"https://example.com/?token=secret","timeout_seconds":3}' +curl -s -X POST http://localhost:8000/monitors/UUID/check +curl -s http://localhost:8000/monitors/UUID/status +``` +CRUD also supports `GET /monitors`, `GET|PUT|DELETE /monitors/{id}`. + +## Configuration +All settings are startup-validated. Invalid values prevent startup. + +| Environment | Default | Constraint | +|---|---:|---| +| `MONITOR_REQUEST_TIMEOUT_SECONDS` | 5 | >0, <=30 | +| `MONITOR_MAX_REDIRECTS` | 5 | 0..10 | +| `MONITOR_MAX_MONITORS` | 1000 | 1..100000 | +| `MONITOR_LOG_LEVEL` | INFO | standard uppercase level | + +Per-monitor timeout overrides the default. Query strings are emitted only as `?REDACTED`. + +## Container +```sh +docker build -t endpoint-monitor . +docker run --rm -p 8000:8000 endpoint-monitor +# or: docker compose up --build +curl http://localhost:8000/healthz +``` +The image runs as a non-root user with exactly one Uvicorn worker. + +## Verification +Run the lint, format, type, and test commands above, then: +```sh +docker build -t endpoint-monitor . +docker run -d --rm --name endpoint-monitor -p 8000:8000 endpoint-monitor +curl --fail http://localhost:8000/healthz +curl --fail http://localhost:8000/readyz +docker stop endpoint-monitor +``` + +## Security and operational limitations +DNS and every redirect target are checked and non-global addresses are denied. This policy is defense in depth, not a substitute for egress firewalling: the standard HTTP transport performs its own DNS lookup, so hostile DNS rebinding between policy resolution and connection remains possible. Enforce outbound network policy in production. + +Data is ephemeral and isolated per process. Restarting loses all monitors; multiple workers produce divergent state. There is no scheduler, persistence, cross-process readiness dependency, TLS termination, rate limiting, or authentication. Deploy one worker, place behind appropriate controls, and use persistent shared storage before scaling.