Skip to content

relay: localhost-only /metrics listener with bind-address validation #60

@ilmoniemi

Description

@ilmoniemi

User Story

As a relay operator, I want /metrics exposed on a separate localhost-only HTTP listener that refuses non-loopback bind addresses at startup, so I can scrape Prometheus metrics via an SSH tunnel or sidecar without putting operational state on the internet-exposed public listener.

Context

The relay is internet-exposed. Prometheus metrics leak operational state (active-connection counts indicate whether anyone is using the relay, upgrade-attempt rates reveal traffic patterns). Putting /metrics on the same listener as /healthz (#10) and /v1/{server,client} would publish that state to anyone who can reach the public listener.

Listener exposure decision (locked, not for re-litigation by the architect): /metrics is exposed on a separate localhost-only listener (default 127.0.0.1:9090). Operators reach it via SSH tunnel or sidecar. The flag default and the bind-address validation are part of this ticket's contract.

This ticket consumes the registry + handler factory introduced in the scaffolding slice (#59) — wires the second http.Server and the flag.

Acceptance Criteria

  • cmd/pyrycode-relay/main.go accepts --metrics-listen (default 127.0.0.1:9090). An empty value disables the metrics listener entirely (operator opt-out — no listener bound, no goroutine started, no error).
  • A second http.Server serves /metrics on the configured address with explicit timeouts (gosec G114 compliance) matching the existing public listener's pattern in main.go, and its graceful-shutdown path mirrors that same pattern.
  • Bind-address validation at startup: the host portion of --metrics-listen is parsed with net.ParseIP and rejected unless IsLoopback() returns true. Hostnames are rejected (avoids DNS-time TOCTOU — IP literals only). A non-loopback or unparseable host causes startup to fail loudly with a clear error message (loud-failure-over-silent-correction pattern, docs/PROJECT-MEMORY.md line 38).
  • Tests cover: (a) happy path — GET /metrics against the configured listener (use 127.0.0.1:0 or httptest) returns HTTP 200 with Content-Type matching Prometheus text format; (b) non-loopback bind addresses (e.g. 0.0.0.0:9090, 192.168.x.y:9090) cause the documented startup failure; (c) empty --metrics-listen results in no listener bound and does not error.
  • make vet, make test -race, and make build clean; docs/knowledge/codebase/<n>.md summary entry created; docs/knowledge/INDEX.md updated.

Technical Notes

Size Estimate

S

Split from #56. Depends on the scaffolding slice (#59).

Metadata

Metadata

Assignees

No one assigned

    Labels

    security-sensitiveTouches auth, crypto, or internet-exposed input pathssize:sSmall ticket: <100 lines production code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions