Implement update group secrets permission mutation
What does this MR do and why?
Related to #577341 (closed)
This MR implements the GraphQL mutation for updating/creating group-level secrets permissions. This allows group owners to grant users, roles, member roles, or groups the ability to manage secrets through the GitLab UI at the group level.
Important distinction: These permissions control who can manage secrets in the GitLab UI (create, read, update, delete secrets via the web interface). This is separate from CI/CD pipeline permissions that control which jobs can use secrets during execution.
This is the first part of work which implements the update mutation for group secrets permissions, following the same pattern established for project-level secrets permissions.
Technical Notes
Architecture & Design Decisions
1. Naming Convention: "Secrets" in Permission Names
The naming uses GroupSecretsPermission and ProjectSecretsPermission (instead of just GroupPermission or ProjectPermission) to clearly indicate that these permissions are specifically for controlling access to secrets within a group or project.
This distinction is important because:
- Groups and projects may have many other types of OpenBao permissions down the road. We focus on secrets here for now.
- These permissions specifically govern who can create, read, update, and delete secrets stored in the secrets manager through the GitLab UI
Also note the change to plural form of "Secrets". In some parts of the code, we still reference to a permission as secret_permission but I think it makes more sense to use the plural form because the permissions we are granting here applies to managing secrets as a whole and not a per-secret permission. We'll rename those references as we continue to build the other services and mutations.
2. Code Refactoring for Reusability
To support both project and group secrets permissions without duplication, this MR introduces several abstractions:
Model Layer (BaseSecretsPermission)
- Extracted common validation and permission logic from
SecretPermissionintoBaseSecretsPermission - Changed
projectattribute to genericresourceattribute to support both projects and groups - Subclasses (
ProjectSecretsPermissionandGroupSecretsPermission) implement resource-specific validation logic - This allows most behavior to work the same way - code references
resourceorresource_idinstead ofproject_id/group_id
Service Layer (SecretsPermissionsUpdateServiceHelpers)
- Created a shared concern containing the core permission update logic
- Both
ProjectSecretsPermissions::UpdateServiceandGroupSecretsPermissions::UpdateServiceinclude this concern - Each service only needs to define:
-
resource- returns the project or group -
client- returns the appropriate secrets manager client -
permission_class- returns the appropriate permission model class
-
GraphQL Layer (SecretsPermissionInterface)
- Used a Ruby concern (not a GraphQL interface) to share common fields between
GroupSecretsPermissionTypeandProjectSecretsPermissionType - Why a concern instead of GraphQL interface? There's no practical use case for polymorphic querying - group and project permissions are always queried in different scopes (group vs project context), so the simpler Ruby module approach is sufficient
PrincipalType Changes
- Changed
project_idtoresource_idin the principal type resolver - This generalization allows the same
PrincipalTypeto be used for both project and group permissions
3. Principal Eligibility Rules
Important: These rules determine which groups are eligible to be added as principals, not which groups automatically have access. Group owners must still explicitly grant permissions via the mutation.
The hierarchical validation checks provide flexibility for delegation:
For Group Permissions:
- Same group can be added as a principal
- Parent groups can be added (enables oversight - parent group members can manage child group secrets)
- Child groups can be added (enables delegation - child group members can manage parent group secrets)
- Groups that are shared with the target group can be added
For Project Permissions:
- Groups that own the project can be added
- Parent groups of the project's group can be added (enables oversight)
- Child groups of the project's group can be added (enables delegation)
- Groups that the project is explicitly shared with can be added
Why allow bidirectional eligibility? This provides owners with flexibility to delegate management both upward (parent groups managing child resources) and downward (child groups managing parent resources). Both project and group permissions follow the same bidirectional pattern for consistency.
4. Accepting group_path for Group Principals
When adding a Group as a principal, the mutation now accepts group_path (full path like my-org/sub-group) instead of just numeric id.
Why this change?
- The frontend dropdown doesn't have a proper endpoint to fetch all groups that are eligible to be principals
- Using
group_pathallows a simple text input where users manually enter the full group path - Validation happens on mutation submit
Migration strategy:
-
PrincipalInputTypenow accepts bothidandgroup_pathfor backwards compatibility - Only Group-type principals use
group_path; other principal types (User, Role, MemberRole) continue usingid - The mutation resolver validates and converts
group_pathtoidinternally - Future project permissions mutations will only accept
group_pathfor Group principals - After frontend migration, the
idfallback will be removed
5. Authorization
New permission: configure_group_secrets_permission
- Only available to group owners (not maintainers)
- Consistent with
configure_group_secrets_managerwhich is also owner-only - Maintainers get
read_group_secrets_managerfor read access - This matches the project-level pattern where owners configure and maintainers can read
Note: Project-level permissions mutations, queries, and services remain under the Permissions namespace for now. See "Follow-up Work" section below.
GraphQL Mutation
Example: Granting permission to a User
mutation {
groupSecretsPermissionUpdate(input: {
groupPath: "my-group"
principal: {
id: 123
type: USER
}
permissions: ["read", "create", "update", "delete"]
expiredAt: "2025-12-31"
}) {
secretsPermission {
group {
id
fullPath
}
principal {
id
type
}
permissions
grantedBy {
id
username
}
expiredAt
}
errors
}
}
Example: Granting permission to a Group (using group_path)
mutation {
groupSecretsPermissionUpdate(input: {
groupPath: "my-group"
principal: {
groupPath: "my-org/sub-group"
type: GROUP
}
permissions: ["read", "create", "update", "delete"]
}) {
secretsPermission {
group {
id
fullPath
}
principal {
id
type
}
permissions
grantedBy {
id
username
}
expiredAt
}
errors
}
}
Feature Flag
This mutation is gated behind the existing group_secrets_manager feature flag.
Database Changes
None - permissions are stored in OpenBao, not the database.
Follow-up Work
Group Secrets Permissions
-
Implement list/query mutation for group secrets permissions -
Implement delete mutation for group secrets permissions
Project Secrets Permissions Namespace Migration
Currently, project-level permissions mutations, queries, and services are under the Permissions namespace. To maintain consistency with the new GroupSecretsPermissions namespace, we need to migrate them to ProjectSecretsPermissions:
-
Backend Migration MR: Migrate existing services to
ProjectSecretsPermissionsnamespace- Move
SecretsManagement::Permissions::ListService→SecretsManagement::ProjectSecretsPermissions::ListService - Move
SecretsManagement::Permissions::DeleteService→SecretsManagement::ProjectSecretsPermissions::DeleteService - Update service references
- Move
-
GraphQL Migration MR: Create new mutations/queries under
ProjectSecretsPermissionsnamespace- Add new mutations:
projectSecretsPermissionUpdate,projectSecretsPermissionDelete - Add new queries for listing project secrets permissions
- New mutations will only accept
group_pathfor Group principals (noidfallback) - Keep old mutations/queries (
secretsPermissionUpdate,secretsPermissionDelete) for backward compatibility - Mark old mutations/queries as deprecated
- Add new mutations:
-
Frontend Migration: Update frontend to use new GraphQL mutations/queries with
group_path -
Cleanup MR: Remove deprecated mutations/queries and
idparameter fallback after frontend migration is complete