Skip to content

[C-471] Session-policy KMS data-key grant for annotate connections - #219

Merged
dfrkp merged 5 commits into
mainfrom
feat/c-471-auto-provision-iam-roles
Sep 8, 2026
Merged

[C-471] Session-policy KMS data-key grant for annotate connections#219
dfrkp merged 5 commits into
mainfrom
feat/c-471-auto-provision-iam-roles

Conversation

@dfrkp

@dfrkp dfrkp commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Intent

The cytario-web tail of C-471/C-461. The admin portal (cytario/admin-portal#35) now grants kms:GenerateDataKey + kms:Decrypt (key-ARN-scoped) on annotate-and-above roles for SSE-KMS buckets — but the browser data plane mints credentials via AssumeRoleWithWebIdentity with an inline session policy, and STS applies that policy as an intersection filter, so a role-side Allow alone would still be filtered out of the session. This PR adds the matching kms:GenerateDataKey statement for annotate-level connections, so annotation sidecar writes (*.annotations.*.json PUTs) to SSE-KMS buckets can generate data keys. Spec: cytario/cytario-docs#163.

Changes

  • app/.server/auth/sessionPolicy.tsKmsGenerateDataKeyViaS3 is now emitted for every level that permits sidecar writes (annotate, joining the existing read-write/admin), keyed off permitsSidecarWrite instead of the prefix-write block. Statement shape unchanged (Resource: "*" + kms:ViaService condition — the intersection with the role's key-ARN-scoped Allow is what bounds it).
  • app/utils/bucketCatalog.schema.ts — bucket lookup row gains optional nullable kmsKeyArn (reported by the portal at bucket registration; optional so OSS YAML catalogs without it still parse).

Testing

  • sessionPolicy.test.ts: the test pinning annotate's omission now asserts presence (shape: Effect/Resource/ViaService); read-only still asserts omission; new size-ceiling regression check (annotate + 64-char prefix + 25-char bucket ≈ 880 chars, well under POLICY_SIZE_CEILING = 2048).
  • policySeparation.test.ts (ARCH-1 import-disjointness): passes unchanged — no shared imports added.
  • Typecheck passes. (The one pre-push vitest failure locally was the unrelated formatHumanReadableDate timezone test — passes under TZ=Europe/Berlin like CI.)

@dfrkp
dfrkp requested a review from a team as a code owner September 6, 2026 08:03
@cytario-plane

cytario-plane Bot commented Sep 6, 2026

Copy link
Copy Markdown

Linked to Plane Work Item(s)

References

This comment was auto-generated by Plane

1 similar comment
@cytario-plane

cytario-plane Bot commented Sep 6, 2026

Copy link
Copy Markdown

Linked to Plane Work Item(s)

References

This comment was auto-generated by Plane

The inline session policy now emits kms:GenerateDataKey (ViaService
s3.<region>) for annotate-level connections — annotation sidecar writes
to SSE-KMS buckets generate a data key, and the STS inline policy is an
intersection filter, so the statement was previously filtered out of
the session even where the role permits it. Read-only connections
still omit it. The bucket lookup schema gains the optional kmsKeyArn
the admin portal reports at bucket registration.
@dfrkp
dfrkp force-pushed the feat/c-471-auto-provision-iam-roles branch from de94e48 to cd20ebc Compare September 6, 2026 08:18
…[C-471]

The autosuggest effect overwrote the name input while it was focused when
the bucket selection resolved asynchronously (catalog arriving mid-fill):
React restored the suggested value between the editor's clear and insert,
so typed text landed appended to the suggestion. Skip the effect while
the name input holds focus.
…ction [C-471]

The session policy's kms:ViaService condition and the STS/S3 client
construction used the provider connection's region, but the condition
must name the bucket's own region (ViaService matches the service that
calls KMS on the bucket's behalf) — a bucket registered under a
differently-regioned connection got KMS scoping that never matched,
failing every SSE-KMS read. Resolve the bucket's region from the bucket
catalog (per-bucket rows already carry it), falling back to the
connection's region when the registry is unavailable or the build has
none (OSS). The browser data-plane provider projection ships the bucket's
region too.
The portal provisions exactly one storage role per (bucket, access
level), so a grant no longer stores a provider-role id. The connection
and share modals' per-grant selector becomes an access-level picker
(Read Only / Annotate / Read Write / Admin); the ConnectionGrant column
becomes accessLevel. Server-side, each grant's level resolves to the
concrete role for the connection's bucket (exact bucket row match when
the bucket catalog is available; connection+level otherwise — unambiguous
under the one-role-per-(bucket, level) invariant). The id-based
resolveConnectionProvider/findProviderRole are dead under the new
model and removed; the advisory level filtering keeps the scope-coverage
semantics via the backing role's allowedScopes.
Roles are identified by (provider connection, bucket, access level) —
exactly one is provisioned per (bucket, level) — and the level-based
grants reference only the level, so the payload's id and synthesized
display name have no consumer. The browser projection follows; the
OSS deploy-time YAML example mirrors the shape.
@dfrkp
dfrkp merged commit f83ade0 into main Sep 8, 2026
7 of 8 checks passed
@dfrkp
dfrkp deleted the feat/c-471-auto-provision-iam-roles branch September 8, 2026 08:31
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 6.15.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 6.4.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant