Repository navigation
fix: serve the console metrics dashboard to admin sessions only - #727
Conversation
The console metrics feed at /console/metrics-data returned the rows of the shared metrics table to any console session. That table has no account column, so an IAM user of one account read the table names, index names, operations, request counts and latencies of every account. The page put an IAM user's account id in a script variable that no script read, and the server sent every row. The metrics page now redirects an IAM-user session to the console dashboard, and the data feed answers it with HTTP 403 and no metric data before the metrics store or the in-memory collector is read. Admin sessions get the same page and payload as before. The navigation bar shows the Metrics link to admins only, and the unused account variable is removed.
Two accounts each create a table and send it reads and writes. Once the metrics flush has written both tables' rows, as the admin's feed shows, an IAM user of the second account gets HTTP 403 and no table names from /console/metrics-data under four query strings, is redirected from /console/metrics, and has no Metrics link in the navigation bar. The admin gets the page and both accounts' tables. The module waits up to three minutes for the flush, so it carries the slow marker, like the TTL tests that wait.
The architecture guide, admin guide, getting started guide and security model say the metrics dashboard serves admins and covers every account. The upgrade manual records the v0.1.15 behaviour change: IAM users no longer get the dashboard or its data feed. The demo manual says to open the dashboard as the admin user.
…orrect comments The fake metrics store's rows now carry an index name. The IAM-user 403 test checks that the refusal does not contain it, and the admin test checks that the admin's feed reports it as a GlobalSecondaryIndexName dimension. With no index name in the rows, the first check could not fail. The router comments for the metrics page and its data feed, and the comment in the cross-account isolation test, now say both serve admin sessions only. The admin guide's console feature list says an IAM-user session is redirected to the console home page, /console. The metrics example in AGENTS.md passes admin credentials, which /metrics requires.
| <a href="/console/accounts">Accounts</a> | ||
| <a href="/console/metrics">Metrics</a> | ||
| <a href="/console/accounts">Accounts</a>{metrics_link} | ||
| <a href="/console/settings">Settings</a> |
There was a problem hiding this comment.
There's an inconsistent refusals for IAM users.
- Metrics page: redirects to /console.
- Settings and Cache: plain-text 403 ("Settings are visible to admin users only"), and they're still linked in the navigation bar.
- Metrics data endpoint: JSON 403.
Three admin-only pages now refuse in three different ways, and the navigation bar hides only one of them. We can hide all admin-only links in the nav bar from IAM users.
There was a problem hiding this comment.
Agreed. e28d8db hides the Metrics, Settings and Cache links from IAM users. All three pages and the cache form now redirect an IAM-user session to /console through one check, require_admin. /console/metrics-data keeps its JSON 403, because the dashboard's script fetches that route and parses the response as JSON.
The branch conflicts with main in one docs file, so CI has not run on this commit yet. The console unit tests and tests/test_console*.py pass locally against a SQLite build.
The navigation bar showed IAM users the Settings and Cache links, and those pages answered them with a plain-text 403. The Metrics page redirected them to /console. The bar now leaves out the Metrics, Settings and Cache links for an IAM-user session. The three pages and the cache form refuse that session through one helper, require_admin, which redirects it to /console. The other admin-only console pages already redirect an IAM-user session. The metrics data feed still answers an IAM-user session with HTTP 403 and a JSON body, because the dashboard's script parses that response as JSON.
|
#718 rewrote the login rate limiting bullet in On the merge, |
#734 added a sentence that says /console/metrics-data serves every console session the metrics of every account until this pull request merges. This pull request makes the feed admin only, so the sentence is false once it merges. The admin-only metrics bullet in the same section states the new behaviour.
|
791ae9a removes the sentence that #734 added to |
What
The console metrics dashboard and its data feed now serve admin sessions only. The Settings and Cache pages were already admin only. Now the three pages refuse an IAM-user session in the same way.
GET /console/metrics,GET /console/settings,GET /console/cacheandPOST /console/cache/invalidateredirect an IAM-user session to/console. They share one check,require_admin. Before, Settings, Cache and the cache form answered with a plain-text 403. The other admin-only console pages already redirect an IAM-user session.GET /console/metrics-dataanswers an IAM-user session with HTTP 403 and{"__type":"AccessDeniedException","message":"Metrics are visible to admin users only"}. The handler returns the refusal before it reads the metrics store or the in-memory collector. This route is a JSON endpoint, and the dashboard's script parses its response as JSON. So the route keeps the 403 instead of a redirect to an HTML page.identity_labelnow returns the label together with whether the caller is an admin. So the 38nav_barcall sites are unchanged.const accountFilterfor IAM users. The dashboard script never read that variable.Admin sessions get the same payload, pages and navigation bar as before. The only change for them is that the metrics page no longer has the unused
accountFiltervariable.The architecture guide, admin guide, getting started guide and security model now say that the dashboard is admin only and covers every account. The admin guide also lists Settings and Cache as admin only. The upgrade manual has a v0.1.15 entry for the change. The demo manual says to open the dashboard as the admin user. AGENTS.md's
/metricsexample now passes admin credentials, which that endpoint requires.Merging with #704 and #734
This PR and #704 (console session revocation) conflict only in the imports of
crates/server/src/console/pages/mod.rs. This PR addsuse crate::console::html;, and #704 addsuse crate::console::session::SessionCheck;. Keeping both lines resolves it. Before the Settings and Cache change, a trial merge with #704 passed 20 tests incargo test -p extenddb-server --lib console. Against a SQLite build of that merged tree,tests/test_console_metrics_admin_only.pypassed 7 cases. The Settings and Cache change adds no conflict with #704.#734 (console account pages) is on
main. It added a sentence todocs/manuals/10-security-model.md: until this PR merges,/console/metrics-dataserves every console session the table metrics of every account. 791ae9a removes that sentence. In the same file, #718 rewrote the login rate limiting line next to this branch's new line. 61a5800 mergesmainand keeps both lines.Why
/console/metrics-datachecked only that the caller had a console session, then returned the rows of themetricstable. That table has no account column, and the store query filters on time, table name and metric only. So any IAM user who could log in to the console read the table names, index names, operations, request counts and latencies of every account. The account id that the page set for IAM users stayed in the browser and did not limit what the server sent.Reproduction on main's console code (
f49c5f823), SQLite backend, 2026-11-01:/console/loginand request/console/metrics-data.TableNamevalues. All of them are tables of other accounts on the server.?window=LastHourand?window=AllTimereturn the same 18 names.The same steps on this branch return HTTP 403 with the body above and no table names.
/console/metricsreturns a redirect to/console.Testing done
New tests:
crates/server/src/console/pages/metrics_pages_tests.rs, 5 tests:accountFilter.tests/test_console_metrics_admin_only.py, 13 cases against a server. Two accounts each create a table and send it reads and writes. The fixture waits until the admin's feed shows both tables, which takes up to two flush intervals of 60 seconds. Then:Commands run before the Settings and Cache change:
cargo +1.97.0 fmt --all -- --check: clean.cargo +1.97.0 clippy --locked -p extenddb-server --all-targets -- -D warnings: clean. With-W clippy::pedanticthe crate has 499 warnings, and none of them are in the changed files.cargo test --locked -p extenddb-serveron Rust 1.99.0 and on 1.88.0: 242 passed on each.cargo check --locked --workspace --all-targets: clean.tests/test_console*.pypassed 42 cases, including the 7 new cases.tests/test_metrics.pywithtests/test_cross_account_isolation.pypassed 23.Commands on e28d8db, which adds the Settings and Cache change:
cargo +1.97.0 fmt --all -- --check: clean.cargo +1.97.0 clippy --locked -p extenddb-server --all-targets -- -D warnings: clean.cargo +1.97.0 test --locked -p extenddb-server --lib console: 8 passed.tests/test_console_metrics_admin_only.pypassed 13 cases. With the same server build, the othertests/test_console*.pyfiles passed 35.Negative controls, each restored with git afterwards. Before the Settings and Cache change:
On e28d8db:
Checklist
cargo test --workspace)cargo fmt --check)cargo clippy -- -W clippy::pedantic)Storagetrait, auth model, on-diskformat, or public CLI surface, an RFC has been accepted or is linked
below. Otherwise, an ADR captures the decision (link below).
On 5d885b4, CI ran the workspace tests on Rust 1.88.0 and on stable, and the integration suites on all four backends. All of them passed. Clippy ran with
-D warnings, without the pedantic group. CI runs again on 791ae9a, after the merge ofmain.ADR / RFC: n/a
Breaking changes
IAM users lose the console metrics dashboard. The page redirects them to
/console, the data feed returns HTTP 403, and the navigation bar no longer links it. The Settings page, the Cache page and the cache form now redirect an IAM-user session to/console, where they answered HTTP 403 before. The navigation bar no longer links Settings and Cache for IAM users. Admin sessions and the admin-authenticated/metricsendpoint are unchanged.Not covered by this change
metricstable has no account column on any of the four backends. The collector and the flush worker carry no account dimension either. A view of its own metrics for an IAM user needs that column and a migration on each backend. It also needs the account carried from the request through the collector and the flush worker. Without the column, two accounts that use the same table name also share one row. So an admin sees their counts and latencies added together.curl .../metricswithout credentials, which answers 401. docs: correct the quick start, monitoring commands, backend count and design index #668 fixes them.By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache License 2.0 and I agree to the Developer Certificate of
Origin (DCO). See CONTRIBUTING.md for details.