Skip to content

ProtectedBranch ignores UserID and GroupID in access levels #267

Description

@henrysachs

Description

The ProtectedBranch resource currently ignores UserID and GroupID fields in PushAccessLevels, MergeAccessLevels, and UnprotectAccessLevels, only using the AccessLevel field. This prevents users from configuring granular, user-specific or group-specific branch protection rules that GitLab's API supports.

Current Behavior

When creating a ProtectedBranch with user or group-specific access:

apiVersion: projects.gitlab.m.crossplane.io/v1alpha1
kind: ProtectedBranch
metadata:
  name: example-protected-branch
spec:
  forProvider:
    projectId: "123"
    branchName: "main"
    pushAccessLevels:
      - accessLevel: 40  # Maintainer level
      - userId: 456      # Specific user
      - groupId: 789     # Specific group
    mergeAccessLevels:
      - accessLevel: 30  # Developer level
      - groupId: 789     # Specific group

Result: Only the accessLevel is applied; userId and groupId are silently ignored.

Expected Behavior

All access level entries (including user-specific and group-specific rules) should be applied to the protected branch, allowing for granular access control as supported by GitLab's API.

Root Cause

In pkg/namespaced/clients/projects/protectedbranch.go (lines 174-188), the GenerateProtectRepositoryBranchesOptions function only extracts the first element from each access level array and only uses the AccessLevel field:

if len(p.PushAccessLevels) > 0 && p.PushAccessLevels[0].AccessLevel != nil {
    accessLevel := gitlab.AccessLevelValue(*p.PushAccessLevels[0].AccessLevel)
    opt.PushAccessLevel = &accessLevel  // ← Only uses AccessLevel from first element
}
// UserID and GroupID are never used

The same pattern applies to MergeAccessLevels and UnprotectAccessLevels.

Use Cases Being Blocked

  1. CI/CD Bot Access: Allow only a specific CI/CD bot user to push to protected branches
  2. Release Team: Restrict push access to a specific "Release Team" group
  3. Mixed Permissions: Combine role-based, user-specific, and group-specific access rules
  4. Security Teams: Allow only security team members to modify certain branches

Proposed Solution

Update the client code to properly handle all elements in the access level arrays and include UserID and GroupID:

// Pseudocode - proper implementation would use GitLab client-go's actual structs
opt.AllowedToPush = []gitlab.BranchAccessDescription{}
for _, level := range p.PushAccessLevels {
    access := gitlab.BranchAccessDescription{}
    if level.AccessLevel != nil {
        access.AccessLevel = level.AccessLevel
    }
    if level.UserID != nil {
        access.UserID = level.UserID
    }
    if level.GroupID != nil {
        access.GroupID = level.GroupID
    }
    opt.AllowedToPush = append(opt.AllowedToPush, access)
}
// Similar for MergeAccessLevels and UnprotectAccessLevels

Files Affected

  • pkg/namespaced/clients/projects/protectedbranch.go - Lines 159-190
  • pkg/cluster/clients/projects/zz_protectedbranch.go - Similar pattern

Additional Context

The CRD types correctly define these fields as *int64:

  • apis/namespaced/projects/v1alpha1/protectedbranch_types.go:42 - UserID *int64
  • apis/namespaced/projects/v1alpha1/protectedbranch_types.go:46 - GroupID *int64

This is a pre-existing limitation, not introduced by recent changes. The types are correct; only the client implementation needs updating.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions