GraphQL wrapper in Rails for the IAM Relationships API (gRPC) - role assignments for Artifact Registry
## Why The closed-by-default role assignment model agreed for Artifact Registry (AR) / Organizations needs a way for clients to grant and read role assignments without proxying through the AR binaries. The Relationships API is planned to be exposed over **gRPC only**, authenticated with a short-lived JWT, and intended to be called from internal services ([note](https://gitlab.com/gitlab-org/gitlab/-/work_items/593455#note_3414879280)). Proxying role-assignment calls through AR would add latency for no added value ([note](https://gitlab.com/gitlab-org/gitlab/-/work_items/593455#note_3415640177)). Instead, GitLab Rails should expose a thin **GraphQL wrapper** over the gRPC Relationships API. This lets the (API-only, for 19.2 closed beta) role-assignment flow work for all users while keeping Rails as the place that mints the JWT. See discussion: [#593455 (note_3414941106)](https://gitlab.com/gitlab-org/gitlab/-/work_items/593455#note_3414941106), [#593455 (note_3415640177)](https://gitlab.com/gitlab-org/gitlab/-/work_items/593455#note_3415640177). ## What A GraphQL mutation/query layer in Rails that: - Wraps the gRPC calls to the IAM **Relationships API** (grant a role / lookup role assignments). - Accepts a normal user credential (e.g. PAT) on the GraphQL side and **internally swaps it for a short-lived JWT** that is attached as `Bearer` metadata on each gRPC call ([note](https://gitlab.com/gitlab-org/gitlab/-/work_items/593455#note_3414941106)). - Targets AR resource scopes (AR namespace and repository) for `(user, role, resource)` tuples. Out of scope: the user management UI, the role <> permission mapping, and the Relationships API service itself (tracked under the [Authentication team epic &21743](https://gitlab.com/groups/gitlab-org/-/work_items/21743), e.g. [Relationships API - Lookup + Update (#599076)](https://gitlab.com/gitlab-org/gitlab/-/work_items/599076) and [Auth for relationships API writes (#599078)](https://gitlab.com/gitlab-org/gitlab/-/work_items/599078)). ## How Follow the existing **KAS client** as the reference pattern: Rails mints a short-lived JWT and attaches it as `Bearer` metadata on each gRPC call ([lib/gitlab/kas/client.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/kas/client.rb)). Sketch of the layering: ``` GraphQL mutation (e.g. artifactRegistryRoleAssignmentCreate) │ user PAT / session ▼ Service / GraphQL resolver │ mint short-lived JWT (audience = relationships API) ▼ gRPC client wrapper (modeled on Gitlab::Kas::Client) │ metadata: { 'authorization' => "bearer <jwt>" } ▼ IAM Relationships API (gRPC) → grant_role / lookup ``` Illustrative client shape, mirroring the KAS client: ```ruby module Gitlab module Iam module Relationships class Client JWT_AUDIENCE = 'gitlab-iam-relationships' STUB_CLASSES = { role_assignment: Gitlab::Iam::Relationships::Rpc::RoleAssignmentService::Stub }.freeze def grant_role(principal:, role:, resource:) request = Gitlab::Iam::Relationships::Rpc::GrantRoleRequest.new( principal: principal, role: role, resource: resource ) stub_for(:role_assignment).grant_role(request, metadata: metadata) end def lookup(principal:, resource:) request = Gitlab::Iam::Relationships::Rpc::LookupRequest.new( principal: principal, resource: resource ) stub_for(:role_assignment).lookup(request, metadata: metadata).to_a end private def metadata { 'authorization' => "bearer #{token}" } end def token # short-lived JWT, audience = JWT_AUDIENCE end end end end end ``` The GraphQL types/mutations then call this client, translating the PAT-authenticated request into the JWT-authenticated gRPC call. ## Open questions - Exact gRPC contract / stub names from the [Relationships API protobuf (#599075)](https://gitlab.com/gitlab-org/gitlab/-/work_items/599075). - JWT audience, signing key, and TTL to align with [Auth for relationships API writes (#599078)](https://gitlab.com/gitlab-org/gitlab/-/work_items/599078). - Whether the same wrapper covers both write (grant) and read (lookup), or only writes for the closed beta.
issue