사용자 정의 역할

Ultimate 고객은 사용자 정의 역할을 생성하고 특정 능력을 할당하여 해당 역할을 정의할 수 있습니다.

예를 들어 사용자는 “엔지니어” 역할을 만들어 코드 읽기Merge Request 관리자 능력을 갖추게 할 수 있지만 이슈 관리자와 같은 능력은 없게 할 수 있습니다.

이 문맥에서 “권한”과 “능력”이라는 용어는 종종 서로 바꿔 사용됩니다.

  • “능력”은 사용자가 할 수 있는 작업입니다. 이것은 선언적 정책 능력에 매핑되어 있으며, ee/app/policies/*의 정책 클래스에서 관리됩니다.
  • “권한”은 능력을 사용자 지향 문서에서 참조하는 방식입니다. 권한에 대한 문서는 매뉴얼으로 생성되므로 문서에 나열된 권한과 정책 클래스에 정의된 능력 간에 1:1 매핑이 필요하지는 않습니다.

사용자 정의 역할 vs 기본 역할

GitLab 15.9 이전에는 GitLab에는 기본 역할만 있는 권한 시스템이 있었습니다. 이 시스템에서 특정 능력에 대해 정적으로 특정된 몇 가지 기본 역할이 있습니다. 이러한 기본 역할은 고객이 사용자 정의할 수 없습니다.

사용자 정의 역할로, 고객은 특정 사용자 그룹에 어떤 능력을 할당할지 결정할 수 있습니다. 예를 들어:

  • 기본 역할 시스템에서 취약성의 읽기 권한은 Developer 역할로 제한됩니다.
  • 사용자 정의 역할 시스템에서, 고객은 새로운 사용자 정의 역할에 이 능력을 어떤 기본 역할을 기반으로 할당할지 결정할 수 있습니다.

기본 역할과 마찬가지로, 사용자 정의 역할은 그룹 계층 구조 내에서 상속됩니다. 사용자가 그룹에 대해 사용자 정의 역할을 가지고 있으면 해당 사용자는 해당 그룹 내의 프로젝트 또는 하위 그룹에 대해 사용자 정의 역할을 가지게 됩니다.

기술적 개요

  • 개별 사용자 정의 역할은 member_roles 테이블에 저장됩니다 (MemberRole 모델).
  • member_roles 레코드는 namespace_id 외래 키를 통해 최상위 그룹에 연결됩니다 (하위 그룹이 아님).
  • 그룹 또는 프로젝트 멤버십 (members 레코드)은 member_role_id 외래 키를 통해 사용자 정의 역할과 연결됩니다.
  • 그룹 또는 프로젝트 멤버십은 해당 그룹이나 프로젝트의 루트 수준 그룹에서 정의된 사용자 정의 역할과 연결될 수 있습니다.
  • member_roles 테이블에는 개별 권한과 base_access_level 값이 포함됩니다.
  • base_access_level유효한 액세스 수준이어야 합니다. base_access_level은 사용자 정의 역할에 포함될 능력을 결정합니다. 예를 들어, base_access_level10이면 사용자 정의 역할에는 기본 Guest 역할이 받는 모든 능력과 read_code와 같은 추가적인 능력이 포함됩니다.
  • 사용자 정의 역할은 base_access_level에 대해 추가적인 능력을 활성화할 수 있지만 권한을 비활성화할 수는 없습니다. 결과적으로 사용자 정의 역할은 “추가적인 것만”입니다. 이 선택의 이유에 대한 근거는 이 코멘트에 있습니다.
  • 사용자 정의 역할 능력은 프로젝트 수준 및 그룹 수준에서 지원됩니다.

능력 리팩터링

기존 능력 확인

하나의 엔드포인트 또는 웹 요청에 대해 능력은 종종 여러 위치에서 확인됩니다. 따라서 특정 엔드포인트에 대해 실행되는 권한 확인 디렉터리을 찾는 것이 어려울 수 있습니다.

이를 돕기 위해 로컬에서 GITLAB_DEBUG_POLICIES=true를 설정할 수 있습니다.

이것은 실행한 스펙에서 수행된 요청에 대해 능력 확인에 대한 정보를 출력합니다. 출력에는 또한 권한 확인이 수행된 코드 라인도 포함됩니다. 호출자 정보는 특히 메타프로그래밍이 사용되는 경우에는 능력 이름 문자열을 grep으로 찾기가 어려운 경우에 도움이 됩니다.

예를 들어:

# 예시 스펙 실행

GITLAB_DEBUG_POLICIES=true bundle exec rspec spec/controllers/groups_controller_spec.rb:162

# 스펙 실행 시 권한 디버그 출력; 여러 정책 확인이 실행되면 모두 디버그 출력에 포함됩니다.

POLICY CHECK DEBUG -> policy: GlobalPolicy, ability: create_group, called_from: ["/gitlab/app/controllers/application_controller.rb:245:in `can?'", "/gitlab/app/controllers/groups_controller.rb:255:in `authorize_create_group!'"]

리팩터링 중에 권한 확인에 대해 더 많이 알아보기 위해 이 설정을 사용하세요. 기본 브랜치의 스펙에 대해이 설정을 유지하지 마십시오.

개별 능력 로직 이해

능력에 대한 참조는 DeclarativePolicy 클래스에서 여러 차례 나타날 수 있으며 다른 능력을 참조하는 조건 및 규칙에 의존할 수 있습니다. 결과적으로 특정 능력에 어떤 조건이 적용되는지 정확히 알아 내기 어려울 수 있습니다.

DeclarativePolicy는 각 정책 클래스에 대한 ability_map을 제공하며, 이를 사용하여 특정 능력에 대한 모든 규칙을 배열로 가져올 수 있습니다.

예를 들어:

> GroupPolicy.ability_map.map.select { |k,v| k == :read_group_member }
=> {:read_group_member=>[[:enable, #<Rule can?(:read_group)>], [:prevent, #<Rule ~can_read_group_member>]]}

> GroupPolicy.ability_map.map.select { |k,v| k == :read_group }
=> {:read_group=>
  [[:enable, #<Rule public_group>],
   [:enable, #<Rule logged_in_viewable>],
   [:enable, #<Rule guest>],
   [:enable, #<Rule admin>],
   [:enable, #<Rule has_projects>],
   [:enable, #<Rule read_package_registry_deploy_token>],
   [:enable, #<Rule write_package_registry_deploy_token>],
   [:prevent, #<Rule all?(~public_group, ~admin, user_banned_from_group)>],
   [:enable, #<Rule auditor>],
   [:prevent, #<Rule needs_new_sso_session>],
   [:prevent, #<Rule all?(ip_enforcement_prevents_access, ~owner, ~auditor)>]]}

DeclarativePolicy는 또한 특정 객체 및 액터에 대한 로직 트리를 이해하는 데 사용할 수 있는 debug 메서드를 제공합니다. 출력은 ability_map의 규칙 디렉터리과 유사합니다. 그러나 DeclarativePolicy는 능력을 prevent한 후에 규칙을 평가를 중단하므로 모든 조건이 호출되지 않을 수도 있습니다.

예시:

policy = GroupPolicy.new(User.last,  Group.last)
policy.debug(:read_group)

- [0] enable when public_group ((@custom_guest_user1 : Group/139))
- [0] enable when logged_in_viewable ((@custom_guest_user1 : Group/139))
- [0] enable when admin ((@custom_guest_user1 : Group/139))
- [0] enable when auditor ((@custom_guest_user1 : Group/139))
- [14] prevent when all?(~public_group, ~admin, user_banned_from_group) ((@custom_guest_user1 : Group/139))
- [14] prevent when needs_new_sso_session ((@custom_guest_user1 : Group/139))
- [16] enable when guest ((@custom_guest_user1 : Group/139))
- [16] enable when has_projects ((@custom_guest_user1 : Group/139))
- [16] enable when read_package_registry_deploy_token ((@custom_guest_user1 : Group/139))
- [16] enable when write_package_registry_deploy_token ((@custom_guest_user1 : Group/139))
  [21] prevent when all?(ip_enforcement_prevents_access, ~owner, ~auditor) ((@custom_guest_user1 : Group/139))

=> #<DeclarativePolicy::Runner::State:0x000000015c665050
 @called_conditions=
  #<Set: {
   "/dp/condition/GroupPolicy/public_group/Group:139",
   "/dp/condition/GroupPolicy/logged_in_viewable/User:83,Group:139",
   "/dp/condition/BasePolicy/admin/User:83",
   "/dp/condition/BasePolicy/auditor/User:83",
   "/dp/condition/GroupPolicy/user_banned_from_group/User:83,Group:139",
   "/dp/condition/GroupPolicy/needs_new_sso_session/User:83,Group:139",
   "/dp/condition/GroupPolicy/guest/User:83,Group:139",
   "/dp/condition/GroupPolicy/has_projects/User:83,Group:139",
   "/dp/condition/GroupPolicy/read_package_registry_deploy_token/User:83,Group:139",
   "/dp/condition/GroupPolicy/write_package_registry_deploy_token/User:83,Group:139"}>,
 @enabled=false,
 @prevented=true>

능력 통합

사용자 정의 역할에 추가된 모든 기능은 최소한의 능력을 가져야 합니다. 대부분의 기능에 대해 read_*admin_*이 있으면 충분해야 합니다. 모든 것을 통합해야 합니다:

  • read_* 아래에 뷰 관련 능력. 예를 들어, 디렉터리 또는 상세 정보 보기.
  • admin_* 아래에 객체 업데이트. 예를 들어, 객체 업데이트, 담당자 추가 또는 객체 닫기와 같은 작업. 일반적으로 admin_을 가능하게 하는 역할은 read_ 능력도 가능하도록 해야 합니다. 이는 MemberRole 모델의 ALL_CUSTOMIZABLE_PERMISSIONS 해시의 requirement 옵션에 정의되어 있습니다.

추가 능력이 필요한 기능이 있을 수 있지만, 이를 최소화하려고 노력해야 합니다. 항상 인증 및 권한 그룹 구성원들의 의견이나 도움을 요청할 수 있습니다.

여기에서 당신의 작업이 시작되어야 합니다. 당신이 작업하는 기능에 대한 모든 능력을 가져와서, 필요한 경우 read_, admin_, 또는 추가 능력으로 통합하세요.

GroupPolicyProjectPolicy 클래스의 많은 능력은 많은 중복 정책이 있습니다. 이와 관련된 이슈가 하나 있습니다. 이러한 클래스에서 비슷한 권한을 발견하면 동일한 이름을 가지도록 리팩터링을 고려하세요.

예를 들어, GroupPolicy에서 read_group_security_dashboard 라는 능력을 보고, ProjectPolicy에서 read_project_security_dashboard 라는 능력을 발견합니다. 둘 다 사용자 정의 가능하도록 만들고 싶습니다. 능력마다 member_roles 테이블에 행을 추가하는 대신, 이들을 read_security_dashboard로 이름을 변경하고 member_roles 테이블에read_security_dashboard를 추가하는 것을 고려해보세요. 부모 그룹에서 read_security_dashboard를 가능하게 하면 사용자 정의 역할이 해당 그룹의 보안 대시보드 및 해당 그룹의 각 프로젝트의 프로젝트 보안 대시보드에 액세스할 수 있습니다. 특정 프로젝트에서 동일한 권한을 가능하게하면 해당 프로젝트의 보안 대시보드에 액세스할 수 있게 됩니다.

사용자 정의 역할에 능력 지원 추가하는 방법

기존 능력을 추가하는 경우, 아래 작업을 완료하기 전에 별도의 Merge Request에서 기능을 위한 능력을 리팩터링 및 통합하는 것을 고려하세요.

단계 1. 구성 파일 생성

  • 새로운 능력에 대한 구성 파일를 생성하려면 ./ee/bin/custom-ability <ABILITY_NAME>을 실행하세요.
  • 이것은 ee/config/custom_abilities에 YAML 파일을 생성하며, 다음 스키마를 따릅니다:
필드 필수 설명
name 사용자 정의 능력을 설명하는 고유한 소문자 및 밑줄이 포함된 이름. 파일 이름과 일치해야 합니다.
description 사용자 정의 능력에 대한 사람이 읽을 수 있는 설명.
feature_category 기능 범주의 이름. 예를 들어, vulnerability_management.
introduced_by_issue 이 사용자 정의 능력 추가를 제안한 이슈 URL.
introduced_by_mr 이 사용자 정의 능력 추가를 추가한 MR URL.
milestone 이 사용자 정의 능력이 추가된 마일스톤.
group_ability 이 능력이 그룹 수준에서 확인되는지 여부를 나타내는 부울 값.
project_ability 이 능력이 프로젝트 수준에서 확인되는지 여부를 나타내는 부울 값.
requirements 아니요 이 능력이 의존하는 사용자 정의 권한 디렉터리. 예를 들어, admin_vulnerabilityread_vulnerability에 의존합니다. 없으면 []을 입력하세요.
available_from_access_level 아니요 해당 능력이 사용 가능한 사전 정의 역할의 액세스 수준(적용 가능한 경우)입니다. 능력에 대한 기본 액세스 수준 결정에 도움이 됩니다. 이것은 정보만으로, 사용자 정의 역할이 작동하는 방식에는 영향을 주지 않습니다.

단계 2: 스펙 파일 생성 및 유효성 검사 스키마 갱신

  • bundle exec rails generate gitlab:custom_roles:code --ability <ABILITY_NAME>를 실행하여 권한 검사 스키마 파일을 업데이트하고 빈 스펙 파일을 생성합니다.

단계 3: 정책 업데이트

  • 능력을 그룹 수준에서 확인하는 경우, 능력을 활성화하기 위한 GroupPolicy에 규칙을 추가합니다.
  • 예를 들어: 추가하려는 능력이 read_dependency인 경우, 다음과 같이ee/app/policies/ee/group_policy.rb를 업데이트합니다.
rule { custom_role_enables_read_dependency }.enable(:read_dependency)
  • 마찬가지로, 능력을 프로젝트 수준에서 확인하는 경우, ProjectPolicy에 규칙을 추가하여 능력을 활성화합니다.
  • 예를 들어: 추가하려는 능력이 read_dependency인 경우, ee/app/policies/ee/project_policy.rb를 업데이트합니다.
rule { custom_role_enables_read_dependency }.enable(:read_dependency)
  • 모든 능력을 양쪽 수준에서 활성화할 필요는 없습니다. 예를 들어 admin_terraform_state는 사용자가 프로젝트의 terraform 상태를 관리할 수 있도록 합니다. 이 기능은 그룹 수준이 아닌 프로젝트 수준에서만 활성화되어야 하므로 ee/app/policies/ee/project_policy.rb에서만 구성해야 합니다.

단계 4: 확인

  • SaaS 모드가 GITLAB_SIMULATE_SAAS=1로 활성화되었는지 확인합니다.
  • 그룹의 소유주인 경우, 설정 -> 역할 및 권한으로 이동합니다.
  • 새 역할을 선택하고 방금 생성한 사용자 정의 역할에 권한을 부여합니다.
  • 그룹의 관리 -> 구성원 페이지로 이동하여 구성원을 방금 만든 사용자 정의 역할에 할당합니다.
  • 다음으로 그 구성원으로 로그인하여 해당 사용자 정의 능력이 의도한 페이지에 액세스할 수 있는지 확인합니다.

단계 5: 스펙 추가

  • MemberRoles 팩토리에 능력을 특성으로 추가합니다. ee/spec/factories/member_roles.rb.
  • 사용자가 사용자 정의 능력을 할당받은 후 컨트롤러, REST API 엔드포인트 및 GraphQL API 엔드포인트에 성공적으로 액세스할 수 있는지 확인하는 테스트를 ee/spec/requests/custom_roles/<ABILITY_NAME>/request_spec.rb에 추가하세요.
  • 아래는 일반적으로 Rails 컨트롤러 엔드포인트를 테스트하기 위해 필요한 typcal 설정의 예입니다.
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project, :repository, :in_group) }
  let_it_be(:role) { create(:member_role, :guest, namespace: project.group, custom_permission: true) }
  let_it_be(:membership) { create(:project_member, :guest, member_role: role, user: user, project: project) }
  
  before do
    stub_licensed_features(custom_roles: true)
    
    sign_in(user)
  end
  
  describe MyController do
    describe '#show' do
      it 'allows access' do
        get my_controller_path(project)
        
        expect(response).to have_gitlab_http_status(:ok)
        expect(response).to render_template(:show)
      end
    end
  end
  • 아래는 일반적으로 GraphQL 뮤테이션을 테스트하기 위해 필요한 일반적인 설정의 예입니다.
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project, :repository, :in_group) }
  let_it_be(:role) { create(:member_role, :guest, namespace: project.group, custom_permission: true) }
  let_it_be(:membership) { create(:project_member, :guest, member_role: role, user: user, project: project) }
  
  before do
    stub_licensed_features(custom_roles: true)
    
    sign_in(user)
  end
  
  describe MyMutation do
    include GraphqlHelpers
    
    describe '#show' do
      let(:mutation) { graphql_mutation(:my_mutation) }
      
      it_behaves_like 'a working graphql query'
    end
  end
  • ProjectPolicy 및/또는 GroupPolicy에 대한 테스트를 추가하세요. 아래는 ProjectPolicy 관련 변경 사항을 테스트하기 위한 예입니다.
  context 'for a member role with read_dependency true' do
    let(:member_role_abilities) { { read_dependency: true } }
    let(:allowed_abilities) { [:read_dependency] }
    
    it_behaves_like 'custom roles abilities'
  end

단계 6: 문서 업데이트

  • bundle exec rake gitlab:custom_roles:compile_docs를 실행하여 사용자 정의 능력 디렉터리을 업데이트합니다.
  • bundle exec rake gitlab:graphql:compile_docs를 실행하여 GraphQL 문서를 업데이트합니다.

권한 상승 고려 사항

기본 역할은 일반적으로 해당 artifact와 상호 작용할 때 해당 artifact와 관련된 artifact의 생성 또는 관리를 허용하는 권한을 갖습니다. 예를 들어, 개발자가 프로젝트용 액세스 토큰을 만드는 경우, 해당 자격 증명에는 개발자 액세스가 인코딩되어 생성됩니다. 새로운 사용자 정의 권한이 생성될 때 GitLab artifact와 상호 작용할 때 권한 상승의 위험이 발생할 수 있으므로 적절한 보호장치나 기본 역할 확인이 추가되어야 합니다.

Seats 소모

게스트 역할을 갖는 새로운 사용자가 CUSTOMIZABLE_PERMISSIONS_EXEMPT_FROM_CONSUMING_SEAT 배열에 포함되지 않는 능력을 활성화하는 멤버 역할에 추가되는 경우, seat가 소비됩니다. 우리는 단순히 Ultimate 고객에 대해 “승격”된 능력을 갖는 게스트 사용자에 대해 비용을 청구하고자 합니다. 이는 네임스페이스 구독에 포함된 과금 사용자에만 적용됩니다. 이 주제에 대한 자세한 내용은 이 이슈에서 확인할 수 있습니다.