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:
+15
-2
@@ -263,6 +263,7 @@ mxgateway apikey init-db
|
||||
mxgateway apikey create-key --key-id ops.alice --display-name "Alice (ops)" --scopes read,write
|
||||
mxgateway apikey create-key --key-id area1.reader --display-name "Area 1 reader" --scopes invoke:read,metadata:read --read-subtree "Area1/*" --browse-subtree "Area1/*"
|
||||
mxgateway apikey create-key --key-id ops.temp --display-name "Temp contractor" --scopes invoke:read --expires 90d
|
||||
mxgateway apikey create-key --key-id team-a.svc --display-name "Team A service" --scopes session:open,invoke:read --dashboard-tags team-a
|
||||
mxgateway apikey create-key --key-id ops.audit --display-name "Audit window" --scopes metadata:read --expires 2027-01-01T00:00:00Z
|
||||
mxgateway apikey list-keys --json
|
||||
mxgateway apikey revoke-key --key-id ops.alice
|
||||
@@ -272,8 +273,20 @@ mxgateway apikey rotate-key --key-id ops.alice
|
||||
Constraint flags are optional. `--read-subtree`, `--write-subtree`,
|
||||
`--read-tag-glob`, `--write-tag-glob`, and `--browse-subtree` are repeatable.
|
||||
`--max-write-classification` accepts one integer. `--read-alarm-only` and
|
||||
`--read-historized-only` are boolean flags. Existing rows with null constraints
|
||||
remain fully unconstrained after migration.
|
||||
`--read-historized-only` are boolean flags. `--dashboard-tags` takes a
|
||||
comma-separated list (`--dashboard-tags team-a,team-b`) and is repeatable; its
|
||||
segments are trimmed and de-duplicated ordinal-ignore-case, and an empty segment
|
||||
is rejected rather than dropped so a stray comma cannot silently persist a grant
|
||||
the operator did not write. Existing rows with null constraints remain fully
|
||||
unconstrained after migration; rows written before `--dashboard-tags` existed
|
||||
deserialize as untagged, unchanged in every other respect.
|
||||
|
||||
`--dashboard-tags` is *not* a data-access constraint — it only labels the key for
|
||||
dashboard event visibility, and sessions the key opens inherit it. See
|
||||
[Authorization](./Authorization.md#constraint-enforcement).
|
||||
|
||||
`list-keys` prints the tags as a trailing tab-separated column (`-` when
|
||||
untagged); the values are operator-chosen labels, not key material.
|
||||
|
||||
Key ids are restricted by the parser to ASCII letters, digits, periods, and hyphens
|
||||
so they remain safe to embed in the token format and in URL paths used by
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user