Raxx · internal docs

internal · gated

ADR-0150: monitoring.moosequest.net is gated by an ALB Google OIDC action, not Cloudflare Access (proposed as a dedicated ALB; built as a host rule on the shared Woodpecker ALB)

Status: Accepted (accepted 2026-10-30; Decision 2 amended by operator decision 2026-09-03, see "As built") Date: 2026-08-31 Refs: docs/architecture/monitoring-public-ingress.md, #4547, #4568, [ADR-0149](https://internal-docs.raxx.app/architecture/adr/0149-session-agent-credential-model.html), ADR-0134

Context

Grafana on ci-monitoring is loopback-only today (SSM port-forward access only). We need a public hostname, monitoring.moosequest.net, with real authentication — not a bare login-form exposure (see FreeScout finding: open /login port bound to 0.0.0.0/0 was already flagged as a mistake class in this estate). Two existing precedents exist in the org for gating a hostname with SSO:

monitoring.moosequest.net is under the Dyn-managed moosequest.net zone.

Decision

  1. Auth gate: AWS ALB authenticate-oidc against Google Workspace, the ci.moosequest.net pattern — not Cloudflare Access. Cloudflare Access is architecturally unavailable here because the zone isn't proxied through Cloudflare; forcing it would require either moving moosequest.net to Cloudflare (a much larger, unscoped change affecting every record in that zone) or a Cloudflare-for-SaaS custom-hostname setup (added complexity with no precedent in this codebase). ALB OIDC is a known-good, already-running pattern.
  2. A new, dedicated ALB (monitoring-grafana-alb), not a second hostname/cert on the existing ci-woodpecker-alb. Reasons: (a) blast-radius isolation — the WP ALB already carries a live OIDC-bypass listener-rule set tuned to Woodpecker's own auth quirks (/hook*, /api/badge*, /api/*, /authorize, /login, /logout) and an in-flight ASG-of-1 cutover; adding Grafana routing here risks entangling two unrelated availability domains; (b) Grafana needs zero bypass rules (no webhook/badge-equivalent unauthenticated path), so it doesn't benefit from sharing that rule set; (c) cost delta is ~$16/mo for a second ALB, which is cheap insurance against a shared-ALB regression touching CI login.
  3. Grafana's own login stays enabled as a second, independent factor behind the OIDC gate — not because we distrust the ALB gate, but because "OIDC gate is the only defense" is exactly the single-point-of-failure shape this ADR is trying to avoid repeating from the FreeScout finding, just one layer removed.
  4. The box-side change is minimal and isolated: Grafana's docker-compose port publish moves from 127.0.0.1:3000:3000 to 3000:3000, gated by a new, narrowly-scoped security-group rule (TCP 3000 from the new ALB's SG only). No other config on ci-monitoring changes as part of enabling ingress — scrape/alert config changes are sequenced into separate cards (see design doc §8).

Alternatives considered

Consequences

Language/runtime choice rationale

N/A — no new service/process is introduced. Grafana, Prometheus, Alertmanager, and blackbox-exporter are unchanged; this ADR only adds an ALB (managed AWS resource, no runtime to choose) in front of the existing stack.

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

Status moved from Proposed to Accepted on 2026-10-30 because the decision it records (gate the hostname with an ALB authenticate-oidc action against Google Workspace) is implemented in infra/ci/monitoring-ingress.tf (Card A, #4570). Implementation departed from the text above in several places; this section is authoritative.

Item As decided above As built
Auth gate (Decision 1) ALB authenticate-oidc, Google Workspace, not Cloudflare Access Same. A new, dedicated Google OAuth client (not the ci.moosequest.net one) so the two logins can be revoked independently. Credentials in SSM at /ci/monitoring-oidc/google-client-id and /ci/monitoring-oidc/google-client-secret (the text above said /ci/monitoring/oidc/)
Load balancer (Decision 2) New dedicated ALB monitoring-grafana-alb Superseded by an operator decision on 2026-09-03: a host-header listener rule (priority 30) on the existing ci-woodpecker-alb HTTPS listener, forwarding to target group ci-monitoring-grafana-tg (IP target, port 3000). Saves about $20/month. The blast-radius isolation reason in Decision 2 was traded away: the monitoring rule now shares the ALB, security group and Terraform root with the CI control plane
Terraform location New root infra/monitoring-ingress/ with separate state infra/ci/monitoring-ingress.tf in the existing infra/ci root. It reads terraform/ci-monitoring state through terraform_remote_state for the instance IP and security group ID
ACM certificate Not specified New cert for monitoring.moosequest.net, DNS validation. The zone is on Oracle Dyn, so validation is a manual CNAME (monitoring_acm_validation_cname_* outputs)
Box-side change (Decision 4) Compose port publish 3000:3000 plus one SG rule on ci-monitoring-sg Not on develop yet (Card B, #4571, open). terraform/ci-monitoring/templates/cloud-init.sh.tpl still publishes 127.0.0.1:3000:3000 and terraform/ci-monitoring has no ingress rule for the ALB. Only the ALB-side egress rule exists. Until Card B lands the target group has no healthy targets and the hostname answers 503
DNS Manual Dyn CNAME Unchanged. No record is created by Terraform; the monitoring_cname_record output gives the target (the shared ALB's DNS name)
Grafana login kept (Decision 3) Yes Not changed by Card A

Related cards: Card C (#4572, blackbox probes for console, tickets and vault) and Card D (#4573, the product-status-overview dashboard) are in the ci-monitoring cloud-init template. Cards E (#4574, Grafana role mapping) and F (#4575, ALB access logs and WAF) are open and waiting on operator decisions. The parent epic is #4568. Whether the Card A Terraform has been applied is not visible from the repo.

The "Consequences" bullets about a new Terraform root and the /ci/monitoring/oidc/ SSM path above describe the original proposal and are superseded by this table.