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