Secrets permissions graphql improvements

What does this MR do and why?

Resolves #582213

This MR introduces significant improvements to the secrets permissions GraphQL API by:

  1. Introducing actions field to replace permissions - better semantic clarity and type safety
  2. Merging create and update into single WRITE action - simplifies the permission model since both are required to create secrets, and "write" is more intuitive for users
  3. Creating new project secrets permissions endpoints under ProjectSecretsPermissions namespace for consistency with GroupSecretsPermissions
  4. Renaming payload keys from secret_permission to secrets_permission (plural) for consistency
  5. Maintaining backward compatibility with old endpoints during migration period

Important: 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.

Technical Notes

Architecture & Design Decisions

1. Actions vs Permissions

Why "actions" instead of "permissions"?

  • More descriptive of what users are actually doing with secrets (read, write, delete)
  • Avoids redundancy with the entity name (SecretsPermission.permissions is confusing)
  • Better semantic clarity for API consumers
  • Enables use of GraphQL enum for type safety

Old behavior:

  • Field name: permissions
  • Type: String (stringified array like "create,read,update,delete")
  • Values: ['create', 'update', 'read', 'delete'] (four separate permissions)

New behavior:

  • Field name: actions
  • Type: [SecretsManagementAction!]! (GraphQL enum array)
  • Values: [READ, WRITE, DELETE] (three actions, WRITE combines create+update)

2. Merging Create and Update into WRITE Action

Problem: Users currently need BOTH create AND update permissions to create a secret, which is unintuitive.

Solution: Merge them into a single WRITE action that grants both create and update capabilities. The name "WRITE" is more intuitive for users as it clearly indicates the ability to create and modify secrets.

Implementation:

  • Model layer: BaseSecretsPermission now uses actions attribute instead of permissions
  • Conversion methods:
    • to_capabilities: Converts WRITE action → ['create', 'update'] capabilities for OpenBao
    • set_actions_from_capabilities: Converts ['create', 'update'] capabilities → WRITE action
  • Internal handling: OpenBao still uses separate create and update capabilities, but the API surface uses the simpler WRITE action

3. New Project Secrets Permissions Endpoints

Created new mutations and queries under ProjectSecretsPermissions namespace:

New Mutations:

  • projectSecretsPermissionUpdate - Uses actions field, accepts group_path for Group principals
  • projectSecretsPermissionDelete - Uses secretsPermission response key (plural)

New Query:

  • projectSecretsPermissions - Returns array of ProjectSecretsPermissionType with actions field

New Types:

  • ProjectSecretsPermissionType - Uses actions field from SecretsPermissionInterface
  • SecretsManagementAction enum - READ, WRITE, DELETE

4. Backward Compatibility

Old endpoints remain functional during migration:

  • secretsPermissionUpdate and secretsPermissionDelete mutations still work
  • Old mutations accept permissions parameter and convert to actions internally
  • Old SecretPermissionType overrides actions field to provide backward-compatible permissions field (stringified)
  • Conversion logic: WRITE action expands back to ['create', 'update'] for old API

Migration path:

  1. This MR: New endpoints available, old endpoints still work
  2. Frontend migration: Update to use new endpoints
  3. Future cleanup MR: Remove old endpoints after frontend migration

5. Payload Key Consistency

Changed: secret_permissionsecrets_permission (plural, emphasis on "secrets")

This applies to all new mutations:

  • groupSecretsPermissionUpdate.secretsPermission
  • groupSecretsPermissionDelete.secretsPermission
  • projectSecretsPermissionUpdate.secretsPermission
  • projectSecretsPermissionDelete.secretsPermission

Old mutations keep secret_permission for backward compatibility.

6. Shared Helper for Principal Resolution

Created Helpers::PermissionPrincipalHelpers concern to share resolve_principal_id logic between group and project mutations. This handles the group_pathid conversion for Group principals.

Files Changed

New GraphQL Files:

  • ee/app/graphql/mutations/secrets_management/project_secrets_permissions/update.rb - New update mutation
  • ee/app/graphql/mutations/secrets_management/project_secrets_permissions/delete.rb - New delete mutation
  • ee/app/graphql/resolvers/secrets_management/project_secrets_permissions_resolver.rb - New list resolver
  • ee/app/graphql/types/secrets_management/permissions/action_enum.rb - Actions enum
  • ee/app/graphql/types/secrets_management/project_secrets_permission_type.rb - New type with actions
  • ee/app/graphql/mutations/secrets_management/helpers/permission_principal_helpers.rb - Shared principal resolution

Updated GraphQL Files:

  • ee/app/graphql/types/secrets_management/secrets_permission_interface.rb - Changed permissions to actions
  • ee/app/graphql/types/secrets_management/permissions/secret_permission_type.rb - Added backward-compatible permissions field
  • ee/app/graphql/mutations/secrets_management/group_secrets_permissions/update.rb - Uses actions, shared principal helper
  • ee/app/graphql/mutations/secrets_management/group_secrets_permissions/delete.rb - Updated payload key
  • ee/app/graphql/mutations/secrets_management/permissions/update.rb - Converts permissions to actions
  • ee/app/graphql/mutations/secrets_management/permissions/delete.rb - Updated payload key

Model Changes:

  • ee/app/models/secrets_management/base_secrets_permission.rb - Changed permissions to actions, added conversion methods

Service Changes:

  • ee/app/services/concerns/secrets_management/secrets_permissions/update_service_helpers.rb - Uses actions
  • ee/app/services/concerns/secrets_management/secrets_permissions/delete_service_helpers.rb - Updated method signature
  • ee/app/services/concerns/secrets_management/secrets_permissions/list_service_helpers.rb - Converts capabilities to actions

GraphQL Examples

New Project Secrets Permission Endpoints

Update Permission (New)

mutation {
  projectSecretsPermissionUpdate(input: {
    projectPath: "my-org/my-project"
    principal: {
      groupPath: "my-org/sub-group"  # For Group principals
      type: GROUP
    }
    actions: [READ, WRITE, DELETE]  # Note: WRITE replaces create+update
    expiredAt: "2025-12-31"
  }) {
    secretsPermission {  # Note: plural "secrets"
      project {
        fullPath
      }
      principal {
        id
        type
      }
      actions  # Returns: ["read", "write", "delete"]
      grantedBy {
        username
      }
      expiredAt
    }
    errors
  }
}

Delete Permission (New)

mutation {
  projectSecretsPermissionDelete(input: {
    projectPath: "my-org/my-project"
    principal: {
      id: 123
      type: USER
    }
  }) {
    secretsPermission {  # Note: plural "secrets"
      principal {
        id
        type
      }
    }
    errors
  }
}

List Permissions (New)

query {
  projectSecretsPermissions(projectPath: "my-org/my-project") {
    nodes {
      principal {
        id
        type
      }
      actions  # Returns: ["read", "write", "delete"]
      grantedBy {
        username
      }
      expiredAt
    }
  }
}

Old Endpoints (Backward Compatible)

The old secretsPermissionUpdate and secretsPermissionDelete mutations still work:

mutation {
  secretsPermissionUpdate(input: {
    projectPath: "my-org/my-project"
    principal: { id: 123, type: USER }
    permissions: ["read", "create", "update", "delete"]  # Old format still accepted
  }) {
    secretPermission {  # Old response key (singular)
      permissions  # Returns stringified: "create,delete,read,update"
    }
  }
}

Feature Flags

  • Group permissions: Gated behind group_secrets_manager feature flag
  • Project permissions: Gated behind secrets_manager feature flag

Database Changes

None - permissions are stored in OpenBao, not the database.

Migration Strategy

  1. This MR: New endpoints available with actions field, old endpoints remain functional
  2. Frontend migration (#582687): Update UI to use new endpoints
  3. Future cleanup MR: Remove old endpoints after frontend migration complete

Dependencies

Depends on:

  • !214500 (merged) - Add delete and list queries for group secrets permissions (merged)

Enables:

  • #582687 - Frontend: migrate to new secrets permissions endpoints

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Erick Bajao

Merge request reports

Loading