Privatelinks GraphQL#2944
Draft
jshearer wants to merge 4 commits into
Draft
Conversation
d41b7a5 to
e9736cb
Compare
4a99da9 to
6ab0520
Compare
…te/` and `ops/tasks/private/` prefixes The generic invite-link APIs (`createInviteLink`, `deleteInviteLink`, `inviteLinks`) should not let users manage or discover delegation under the platform-owned `ops/dp/private/<tenant>/` and `ops/tasks/private/<tenant>/` prefixes. Those grants are derived from the customer catalog prefix at data-plane provisioning time, so direct user-driven invite links there would bypass that ownership relationship. * createInviteLink and deleteInviteLink reject those prefixes before authorization, so even an explicit admin grant on an internal prefix cannot be used to mint or revoke a delegation link there. * inviteLinks filters those prefixes out of the listing query as defense-in-depth against directly-inserted rows or unexpected admin grants. * `ensure_private_data_plane_grants()` is unchanged; its sub-prefix `read` grants are a separate RLS workaround.
…async-graphql derives
The GraphQL API and the data-plane controller need to speak the same `PrivateLink` shape: DPC reads the `private_links json[]` column off `data_planes` and ships it into the est-dry-dock Pulumi stack, and the GraphQL surface needs both typed read and a typed input for the mutation that replaces the column. Defining the types once in `models` is the only way to share them without making DPC depend on the GraphQL crate.
* Move `PrivateLink`, `AWSPrivateLink`, `AzurePrivateLink`, `GCPPrivateServiceConnect` to `models::private_links`, re-export from `data-plane-controller::shared::stack` so existing DPC callers keep compiling unchanged.
* Add async-graphql derives under the existing `async-graphql` feature: `Union` + `OneofObject` on `PrivateLink`, `SimpleObject` + `InputObject` on each provider struct. Same Rust enum drives `union PrivateLink` on output and `input PrivateLinkInput @oneOf` on input, so the two GraphQL surfaces cannot drift.
* Azure `dns_name` and `resource_type` flip from `String`-with-empty-as-sentinel to `Option<String>` with a serde adapter that normalizes both an absent field and an empty string to `None` on read and omits both `None` and `Some("")` on write. Wire format byte-for-byte unchanged; the GraphQL surface exposes them as nullable instead of requiring clients to know the empty-string convention.
* AWS `service_region: Option<String>` carries the master-side change (commit b1d51ed) into the moved type, with `skip_serializing_if = "Option::is_none"` so the column stays absent (not `null`) when unset, preserving est-dry-dock's existing fallback to `region`.
…d `updateDataPlanePrivateLinks` mutation Exposes the configured private-link endpoints on the `dataPlanes` query as a typed union against `models::PrivateLink`, alongside the three provisioning-result endpoint columns (`awsLinkEndpoints`, `azureLinkEndpoints`, `gcpPscEndpoints`) as opaque JSON arrays. Adds a mutation that replaces the entire `private_links` column on a private data plane; the data-plane controller picks up the new configuration on its next poll and converges via Pulumi. * `dataPlanes` selects the raw `private_links` JSON once per page and parses each entry lazily in a `ComplexObject` resolver, so a malformed historical row produces a descriptive field-level error naming the data plane and the failing index rather than breaking the whole query selection. * `updateDataPlanePrivateLinks(dataPlaneName, privateLinks)` accepts `[PrivateLinkInput!]!` directly against the same `models::PrivateLink` types via async-graphql's `@oneOf` input; supplying multiple branches in a single element is rejected by GraphQL validation before the handler runs. * Name gating is a structural check: the name must sit under `ops/dp/private/<tenant>/...`. Anything more specific (cluster suffix shape, owning prefix shape) is the data plane's problem; an unknown name falls out as "not found" when the UPDATE matches zero rows. Per-provider validation is intentionally not duplicated here: the authoritative schema is the est-dry-dock Pydantic model on the consumer side, and a bad shape surfaces via DPC convergence. * Interim authorization requires `read` on the private data-plane name, mirroring the existing deployment mutation shape. This will move to `manage_dataplane` once the orthogonal capability model lands. * Returns `Boolean!` so the mutation surface is minimal; callers that need the post-write state re-query `dataPlanes`.
Adds two capability bits, `ViewDataPlanePrivateNetworking` and `ModifyDataPlanePrivateNetworking`, composed by a new `ManageDataPlane` bundle. The bundle is granted on the role-grant edge `<tenant>/ -> ops/dp/private/<tenant>/` at provisioning time, so tenant admins (whose `Admin` bundle composes `ManageDataPlane`) inherit both bits on their private data planes; non-admin tenant grantees intersect to their own bundle at the edge and never reach these bits.
* `private_links` resolver on `DataPlane` returns an empty list to callers that lack `ViewDataPlanePrivateNetworking` on the data plane name.
* `updateDataPlanePrivateLinks` mutation requires `ModifyDataPlanePrivateNetworking` (replacing the interim legacy `read` check).
* `evaluate_names_authorization` accepts any `Into<CapabilitySet> + Display + Copy`, letting call sites pass either legacy `models::Capability` or a single `models::authz::Capability` bit; legacy callers keep working unchanged via the `From<legacy>` impl.
* `models::authz::Capability` gets a `Display` impl that delegates to `Debug`, so error messages reference each bit by its Rust identifier.
* `create_data_plane.rs` stamps `bundles = '{manage_data_plane}'` on the new role grant. The accompanying two-step migration adds the enum value and backfills existing rows -- `alter type add value` cannot be used in the same transaction that subsequently references the new value.
6ab0520 to
55164df
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WIP