lablup / lablup/backend.ai

Implement project-scope RBAC for ContainerRegistry

Open
#11,565 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
670
Forks
183
Avg merge
15h 13m
Merged PRs (30d)
368

Description

## Background

The container_registries table can hold multiple rows that share the same (registry_name, project) tuple. There is no database-level uniqueness constraint on this combination, and the application layer has never enforced it either since the project column was introduced. The legacy GQL mutations used registry_name (hostname) alone as the lookup key and silently allowed duplicates, which causes orphaned image rows referencing dead registry rows after rescans.

The intuitive fix of adding a unique constraint on (registry_name, project) is unsuitable, because the long-term design intent is for the same (registry_name, project) to legitimately appear under different scopes (global, project, user) with separate credentials, ACLs, and visibility rules. A flat unique constraint would block that model.

## Objective

Introduce project-scope semantics into ContainerRegistry so that each project can own, manage, and search its own set of container registry records independently of the global scope. Replace the implicit 'every registry is global' model with explicit scope ownership, and expose project-scope search through the v2 API stack (REST/GQL/SDK/CLI) following the existing RBAC scope-search pattern already used by VFolder, Image, and Session.

## Rationale: Why Not a Database-Level Unique Constraint

- Each scope (global, project, user) must be able to own a distinct registry record for the same (registry_name, project) tuple, with its own credentials and visibility. A flat uniqueness constraint blocks this.
- Scope membership currently lives in a separate table (association_container_registries_groups), so a cross-table invariant cannot be expressed as a single-table UNIQUE constraint in PostgreSQL.
- Trigger-based or denormalization-based enforcement is brittle and adds maintenance cost. Materializing the scope into the container_registries row (scope_type, scope_id) is a cleaner direction and allows a single composite unique index per scope.

## Proposed Approach

- Reuse the existing RBAC scope infrastructure (SystemScope, ProjectScope, UserScope, PredefinedRole) defined in src/ai/backend/manager/models/rbac/.
- Add scope_type and scope_id columns to container_registries with a composite unique index on (registry_name, project, scope_type, scope_id). Backfill from is_global and association_container_registries_groups. Keep association_container_registries_groups as an additive sharing ACL rather than the source of ownership.
- Introduce ProjectContainerRegistrySearchScope in the repository layer following the VFolder pattern (repositories/vfolder/types.py).
- Add SearchProjectContainerRegistriesAction in the service layer, project_search() in the adapter, and project_container_registries resolvers in GQL v2 and REST v2 (/v2/container-registries/projects/{id}/search), following the admin_search vs scope_search separation described in api/CLAUDE.md.
- Mirror the new endpoints into the v2 SDK and CLI.

## Acceptance Criteria

- container_registries has scope_type and scope_id columns with a composite unique index that enforces (registry_name, project) uniqueness per scope at the database level.
- Existing rows are backfilled correctly: is_global=true becomes scope_type=GLOBAL; project-bound rows become scope_type=PROJECT with the appropriate scope_id.
- Project-scope search is available end to end: REST v2 endpoint, GQL Strawberry resolver, SDK v2 method, and CLI v2 command.
- Admin-only search continues to work and returns all registries regardless of scope.
- A non-admin user can only see registries belonging to their project scope or to the global scope, verified via integration tests against the live server.
- Creator enforces scope-aware deduplication so attempting to register the same (registry_name, project) within the same scope returns a domain error instead of silently inserting a duplicate.

## Further Consideration

Once project-scope RBAC is in place, the same pattern can be extended to user-scope ContainerRegistry, allowing individual users to own private registry records (for example, personal Harbor or ECR credentials). This is out of scope for this issue but is the natural follow-up, and the database schema landing here (scope_type, scope_id) should be designed to accept ScopeType.USER without further migration.

## Notes

- Significant schema change. Should be preceded by a BEP that captures scope semantics and the relationship between ownership (scope_type/scope_id) and additive sharing (association_container_registries_groups).
- Expected to land as roughly six PRs of 200 to 400 lines each: (1) partial UK hotfix for the global scope, (2) scope_type/scope_id schema migration, (3) repository scope-aware search, (4) service/action separation, (5) adapter + GQL v2 + REST v2, (6) SDK v2 + CLI v2.

JIRA Issue: BA-6011

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.