Skip to content

fix(web-console): query OpenShift dashboard metrics securely - #270

Merged
jsell-rh merged 3 commits into
mainfrom
fix/dashboard-metrics-sources
Sep 10, 2026
Merged

jsell-rh merged 3 commits into
mainfrom
fix/dashboard-metrics-sources

Conversation

@jsell-rh

@jsell-rh jsell-rh commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

The web dashboard returns HTTP 502 on hypc-aws-00 because its metrics client defaults to localhost. OpenShift cluster metrics and HyperShell gateway metrics are also in separate stores.

Add a separate cluster metrics endpoint with a rotating token file and trusted CA bundle. Cluster charts use that endpoint; gateway counts and provision duration use application Prometheus with a server-configured instance namespace. Repeated API replica gauges do not add duplicate counts. Missing cluster samples return an error. Existing single-source development settings still work.

Both clients limit responses to 4 MiB. The application client applies the limit after decompression. Credential errors identify the configuration variable and failure type without exposing file paths or values. TLS verification, redirect rejection, browser credential isolation, and existing dashboard authorization stay in place.

The reusable OpenShift deployment component is owned here at deploy/gitops-base/components/openshift-dashboard-metrics. It contains the console patch, service account, service CA, and projected token mount. GitOps PR #119 consumes a pinned component and supplies its application endpoint and instance RoleBinding. This component works with both deploy/hub and the wrapper from PR #251. It is optional, so #251 can merge in either order.

Tracking: HYPERSHELL-329. Deployment configuration is tracked by HYPERSHELL-330.

Validation: the full pnpm check:web command passes, including 187 backend tests, formatting, lint, type checks, the production build, and Storybook. Tests cover HTTP/HTTPS size limits, chunked stream cancellation, compressed responses, credential failure types, TLS, token rotation, redirects, timeout, source selection, and namespace filtering. Repository policy checks pass. The component builds with the existing hub base and PR #251's wrapper in both hyp0 and hyp1. Read-only queries confirmed the live platform metrics, zero-valued pod phases, the metrics API role, and both gateway namespace label schemas. A regression test covers valid zero-valued cluster samples. The README describes missing data and rate history requirements.

Rollout: merge this source change, publish its console image, then update the component revision and console image pins in the companion GitOps PR before merge. No live configuration has changed.

Track HYPERSHELL-329. Separate cluster and application metrics, read rotating credentials, and filter gateway metrics by instance.
@jsell-rh jsell-rh added the hypbox-managed Changes for hypbox cluster management label Sep 10, 2026
@coderabbitai

coderabbitai Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: b4179f15-7f88-4453-ad4c-3a091c0b766e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@amber-review-bot

amber-review-bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@amber-review-bot amber-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

COMMENT. This is a well-constructed, security-conscious fix: the new fetchMetrics helper keeps cluster-monitoring credentials server-side, verifies TLS, refuses redirects, re-reads projected files per request for rotation, bounds the response body, and validates the token and namespace before use. I found no blockers or convention violations; the notes below are minor hardening/observability points plus one cross-PR coordination decision for maintainers.

Strengths

  • Credentials never reach the browser or the application-metrics request path; test asserts browserRuntimeConfig excludes the cluster token and URL.
  • Redirects cannot receive the bearer token (redirect: "error" on the unauthenticated path; node:https request does not follow redirects and the token-before-TLS-trust ordering is covered by a test).
  • Config fails closed: credential files require an explicit https:// cluster URL, and the namespace is regex-validated (in config and again in namespaceSelector) before being interpolated with JSON.stringify, preventing PromQL selector injection.
  • Error messages are generic and do not echo token/CA contents.

Test Diff Scrutiny

The four pre-existing cluster tests (metrics-cluster-cpu/memory/nodes/pods) change the expected error string from a source-specific message (e.g. "No cluster CPU capacity data") to "Prometheus query returned no samples". This is a message change, not a flipped contract: the "missing data throws" guarantee is preserved, and the intent (never surface zero for absent samples) is reinforced by the new does not report zero CPU use or gateway counts when samples are missing test. No removed guarantee.

Minor findings

  1. [Minor] metrics-source.ts:22 - the unauthenticated path returns fetch(...) directly and relies on the caller's response.json(), so it does not apply the 4 MiB body cap that the HTTPS path enforces (metrics-source.ts:71). For the trusted in-cluster app endpoint this is acceptable, but the asymmetry means a misbehaving/compromised app Prometheus could return an unbounded body. Consider a consistent size bound or a short note on why the app path is exempt. Confidence: Medium.

  2. [Minor] metrics-source.ts:44 - the catch {} collapses distinct credential failures (missing file, empty value, malformed token) into one generic "Could not read metrics credentials". Keeping secrets out of errors is correct, but a redacted debug log (e.g. which file, len=, failure kind) would materially help operators diagnose token-rotation/mount problems without leaking the value. Confidence: Medium.

Cross-PR coordination

PR 270 introduces new required deployment wiring for the web console (a cluster-monitoring-metrics-api RoleBinding in openshift-monitoring, a projected service-account token volume, a CA ConfigMap mount, and the CLUSTER_PROMETHEUS_* / PROMETHEUS_NAMESPACE env vars) and states this configuration is delivered through the external hypershell-gitops repository. PR #251 moves the GitOps deployment layer into this repository (deploy/gitops-base) on the stated principle that "the app owns its deployment definition," and it already owns the web-console Deployment patches and NetworkPolicies. These two efforts make opposite assumptions about where the console's deployment configuration is authored. Maintainers should decide the canonical home for this PR's console monitoring-access wiring and the merge order: if #251 establishes in-repo ownership of the web-console deployment surface, PR 270's RBAC/token-mount/env configuration should target deploy/gitops-base rather than the external repo, to avoid drift or one source overwriting the other.

Convention Checklist

Convention Result
No panic() / fail-fast in production code Pass
Errors carry context, no secret leakage in errors/logs Pass
No secrets in responses or browser config Pass
Input validated (namespace selector, token charset, HTTPS-only creds) Pass
Redirects refused; TLS verification enforced Pass
Config separated from code (env-driven) Pass
Test Diff Scrutiny (modified assertions justified) Pass
No em dashes Pass

Findings Summary (highest severity first):

  1. [Minor] Unauthenticated metrics path lacks the 4 MiB body cap the HTTPS path enforces - Hardening (metrics-source.ts:22)
  2. [Minor] Generic credential-read error hides failure cause from operators; add a redacted debug log - Observability (metrics-source.ts:44)

Apply the response limit to both clients, report credential failure types without secret values, and provide the reusable OpenShift deployment component. Fix README formatting for CI.
@jsell-rh

jsell-rh commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator Author

Addressed Amber's review in d04466a and resolved both inline threads.

Both clients now enforce a 4 MiB response limit, including decoded application responses. Credential errors identify the configuration variable and failure type without file paths or credential values. The README formatting failure is fixed. The full pnpm check:web command passes locally, including 186 backend tests and the production and Storybook builds.

For deployment ownership, the reusable component is now in deploy/gitops-base/components/openshift-dashboard-metrics. GitOps PR #119 consumes that component and retains only its application metrics endpoint and instance bindings. I verified that the component builds with both the existing hub base and PR #251's wrapper, in hyp0 and hyp1. This keeps deployment settings in the application repository and permits #251 to merge in either order.

PR #119 remains a draft until #270 merges and its console image is published and pinned. No live configuration changed. CI is running on the new commit.

@amber-review-bot

amber-review-bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

Amber review: comment

Amber review

Status: Complete

View the submitted review.

@amber-review-bot amber-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

This PR cleanly separates cluster metrics (authenticated, TLS-verified, per-request token rotation) from application metrics, bounds both response paths to 4 MiB, and keeps credentials off redirects and out of logs. The security posture is solid; my remaining notes are verification items and one behavioral change worth confirming, so this is a COMMENT rather than an approval-blocking review.

Summary

The design is careful: fetchMetrics refuses to attach a bearer token to a non-HTTPS URL or a URL carrying inline credentials, uses redirect: "error" on the plain path, and the raw https.request path never follows redirects, so the token cannot leak to a redirect target. readCredential reports only a fixed variable name plus a coarse failure type (missing/unreadable/empty/invalid format), never the path, value, or underlying fs error. Namespace selectors are regex-validated and quoted, so PromQL injection is not possible. Both response readers enforce the 4 MiB cap, and the plain path applies it after undici decompression. Test coverage is broad (size limits on HTTP/HTTPS, compressed bodies, stream cancellation, token rotation, redirect rejection, TLS, timeout, source selection, namespace filtering).

The notes below are Minor.

Findings

  1. [Minor] Namespace label names differ between metric families (namespace for hypershell_gateways_total, k8s_namespace_name for gateway_provision_duration_seconds_*). If either label does not match the label the metric actually carries in the target Prometheus, the query returns no samples, which the new code now surfaces as a hard error (HTTP 502) rather than an empty chart. Please confirm both labels against the live metric schema. Correctness (Confidence: Medium)

  2. [Minor] The empty-sample contract for cluster queries changed from "return 0" to "throw". This is intentional per the description, and the updated existing tests still assert rejection (guarantee preserved, message refined). Worth confirming that no cluster subquery (for example a genuinely idle "used" series that resolves to an empty result set rather than a 0 sample) can now 502 a previously-rendering dashboard. Behavior Change (Confidence: Medium)

  3. [Minor] In the authenticated data handler, reject(...) is called before req.destroy() on the over-limit branch. The Promise ignores the later settlement so this is harmless, but destroying first is slightly cleaner. Style (Confidence: High)

Cross-PR coordination

No material cross-PR coordination issue requires maintainer action.

Findings Summary (ordered by severity, highest first):

  1. [Minor] Namespace label names differ between gateway metric families; verify they match the live metric schema or empty results become 502s - Correctness (metrics-gateway-provision-duration.ts:85, metrics-gateways.ts:41)
  2. [Minor] Cluster empty-sample handling changed from return-0 to throw; confirm no legitimately-empty series 502s a working dashboard - Behavior Change (metrics-cluster-cpu.ts:53)
  3. [Minor] reject before req.destroy() on the over-limit branch - Style (metrics-source.ts:129-130)

Convention Checklist

Convention Result
No secrets in logs or error messages Pass
Secret references, not inline secrets (projected token/CA files) Pass
Input validated (namespace regex, token format, HTTPS enforcement) Pass
No panic / explicit error propagation Pass
Config separated from code (env-driven, GitOps component) Pass
Test diff scrutiny (modified assertions still assert rejection) Pass
SecurityContext on pod specs (patch adds only env/SA/volumes to existing spec) N/A

Confirm live namespace labels, document missing observations, cover valid zero-valued cluster samples, and destroy oversized requests before rejecting them.
@amber-review-bot

amber-review-bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

Amber review: approve

Amber review

Status: Complete

View the submitted review.

@amber-review-bot amber-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

APPROVE. This change routes cluster metrics through a credentialed, TLS-verified server-side client while keeping gateway metrics on the application store, and it is backed by unusually thorough tests covering size limits, token rotation, redirects, credential-failure redaction, and zero-vs-missing sample handling. I found no blocker, critical, or major issues; the notes below are minor and non-blocking.

Amber Analysis

The BFF now fetches OpenShift cluster metrics from a dedicated HTTPS endpoint using a rotating projected token and a service-CA bundle, while gateway counts and provision duration stay on the application Prometheus with a server-fixed instance namespace. The security posture is strong: TLS verification stays on, redirects are rejected before the bearer token is attached, credential-read failures name only the env var and failure type (no paths or values), namespace input is regex-validated and quoted via JSON.stringify to block PromQL injection, and both fetch paths cap responses at 4 MiB.

Test Diff Scrutiny. Four pre-existing cluster tests flip their expected message from a specific "No cluster X data" string to the generic "Prometheus query returned no samples". This is not a removed guarantee: queryPrometheusInstant changed from returning 0 on an empty result to throwing, and the new metrics-source.test.ts explicitly proves that samples carrying a literal value of 0 (idle CPU, zero ready nodes, empty pod phases) are still accepted, while only a truly absent series errors. This matches the stated intent that missing cluster samples surface as an error (HTTP 502) rather than a fabricated zero, and it is well covered.

Security review. No secrets are logged or returned to the browser; browserRuntimeConfig is asserted to exclude the cluster token and URL. Credential files are read per request to honor rotation. The deployment component disables SA token automounting and mounts a short-lived projected token. Container SecurityContext is unchanged by this PR and is not in scope.

Full findings

Minor - observability asymmetry (metrics-gateways.ts). With PROMETHEUS_NAMESPACE set (the production deployment path), an empty gateway result throws "No gateway metrics for the configured namespace" and returns 502. Without a namespace (dev / dedicated-store), an empty result silently returns all-zero phase counts, which could mask a metrics outage as "0 gateways". This looks intentional as a single-source dev fallback, but the two paths now disagree with the cluster-metrics "missing means error" convention. Confirm this is deliberate; if so, no change needed.

Nit. The authenticated HTTPS path in fetchMetrics builds the response with an implicit 200 status and does not negotiate/decompress content encoding (Node's http.request sends no Accept-Encoding, so the 4 MiB cap is over uncompressed bytes today). Fine as written; worth a comment if compression negotiation is ever added, since the cap would then apply to compressed bytes.

Cross-PR coordination

No material cross-PR coordination issue requires maintainer action.

Findings Summary

(ordered by severity, highest first)

  1. [Minor] Asymmetric missing-data handling: namespaced gateway query errors, non-namespaced returns zeros silently - Observability (metrics-gateways.ts L68)
  2. [Minor] Authenticated HTTPS path does not decompress; 4 MiB cap applies to raw bytes only - Robustness (metrics-source.ts L104-155)

Convention Checklist

Convention Result
No secrets in logs or responses Pass
Input validated (namespace regex, token format, HTTPS-only creds) Pass
Secret references / files, not inline secrets Pass
TLS verification enabled; redirects reject credentials Pass
Errors surfaced, partial failures not silently swallowed Pass
Config separated from code (env vars, kustomize component) Pass
Test diff scrutiny (modified assertions justified + backfilled by new tests) Pass
Container SecurityContext / SA token hardening Pass
No em dashes Pass
Conventional commit messages Pass

@jsell-rh
jsell-rh added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 285b2ec Sep 10, 2026
19 checks passed
@jsell-rh
jsell-rh deleted the fix/dashboard-metrics-sources branch September 10, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hypbox-managed Changes for hypbox cluster management

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants