Raxx · internal docs

internal · gated

Design: public ingress for monitoring.moosequest.net + product-status dashboard coverage

Status: Accepted. Cards A, C and D are in the code; Card B is pending. §§1-10 are the 2026-08-31 proposal; §11 records what was built (verified 2026-10-30 UTC). Author: software-architect Refs: #4547 (Dyn existence-monitor gap), #4545/#4544 (CI incident chain), #4568 (epic), [ADR-0149](https://internal-docs.raxx.app/architecture/adr/0149-session-agent-credential-model.html) (credential model), ADR-0150, ADR-0134 (ci-monitoring box), card #1022 (stack build)

1. Context

Grafana (+ Prometheus/Alertmanager/blackbox-exporter) runs on the ci-monitoring EC2 box, 100% loopback-bound, accessible only via aws ssm start-session --document-name AWS-StartPortForwardingSession. Two asks:

  1. Give it a real public hostname, monitoring.moosequest.net, gated the way this org already gates internal tools — not "obscure but reachable."
  2. Broaden Prometheus/blackbox coverage from CI-only to a "product status" view (api.raxx.app, console, vault.raxx.app, tickets.raxx.app, Woodpecker) and ship one overview dashboard for it.

This lands the same week as the CI incident chain (#4544/#4545) that produced ADR-0149. The ci-monitoring box is shared, fragile infra right now (build cache + alerting brain for two products) — this design sequences public-ingress work away from the box's existing scrape/alert config so a botched step can't repeat that incident class.

2. Invariants

3. Assumption check: is moosequest.net still Dyn, or moved to Cloudflare?

Verified live: moosequest.net NS records are still ns[1-4].dns.dyn.com (Oracle Dyn) and ci.moosequest.net still resolves via CNAME to the Woodpecker ALB. The zone has not moved to Cloudflare. This confirms the brief's assumption: the vault.raxx.app (Cloudflare Access) precedent does not apply here — Cloudflare Access requires the hostname's DNS to be proxied through Cloudflare's edge, and moosequest.net isn't delegated to CF at all. The ci.moosequest.net precedent (ALB + authenticate-oidc against Google Workspace) is the correct, and only architecturally consistent, pattern for monitoring.moosequest.net.

4. Architecture decision (see ADR-0150 for the full record)

A new, dedicated public ALB (monitoring-grafana-alb) — not a second hostname bolted onto the existing ci-woodpecker-alb (as built, the operator reversed this on 2026-09-03: see §11) — in the same VPC as ci-monitoring and the Woodpecker box (public subnets already exist there per infra/ci).

5. DNS — operator action (Kristerpher, manual, Oracle Dyn)

No IaC path exists for the Dyn zone. After the ALB is applied and its DNS name is known (terraform output monitoring_alb_dns_name), Kristerpher adds, by hand, in the Dyn control panel:

monitoring.moosequest.net.  CNAME  <monitoring-alb-dns-name>.us-east-2.elb.amazonaws.com.  TTL 300

Same shape as the existing ci.moosequest.net CNAME. Also needs an ACM certificate for monitoring.moosequest.net validated via DNS — that CNAME validation record is a second manual Dyn entry ACM will emit; add it before the cert issues. Card #4547 (no existence monitor for hand-typed Dyn records) already tracks the class of risk this introduces — flagging here rather than solving it in this pass.

6. Broader scrape/probe coverage

Follows /onboard-o11y exactly — Terraform (cloud-init.sh.tpl) is the source of truth, hot-applied on the box, one target/probe at a time, idempotent:

Target Type Signal
api.raxx.app blackbox http_2xx prod API liveness
console (raxx.app console URL) blackbox http_2xx console liveness
vault.raxx.app blackbox http_2xx (+ existing TLS-expiry probe already covers cert) Infisical liveness
tickets.raxx.app blackbox http_2xx FreeScout liveness
Woodpecker already scraped (woodpecker job) no change

No new alert severities invented — reuse RaptorProdDown-shaped rules (probe_success == 0, for: 2m, severity: critical) per target, routed through the existing Alertmanager → Postmark → kris@moosequest.net path. Per the skill's liveness-≠-health lesson, each of these gets a follow-up open question (§9) on whether a deeper functional signal exists per surface — do not block this rollout on inventing one for all four at once.

7. Dashboards

One new "Product Status — Overview" dashboard: a single row of up/down stat panels (one per probed surface) + a shared latency graph, sourced from the same blackbox-http/blackbox-tls jobs. Existing CI-specific boards (wp-pipeline-health, ci-ha-health, host-metrics, blackbox-probes, kms-cost-tracking) are untouched. Deliberately not building a per-surface detail board in this pass — that's scope creep into an unmaintained board pile; open a follow-up card per surface only if the overview proves insufficient.

8. Rollout sequence (risk-minimizing — read in order)

  1. Card A — ALB + target group + security groups + ACM cert (Terraform only, infra/monitoring-ingress/ new root, does NOT touch terraform/ci-monitoring/). No DNS live yet; verify via ALB's own DNS name + curl -v for TLS/OIDC redirect.
  2. Card B — Grafana compose port-publish change (127.0.0.1:3000:3000 → 3000:3000) + the one additive SG rule on ci-monitoring-sg. Applied via the same hot-apply + cloud-init-template discipline as /onboard-o11y. This is the only card that touches the shared box, and it's a one-line diff, isolated from scrape/alert config.
  3. Operator action — Kristerpher adds the Dyn CNAME(s) (§5) once Card A's ALB DNS name exists.
  4. Card C — Prometheus/blackbox coverage expansion for the four product surfaces (pure /onboard-o11y pattern, additive to prometheus.yml / blackbox.yml / rule files).
  5. Card D — "Product Status — Overview" Grafana dashboard.
  6. Card E — Grafana OIDC/role-mapping hardening + access review (post-launch, see open questions).

Cards A/B can land and be verified independently of C/D/E — public ingress does not depend on broadened coverage, and vice versa. If Card B's SG/port change causes any issue, rollback is a one-line revert with no coupling to Card C/D.

9. Security considerations

10. Open questions (operator decision needed before Cards C/D/E are claimed)

  1. Should ALB access logging be enabled for monitoring-grafana-alb (and retroactively for ci-woodpecker-alb, which currently has none)? This design assumes "yes, 90d S3" but that's a new recurring cost + a gap in the existing ALB that predates this work — confirm before Card A bakes in a logging bucket.
  2. Grafana role mapping post-OIDC: should every moosequest.net Google account get Grafana Editor, or should this stay Admin-only (Kristerpher) with future viewers added explicitly? Affects whether Card E is "map by domain" or "map by explicit allowlist."
  3. Per-surface functional-health signal (liveness ≠ health, per /onboard-o11y) — for console/vault.raxx.app/tickets.raxx.app, is blackbox http_2xx alone sufficient for v1, or does any of them already expose a deeper /health payload worth alerting on directly? Recommend deferring to a follow-up card per surface rather than blocking this rollout.
  4. Should the ci-woodpecker-alb pattern's WAF rate-limit rule (referenced in alb.tf's webhook/badge comments) be mirrored onto monitoring-grafana-alb, or is the OIDC gate (no anonymous path at all) sufficient without a separate WAF?

11. As built (verified against develop, 2026-10-30 UTC)

Sections 1 to 10 are the design as proposed on 2026-08-31. ADR-0150 is now Accepted and its "As built" table is the authoritative record; the points that change this document: