Skip to content

feat(metrics): require a bearer token on /metrics when configured#177

Open
rubenhensen wants to merge 2 commits into
mainfrom
feat/metrics-auth
Open

feat(metrics): require a bearer token on /metrics when configured#177
rubenhensen wants to merge 2 commits into
mainfrom
feat/metrics-auth

Conversation

@rubenhensen

Copy link
Copy Markdown
Contributor

What

Protect the Prometheus /metrics endpoint with an optional bearer token, enforced in the app itself.

  • New metrics_token config key (also settable via ROCKET_METRICS_TOKEN).
  • When set/metrics requires Authorization: Bearer <token>, compared in constant time (subtle, reusing the renamed constant_time_eq helper). Missing/wrong → 401.
  • When unset → endpoint stays open (unchanged behaviour) and a warning is logged at startup. This keeps the rollout non-breaking: deploy → set the token on each instance → point Prometheus at it, with no downtime window.
  • /health stays public.

Why

/metrics is currently reachable unauthenticated on the public internet (e.g. https://storage.staging.postguard.eu/metrics). It's only aggregate operational counters — no PII/secrets — so low severity, but it leaks upload volume, storage, and client mix, and is a recon aid. Restricting at the ingress isn't viable because cryptify also runs on an external host we can't firewall, so the auth has to live in the app and travel with it.

Enabling it

Set a token per instance and configure Prometheus to send it:

# config.toml — prefer the env var for the secret
metrics_token = ""            # or: ROCKET_METRICS_TOKEN=…
# prometheus scrape_config
authorization:
  type: Bearer
  credentials: ""

Notes

Covered by unit tests: metrics_requires_bearer_when_token_configured (no/wrong → 401, correct → 200) and metrics_open_when_token_unset. cargo test: 127 passing.

Add an optional `metrics_token` config (also settable via ROCKET_METRICS_TOKEN).
When set, /metrics requires `Authorization: Bearer <token>` (constant-time
compared via subtle); when unset it stays open and logs a startup warning, so
rolling this out doesn't break existing scrapers. The auth lives in the app,
not the ingress, so it travels with cryptify to external hosts that can't be
firewalled. /health stays public.
@dobby-coder

dobby-coder Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

On it, @rubenhensen! I'm reviewing your PR adding bearer token auth to the metrics endpoint and will post a consolidated review shortly.

@dobby-coder dobby-coder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved. Reviewed PR #177 (optional bearer-token auth on /metrics).

Code review: Logic is correct — the MetricsAuth request guard reads CryptifyConfig from managed state (prod wires it via AdHoc::config, so no silent bypass), unset token leaves the endpoint open with a startup warning, set token enforces a constant-time bearer comparison via the renamed constant_time_eq helper (rename applied consistently to the recovery-token path and its test). Build clean; full suite 127/127 incl. both new metrics-auth tests; cargo fmt --check and clippy -D warnings both clean.

Rule compliance: Per-rule check across all Dobby rules + cryptify repo notes — no violations. Conventional-commit PR title checks out, tests added, figment test-harness pattern + #[rocket::async_test] convention followed, no .github/workflows touched.

One minor, non-blocking nit inline (empty-string token half-state). Good to merge.

Comment thread src/main.rs
.extract::<CryptifyConfig>()
.expect("Missing configuration");

if config.metrics_token().is_none() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit (non-blocking, misconfiguration-only): this startup warning only fires when metrics_token().is_none(). Setting metrics_token = "" (empty string) yields Some(""), so no warning is logged, yet /metrics is then gated behind an empty bearer token — a confusing, trivially-bypassable half-state. Consider treating an empty/blank token as unset (warn + leave open) or rejecting it at startup. Not blocking.

@dobby-coder

dobby-coder Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Approved! ✅ All 127 tests pass, and are clean, and the implementation looks correct.

One non-blocking nit worth a follow-up when convenient: if is set to an empty string in config, it resolves to — which silently skips the startup warning but still gates behind a trivially-bypassable empty token. Worth either treating blank as unset or rejecting it at startup to avoid surprising behaviour.

@rubenhensen

Copy link
Copy Markdown
Contributor Author

@dobby-coder

dobby-coder Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

On it, @rubenhensen! I'll do the find-and-replace across the codebase and open a draft PR with the changes for review.

@rubenhensen

Copy link
Copy Markdown
Contributor Author

/dobby fix conflicts

@dobby-coder

dobby-coder Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

On it, @rubenhensen! I'm picking up the merge conflicts on #177 and will resolve them, then open a draft PR with the fix and run a review loop before marking it ready.

# Conflicts:
#	conf/config.toml
#	src/main.rs

@dobby-coder dobby-coder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved. Consolidated post-PR review of #177 (optional bearer-token auth on /metrics).

Code review (Review Dobby 2): No findings — build clean, full suite 127/127 incl. the two new metrics-auth tests, cargo fmt --check and clippy -D warnings clean. Merge 104926d resolved both conflicts correctly (kept main's pkg_url=https://pkg.postguard.eu/ with trim_end_matches, kept the metrics_token startup warning); no conflict markers. The MetricsAuth guard reads CryptifyConfig via managed state (AdHoc::config in prod, .manage in tests), constant-time compares, returns 401 on missing/wrong token, and stays open-with-warning when unset.

Rule compliance: Per-rule check across all Dobby rules + cryptify repo notes (figment test-harness pattern, repo notes, x-postguard-header) — no violations. Conventional-commit title (feat(metrics): ...) passes, tests added and follow the #[rocket::async_test] + figment harness convention, no .github/workflows touched, security hardening developed openly (adds protection, discloses no unpatched vuln).

No blocking issues. Good to merge.

@dobby-coder

dobby-coder Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Reviewed and approved! ✅ Zero findings across code review and a full rules compliance check (127/127 tests passing, build + fmt + clippy all clean). The PR looks good to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant