feat(security): DashboardTags on API-key constraints; sessions inherit owner tags (SEC-25 groundwork)

Adds a dashboard event-visibility tag to ApiKeyConstraints, riding in the existing
constraints JSON blob so no auth-store schema migration is needed (design
docs/plans/2026-07-10-dashboard-session-acl-tst15.md sections 3/3.1, open call
settled per its own recommendation). The tag is visibility-only: no read, write,
browse, or subscribe path consults it, and HasRead/HasWriteConstraints ignore it.

GatewaySession gains an immutable, ordinal-ignore-case Tags set stamped at
construction from the owning API key, forwarded by MxAccessGatewayService.OpenSession
from the resolved ApiKeyIdentity — never from the wire request, so a client cannot
label its own session with another tenant's tag. ISessionManager gains a tag-carrying
OpenSessionAsync overload whose default implementation forwards to the tagless one, so
an implementation that does not model tags opens an untagged (least visible) session.

apikey create-key gains --dashboard-tags team-a,team-b (repeatable, trimmed,
de-duplicated; an empty segment is rejected rather than dropped) and list-keys prints
the tags column. No enforcement yet — the EventsHub ACL that consumes the tag is a
later change.
This commit is contained in:
Joseph Doherty
2026-08-17 03:58:14 -04:00
parent 9130994736
commit a212e145ac
14 changed files with 477 additions and 10 deletions
+24
View File
@@ -178,6 +178,30 @@ Supported constraints are:
| `browse_subtrees` | Contained-path globs used to filter Galaxy browse results and deploy-event counts. |
| `read_alarm_only` | Read/subscription commands must target objects with alarm-bearing attributes. |
| `read_historized_only` | Read/subscription commands must target objects with historized attributes. |
| `dashboard_tags` | Dashboard event-visibility tags. **Not a data-access constraint** — see below. |
`dashboard_tags` is the one member of the blob that constrains nothing on the
gRPC data path. No read, write, browse, or subscribe check consults it, and
`HasReadConstraints` / `HasWriteConstraints` deliberately ignore it: adding a tag
neither widens nor narrows what a key may read or write. It rides in the same
serialized blob only to avoid an auth-store schema migration
(`docs/plans/2026-07-10-dashboard-session-acl-tst15.md` §3.1).
Its sole purpose is dashboard event visibility. A session records the tags of the
API key that opened it (`GatewaySession.Tags`, immutable for the session's life,
compared ordinal-ignore-case). The tags come from the owning key, never from the
client's `OpenSession` request, so a client cannot label its own session with
another tenant's tag. A key with no tags opens untagged sessions.
Tags are set at key creation with
`apikey create-key --dashboard-tags team-a,team-b` (repeatable; segments are
trimmed and de-duplicated ordinal-ignore-case). Keys created from the dashboard
API Keys page are currently always untagged.
The tag is carried end to end today; the dashboard ACL that consumes it — scoping
a Viewer's `EventsHub` subscriptions to the sessions their LDAP groups are
granted — is a separate change. Until it lands, the tag affects nothing at
runtime.
Glob matching is anchored, case-insensitive, and supports `*` and `?`.
Subtree and tag glob lists are alternatives: matching either list allows that