Skip to content

feat(engine-mgmt): StarRocks query log - #849

Merged
viethqb merged 24 commits into
mainfrom
feat/starrocks-query-log
Nov 11, 2026
Merged

viethqb merged 24 commits into
mainfrom
feat/starrocks-query-log

Conversation

@viethqb

@viethqb viethqb commented Nov 11, 2026

Copy link
Copy Markdown
Member

Summary

StarRocks engines get a Query log. egret installs the AuditLoader plugin, which ships in the egret fork image at /opt/egret/auditloader.zip, and it now loads every statement into starrocks_audit_db__.starrocks_audit_tbl__, kept for 30 days. Engine detail gets a Query log tab that reads it.

Design and plan:

  • docs/en/specs/2026-11-09-starrocks-audit-platform-design.md, including the rulings of 2026-11-10 and 2026-11-11 and the facts measured on kind;
  • docs/en/plans/2026-11-09-starrocks-audit-platform.md.

Changes

Setup (query_log.rs)

  • Creates the audit database and table, then the loader user egret_query_log.
    • The user's password is a per-engine system secret (engines.query_log_secret_id). The name is reserved in the username ledger.
    • The loader gets a native grant only when the engine has no policy profile.
  • Installs or reinstalls the plugin. SHOW PLUGINS is read by header, and only Status = INSTALLED counts as installed.
  • Waits for an alive compute node before creating the table.
  • Runs from four places:
    • the post-Ready bootstrap, after the delegation statements;
    • after an apply or Upgrade security, with a bounded retry (5 × 60 s);
    • a boot pass, setup_query_logs;
    • POST /engines/{id}/query-log/setup.
  • None of these block the request or boot path.

Ranger (IR infra)

  • egret-query-log-loader allows only insert on the table.
  • egret-query-log-guard - <chain> rows deny group public, engine admins included, everything on the audit database. The one exception is the loader's insert.
  • Convergence is type-aware: these names count only on StarRocks profiles.

Read API

  • Four routes: status, list, detail and setup.
  • Cursor paging, a range of at most 31 days and filters.
  • Every request value reaches SQL only through quote_string or as an egret-formatted value.
  • FE errors are always scrubbed.
  • Statement text is redacted: exact root and loader passwords plus shape rules.
  • egret's own reads carry /* egret-query-log-read */ and are excluded.

Permission

  • New engine:audit, required to see statement and errorMessage.
  • Migration 0218 grants it to platform roles that hold engine:update.

feConfig

  • plugin_enable and plugin_dir are now refused.

Driver

  • The root and user StarRocks connections set max_allowed_packet and wait_timeout. The driver therefore no longer sends SELECT @@max_allowed_packet,@@wait_timeout, which used to show up in the log.

Frontend

  • A Query log tab, StarRocks only, lazy-loaded. It has:
    • status line and callouts;
    • URL-kept filters;
    • Load more;
    • a side panel.

Test plan

Unit, DB and route tests

  • The full egret-engine-mgmt suite: 626/626, with no DB skips.
  • The egret-core RBAC tests.
  • committed_openapi_json_is_current.
  • Frontend: lint, typecheck, npm run build and the bundle tests. The QueryLogTab tests pass 36/36.
  • Workspace-wide cargo clippy -D warnings --all-targets and cargo fmt --check are clean.
  • Every behaviour change has a mutant that its test kills; these are recorded per task.
  • The full frontend suite has one failure. ScheduleDetail "creates an alert through the real body shape" times out, and it fails the same way on main's tree.

Live, against a real FE (tests/query_log_live.rs, opt-in EGRET_TEST_STARROCKS_URL)

  • On egret.4, setup reaches Ready, and a second run installs nothing.
  • EXECUTE AS rows show the right authorizedUser.
  • egret's own reads are absent.
  • The official image answers pluginNotShipped, with the table and user created.

Measured on a real FE

  • StarRocks itself stores *** for passwords in CREATE/ALTER USER, SET PASSWORD and INSTALL PLUGIN.
  • FE errors were pinned verbatim: missing zip, already installed, unknown database and unknown table.
  • The FE time zone is UTC.
  • The SHOW PLUGINS header was pinned.

Fork e2e with egret's rendered loader and guard policies (egret.4)

  • Audit checks: 10/10. The loader still loads.
  • Guard checks: 6/6. eve, and a user with an all-access allow, are denied SELECT and DROP TABLE on the audit table.

Live on kind (in-cluster egret built from this branch, Ranger on)

Check Result
New engine The tab reaches ready on its own at Running, and delegation statements run first
User / Connected as Shown correctly. Rows were produced with EXECUTE AS (see below)
A Ranger deny Shows Failed with INTERNAL_ERR; the audit row stores the error code only
Admin read of the audit table admin and member-sso are denied. Root is not subject to Ranger
FE pod deleted The plugin is still INSTALLED (re-extracted from the image) and events resume. About 2 minutes of events are lost, so no plugin_dir move is needed
Legacy 4.1.6 engine pluginNotShipped; ready 66 s after Upgrade security
egret's own reads Absent from the list; "Latest event" is stable on reload

Known limit: StarRocks checks SELECT per column, so SELECT count(*) on the audit table gets past the guard. The row count is visible; the contents are not. This is in the spec and the docs.

Found on kind, outside this PR (follow-ups)

  1. Under Ranger, an impersonate-mode StarRocks engine cannot run queries through egret: there is no Ranger impersonate rule for the delegator, and the target user is not created.
  2. The CN has no MYSQL_PWD, so it crash-loops once the initpwd Job has set root's password.
  3. Deleting an engine leaves the initpwd pods and the FE meta PVCs behind.

Documentation

egret-data/docs#75 (Engine detail, Security, Permissions, Upgrade notes, REST API, screenshots).

Checklist

  • Linked to an issue
  • Tests added or updated (or not applicable)
  • Docs updated in egret-data/docs for any user-visible feature, UI, API or config change (or not applicable: internal only)
  • Commit messages follow Conventional Commits
  • No secrets, tokens or real data added

🤖 Generated with Claude Code

viethqb and others added 23 commits November 10, 2026 20:30
Design for package A of the StarRocks follow-ups: the AuditLoader setup,
the loader user and Ranger guard, the read API and the Query log tab,
with four open questions for the user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ETURNING lists

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…icies

A StarRocks profile bound to a provisioned engine now gets two IR-owned
infrastructure statements, converged like the egret-admin set:

- QueryLogLoader: egret_query_log may insert into
  default_catalog.starrocks_audit_db__.starrocks_audit_tbl__
  (egret-query-log-loader);
- QueryLogGuard: group public is denied every access on the audit
  database, one row per hierarchy under database
  (egret-query-log-guard - <chain>), with the loader's insert excepted
  on the table row. Engine admins are denied too (open question 2, (a)).

The statements need only a bound non-external StarRocks engine, not a
delegating one; AdminAllAccess keeps its own condition and Trino output
is unchanged. The names are IR infra names and reserved on StarRocks
profiles only. Convergence's row query and the boot pass's service list
cover the new names and the non-delegating StarRocks profiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… test

A provisioned StarRocks engine now brings the query-log loader and guard
(managed_by = 'infra') onto its profile. The test counts the masks and the
hidden-column deny the compiler writes for tagged columns, so it leaves
the infra rows out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…only

is_ir_infra_name ignored the service type, so a human policy named
egret-query-log-loader on a Trino profile (a legal name there) was read
by convergence and deleted, and the boot pass pulled the profile in for
holding it. The predicate now takes the service type and owns the
query-log shapes on starrocks profiles only, the same rule as the
reservation. Convergence passes the profile's type; the boot pass reads
it with the holding rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec and the fork's measured audit-loader policy have no column
level, and insert is a table-leaf access type in the StarRocks def. The
guard render test now pins the table and column rows' deny accesses as
literals, and the guard arm maps its store error with from_store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Query log's audit plugin depends on both; an feConfig override must
not switch plugins off or move the plugin directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ensure_query_log creates the audit database and table (the fork's
audit-table.sql at d77446e), the egret_query_log loader user, the native
grant on unbound engines and the AuditLoader plugin on egret.4+ images,
idempotently. It runs from the post-Ready bootstrap behind its own
checkpoint, after a StarRocks update or security upgrade that restarts
the FE, and once at start for every provisioned StarRocks engine.

The bootstrap's FE-ready and root-login loops move unchanged into
wait_for_fe_root_login, shared with the spawned setup. StarRocksDdl gains
query_with_header so SHOW PLUGINS is read by its Name column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Just after an apply the old FE pod still answers, so the first setup can
reach the pod that is being replaced: after an upgrade to egret.4 its
INSTALL hits an official image without the plugin. spawn_query_log_setup,
also used by the boot pass, now tries up to five times a minute apart,
each behind the FE waits and checkpoints, and warns only after the last.

An INSTALL answered with already-installed (an overlapping setup won) is
Ready, not Failed. The update and security-upgrade spawns are now under
test, the probe resend has a unit test, the scrub map uses the real
secret id, and the taken-name test cannot leave its human behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the four query-log routes on the engines router: the status read
from the FE, a keyset-paged list, one entry with its whole statement,
and the synchronous setup. Every read starts with the
`/* egret-query-log-read */` marker, which the SQL uses to leave egret's
own reads out, and carries only validated values through quote_string
or instants egret formatted itself (UTC until the FE zone is measured).

Statement text needs the new platform permission `engine:audit`,
granted by migration 0218 to every platform role holding
`engine:update`. Statements and FE errors are redacted of the root and
loader passwords and of secret-shaped literals; FE errors leave only
through FeError::scrubbed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 of the query-log reads:

- Without engine:audit, errorMessage is absent like statement: a
  StarRocks error quotes the statement it rejects (user ruling,
  2026-11-11, recorded in the spec and the plan).
- The user filter reads authorizedUser as authorized_name does: the
  name up to the last '@'<host>', with '' read as one quote (user
  ruling, 2026-11-11).
- A password the 2,000-character cut runs through no longer leaves its
  prefix at the end of a preview.
- SET PASSWORD ... = PASSWORD('x') and SET PASSWORD = 'x' are redacted.
- A missing table on an FE that then stops answering is a 502 with the
  scrubbed message, not a 409 carrying `unreachable`.
- Migration 0218's engine:audit grant has a DB test, and the DTO
  comments say null where the field is null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… FEs

Add tests/query_log_live.rs, gated on EGRET_TEST_STARROCKS_URL (egret.4)
and EGRET_TEST_STARROCKS_OFFICIAL_URL (official 4.1.7): Ready with events
and no second INSTALL, the second-user and EXECUTE AS rows through the
list read with the right authorizedUser, user filters, cursor paging,
egret's own reads excluded, and PluginNotShipped with the table and user
created on the official image.

The live suite reaches the setup and the list read through a test-util
entry point; the list route's page read is factored into read_page so
the test runs the route's own code. Pin the FE texts measured in Task 5
in the unit tests and the fake, and record UTC as measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A provisioned StarRocks engine's detail page gets a Query log tab after
Monitor (?tab=query-log; a Trino engine falls back to Overview). It shows
the status line from /query-log/status (ready, notSetUp with Set up query
log, pluginNotShipped naming Upgrade security, unreachable with Retry),
filters kept in ?from=&to=&user=&state=, the statements newest first with
Load more over nextCursor, and a side panel with one statement whole.
Without engine:audit the statement and the error read "Hidden: needs
engine:audit".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Show a delivered errorMessage even when the statement is absent; read
  text as withheld only when both are absent.
- Filters and the opened statement push history, so Back walks them
  (FE-PAGE-2).
- URL values the server would refuse (bad from/to, unknown state, a user
  over 128 characters) fall back to the defaults; "filtered" is read from
  the parsed values.
- Name the row's open button, show Connected as in the panel only when it
  differs, and give an unknown status state a neutral line.
- Load the tab lazily, which keeps it out of the entry chunk (the
  workflows bundle test marks its module by "Last 24 hours").
- Spec section 6 (en, vi): the notSetUp sentence and both
  pluginNotShipped variants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…retry at bootstrap

- plugin_step reads the SHOW PLUGINS Status column by header: only an
  AuditLoader row whose status is INSTALLED is installed. Any other row
  gets UNINSTALL PLUGIN then INSTALL on an image that ships the plugin,
  and is NotShipped elsewhere; the status route reads notSetUp. A header
  without Status keeps the old reading.
- The step-6 probe and its poll start with the read marker, so the list
  and the newest event leave them out; the poll matches the probe's UUID
  without that exclusion, so it still finds it.
- The bootstrap's failed first query-log setup gets the after-apply
  path's bounded retry (retry_query_log_setup, shared).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keep the driver preamble out of the log

Two defects found by the live check on kind:

- The bootstrap's setup ran before any compute node had registered, so a
  shared-data FE refused CREATE TABLE ("Number of available CN nodes is 0")
  and a new engine stayed notSetUp. ensure_query_log now polls SHOW COMPUTE
  NODES and SHOW BACKENDS (read-marked, Alive read by header) for up to
  about 5 minutes before step 2; a timeout is a scrubbed Failed, so the
  bounded retry still applies. The synchronous route checks once. The FE's
  "available CN nodes is 0" answer is decided on the raw message and is
  Failed, never terminal.
- Every root connection logged mysql_async's unmarked
  SELECT @@max_allowed_packet,@@wait_timeout, which drove "Latest event".
  The root opts now set both (StarRocks' session defaults), which makes the
  vendored driver skip that query.

The spec records the facts measured on kind on 2026-11-11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…river settings on user connections

- The bootstrap now runs the query log setup (its checkpoint, the wait for
  an alive compute node, and step 6) after the delegation statements, so
  the node wait can never delay them. The checkpoints are renumbered in
  order (4-6 delegation, 7 query log); the security-integration
  checkpoint test now flips on the catalog CREATE again, and a new test
  pins checkpoint 7 on a delegating engine. Another shows every delegation
  statement is sent before the first node check while no node is alive.
- StarRocksConnector sets max_allowed_packet and wait_timeout too, so user
  and delegated sessions no longer log the driver's
  SELECT @@max_allowed_packet,@@wait_timeout. The values live once in
  egret_query_api::starrocks_session, shared with the root connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s single check

- an_engine_deleted_during_the_node_wait_gets_no_audit_ddl: the engine is
  flipped to deleting from the first node listing (new
  FakeStarRocksDdl::on_node_check hook); the post-wait checkpoint answers
  Failed and no audit DDL is sent.
- the_setup_route_checks_once_for_a_node_and_does_not_wait (routes.rs,
  production bounds): with no node ever alive the route answers a 502
  within 20 s after one node check.
- Spec (en, vi): fact 2 and fact 3 say only what was measured, the
  own-reads paragraph covers user connections, vi section 7 matches en.
- starrocks_session: note the one behaviour difference of a pinned
  max_allowed_packet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rows

Every StarRocks service now compiles egret-query-log-loader plus six
egret-query-log-guard rows. A semantic comparison shows each fixture keeps
every existing policy unchanged (ignoring the positional id) and gains only
these seven rows per StarRocks service.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@viethqb
viethqb merged commit a493356 into main Nov 11, 2026
9 checks passed
@viethqb
viethqb deleted the feat/starrocks-query-log branch November 11, 2026 14:54
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