REST API 구현 가이드
침해된 개인 액세스 토큰(PAT)의 보안 영향을 줄이기 위해, 세분화된 PAT를 사용하면 특정 조직 경계(그룹, 프로젝트, 사용자 또는 인스턴스 수준)에 제한된 세밀한 권한으로 토큰을 생성할 수 있습니다. 세분화된 PAT는 경계와 특정 리소스 권한으로 구성된 세분화된 범위를 통해 세밀한 액세스 제어를 허용합니다.
침해된 개인 액세스 토큰(PAT)의 보안 영향을 줄이기 위해, 세분화된 PAT를 사용하면 특정 조직 경계(그룹, 프로젝트, 사용자 또는 인스턴스 수준)에 제한된 세밀한 권한으로 토큰을 생성할 수 있습니다. 이를 통해 사용자는 토큰에 필요한 권한만 부여하는 최소 권한 원칙을 따를 수 있습니다.
세분화된 PAT는 경계와 특정 리소스 권한으로 구성된 세분화된 범위를 통해 세밀한 액세스 제어를 허용합니다. 세분화된 PAT로 API 요청을 인증할 때, GitLab은 토큰의 권한이 지정된 경계 수준에서 요청된 리소스에 대한 액세스를 포함하는지 검증합니다.
이 문서는 REST API 엔드포인트를 세분화된 PAT 인증과 호환되도록 만들고자 하는 커뮤니티 기여자와 GitLab 개발자를 위해 설계되었습니다.
단계별 구현 가이드#
이 가이드는 REST API 엔드포인트에 세분화된 PAT 인증을 추가하는 방법을 안내합니다. 시작하기 전에 전체에서 사용되는 용어를 이해하기 위해 권한 명명 규칙 문서를 검토하십시오.
이 단계는 REST API 엔드포인트만 다룹니다. GraphQL 쿼리 및 변이에 대한 지원 추가는 GraphQL 구현 가이드를 참조하십시오.
워크플로 개요#
구현은 다음 흐름을 따릅니다:
- 1-2단계: 계획 - 엔드포인트 식별 및 권한 설계
- 3단계: 원시 권한 생성(YAML 파일)
- 4단계: 원시 권한을 할당 가능한 권한으로 번들화(YAML 파일)
- 5단계: 엔드포인트에 인증 데코레이터 추가(Ruby 코드)
- 6단계: 인증 테스트 작성(Ruby 사양)
- 7단계: 로컬 테스트(수동 검증)
각 단계에서 생성되는 파일#
각 단계에서 생성하는 파일을 보여주는 빠른 참조:
| 단계 | 파일 유형 | 위치 | 수량 | 예시 |
|---|---|---|---|---|
| 2 | 계획 문서 | (메모) | — | 권한 이름 식별 |
| 3 | 원시 권한 YAML | config/authz/permissions/<resource>/<action>.yml |
권한당 1개 | config/authz/permissions/job/read.yml |
| 3 | 원시 권한 리소스 메타데이터 | config/authz/permissions/<resource>/.metadata.yml |
리소스당 1개 | config/authz/permissions/job/.metadata.yml |
| 4 | 할당 가능한 권한 YAML | config/authz/permission_groups/assignable_permissions/<category>/<resource>/<action>.yml |
그룹당 1개 | config/authz/permission_groups/assignable_permissions/ci_cd/job/run.yml |
| 4(선택) | 범주 메타데이터 | config/authz/permission_groups/assignable_permissions/<category>/.metadata.yml |
범주당 0 또는 1개 | config/authz/permission_groups/assignable_permissions/ci_cd/.metadata.yml |
| 4 | 리소스 메타데이터 | config/authz/permission_groups/assignable_permissions/<category>/<resource>/.metadata.yml |
리소스당 1개 | config/authz/permission_groups/assignable_permissions/ci_cd/job/.metadata.yml |
| 5 | Grape 데코레이터 | lib/api/<resource>.rb 수정 |
엔드포인트당 1개 | route_setting :authorization 추가됨 |
| 6 | RSpec 테스트 | spec/requests/api/<resource>_spec.rb 수정 |
엔드포인트당 1개 | it_behaves_like 'authorizing...' 추가됨 |
1단계: 리소스에 대한 REST API 엔드포인트 식별#
목표: 작업하는 리소스에 대한 모든 REST API 엔드포인트를 찾습니다.
-
lib/api/<resource_name>.rb에서 리소스에 대한 API 파일을 찾습니다.예시: jobs 리소스의 경우
lib/api/ci/jobs.rb열기팁:
- 일부 리소스는 여러 API 파일에 걸쳐 엔드포인트가 분산되어 있을 수 있습니다(예: 중첩된 리소스)
resources :resource_name do블록에서 중첩된 엔드포인트를 정의하는 것을 확인하십시오- 라우터를 살펴보아 리소스에 대한 엔드포인트의 전체 범위를 이해하십시오
-
파일에서 모든 HTTP 메서드/라우트 쌍을 식별합니다. HTTP 동사와 함께 각 엔드포인트를 문서화합니다:
get ':id/jobs' get ':id/jobs/:job_id' post ':id/jobs/:job_id/cancel' post ':id/jobs/:job_id/retry' delete ':id/jobs/:job_id/artifacts' -
이미 인증 데코레이터(
route_setting :authorization)가 있는 엔드포인트를 확인합니다. 다음을 수행해야 합니다:- 데코레이터가 없는 엔드포인트에 데코레이터 추가
- 불완전하거나 잘못된 권한이 있는 엔드포인트의 데코레이터 업데이트
이러한 엔드포인트는 다음 단계에서 생성할 원시 권한의 기준이 됩니다. 각 고유한 작업(HTTP 동사 + 라우트)에는 일반적으로 자체 권한이 필요합니다.
2단계: 필요한 권한 결정#
목표: GitLab 명명 규칙에 따라 세분화된 권한을 정의합니다.
명명 규칙은 규칙 문서의 권한 명명을 참조하십시오.
엔드포인트에 대한 리소스 이름 결정#
세분화된 PAT 인증을 구현할 때 엔드포인트가 수정하거나 반환하는 내용에 따라 권한 이름을 지정하며, 라우트 구조가 아닙니다.
예시:
- 엔드포인트
DELETE /projects/:id/jobs/:job_id/artifacts→artifacts를 수정 → 권한 이름은delete_job_artifact - 엔드포인트
GET /projects/:id/issues→issues를 반환 → 권한 이름은read_issue - 엔드포인트
POST /projects/:id/jobs/:job_id/cancel→job상태를 수정 → 권한 이름은cancel_job
일반적인 패턴#
- 목록 및 표시 작업: 두 작업에 단일
read_resource권한 사용GET /projects/:id/jobs→read_jobGET /projects/:id/jobs/:job_id→read_job
- 중첩된 리소스: 권한 이름에 상위 리소스 포함
POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/variables→create_pipeline_schedule_variable
- 특수 작업: 고유한 작업에 대한 특정 권한 생성
- 취소, 재시도, 다운로드, 트리거 등 각각 자체 권한 보유
- 속성 업데이트: 모든 속성을 포함하는 단일 업데이트 권한 사용
update_issue는 제목, 설명, 담당자 업데이트 등을 포함update_issue_description,update_issue_title을 만들지 마십시오
3단계: 권한 정의 파일 생성#
목표: 아직 존재하지 않는 경우 각 권한에 대한 YAML 정의 파일을 생성합니다.
bin/permission 명령을 사용하여 원시 권한 YAML 파일을 생성하는 방법은 권한 정의 파일 섹션의 지침을 따르십시오.
4단계: 할당 가능한 권한 생성#
목표: 더 간단한 사용자 경험을 위해 관련 원시 권한을 번들링하는 할당 가능한 권한을 생성합니다.
할당 가능한 권한 YAML 파일을 생성하는 방법은 할당 가능한 권한 섹션의 지침을 따르십시오.
5단계: API 엔드포인트에 인증 데코레이터 추가#
각 엔드포인트에 대해 라우트 정의 바로 앞에 route_setting :authorization 데코레이터를 추가합니다:
route_setting :authorization, permissions: :read_job, boundary_type: :project
get ':id/jobs' do
# 엔드포인트 구현
end
데코레이터 옵션#
| 옵션 | 설명 |
|---|---|
permissions |
이 엔드포인트에 필요한 권한(기호 또는 기호 배열) |
boundary_type |
단일 경계 엔드포인트의 경계 유형: :project, :group, :user 또는 :instance |
boundary_param |
선택 사항. 경계 식별자를 포함하는 요청 매개변수. 프로젝트의 경우 :id, 그룹의 경우 :id 또는 :group_id가 기본값 |
boundaries |
여러 경계를 지원하는 엔드포인트에 대해 boundary_type 대신 사용(아래 참조) |
boundary |
표준 매개변수 조회를 통해 경계를 결정할 수 없는 엔드포인트에 대해 boundary_type 대신 사용. 경계 개체를 반환하는 호출 가능한 개체(proc, lambda, 또는 method) |
skip_granular_token_authorization |
선택 사항. true로 설정하면 세분화된 PAT가 특정 권한 없이 엔드포인트에 액세스할 수 있습니다(아래 참조) |
사용자 지정 boundary_param 예시:
route_setting :authorization, permissions: :read_job, boundary_type: :project, boundary_param: :project_id
get 'jobs' do
# 엔드포인트는 params[:id] 대신 params[:project_id]를 사용
end
boundary 사용 예시:
def registry
::VirtualRegistries::Packages::Maven::Registry.find(params[:id])
end
route_setting :authorization, permissions: :download_maven_package_file, boundary: -> { registry.group }, boundary_type: :group
get '/api/v4/virtual_registries/packages/maven/:id/*path' do
# `params`를 통해 경계를 결정할 수 없습니다. 대신 엔드포인트의
# 매개변수에서 ID를 사용하여 가져온 개체(registry)에서 결정됩니다.
end
엔드포인트당 여러 경계#
일부 엔드포인트는 여러 경계 유형을 지원해야 할 수 있습니다. 예를 들어, 가져오기 엔드포인트는 그룹 네임스페이스로 가져올 때 그룹 수준에서 작동하거나 개인 네임스페이스로 가져올 때 사용자 수준에서 작동할 수 있습니다. 이러한 경우 boundary_type 또는 boundary 대신 boundaries 옵션을 사용합니다:
route_setting :authorization, permissions: :create_bitbucket_import,
boundaries: [{ boundary_type: :group, boundary_param: :target_namespace }, { boundary_type: :user }]
post 'import/bitbucket' do
# 엔드포인트 구현
end
여러 경계가 정의된 경우:
- 시스템은 우선순위 순서로 경계를 평가합니다:
project>group>user>instance - 해결될 수 있는 첫 번째 경계(사용 가능한 매개변수를 기반으로)가 인증에 사용됩니다
- 배열의 각 경계에는
boundary_type키가 필요하고 선택적으로 경계 식별자를 포함하는 요청 매개변수를 지정하는boundary_param키가 필요합니다
세분화된 토큰 인증 건너뛰기#
일부 엔드포인트는 인증이 필요하지 않고 공개적으로 액세스 가능하거나 토큰 인증을 구현하지 않습니다. 이러한 엔드포인트에서는 토큰 인증이 건너뛰어지므로 세분화된 권한을 정의하는 것이 의미가 없습니다. 그러나 모든 엔드포인트에 대한 적용 범위 추적을 유지하려면 skip_granular_token_authorization 옵션을 사용하십시오:
route_setting :authorization, skip_granular_token_authorization: true
get 'public-endpoint' do
# 엔드포인트 구현
end
skip_granular_token_authorization 사용 시기:
- 인증이 필요 없는 공개 엔드포인트
- 개인 액세스 토큰 이외의 수단으로 인증하는 엔드포인트
- 인증 없이 액세스할 수 있는 검색 또는 메타데이터 엔드포인트
- 인증이 선택 사항이고 응답이 동일한 엔드포인트
이 데코레이터를 추가하면 권한이 필요하지 않은 엔드포인트도 인증 시스템에 의해 명시적으로 적용됩니다.
중요 사항:
- 여러 엔드포인트가 동일한 권한을 사용하더라도 모든 엔드포인트에 개별적으로 데코레이터를 추가하십시오
- 데코레이터는 HTTP 메서드 정의(
get,post,put,delete) 바로 앞에 위치해야 합니다 - YAML 파일에 정의된 정확한 권한 이름(기호)을 사용하십시오
- 단일 경계 엔드포인트에는
boundary_type또는boundary를 사용하고, 다중 경계 엔드포인트에는boundaries배열을 사용하십시오 - 실제로 권한 확인이 필요하지 않은 엔드포인트에만 드물게
skip_granular_token_authorization: true를 사용하십시오
공개적으로 표시되는 리소스에 대한 접근 허용#
config/authz/roles/public_anonymous.yml에 나열된 권한은 대상 프로젝트 또는 그룹이 공개적으로 표시되고 관련 기능이 활성화된 경우, 명시적인 범위 없이 세분화된 PAT에 부여됩니다. 이는 동일한 리소스에 대해 익명 호출자가 가지는 액세스를 반영합니다.
권한을 이 동작에 옵트인하려면 config/authz/roles/public_anonymous.yml의 해당 경계(project: 또는 group:) 아래에 추가합니다:
project:
permissions: []
raw_permissions:
- read_release
group:
permissions: []
raw_permissions:
- read_subgroup
옵트인 시기:
-
권한이 공개 프로젝트 또는 그룹의 익명 호출자에게 이미 표시되는 데이터에 대한 읽기 전용 액세스를 나타내는 경우.
-
권한이
ProjectFeature(예:repository,issues)로 제어되어 기능을 비활성화하면 여전히 액세스가 차단되는 경우.
이 우회는 :project 및 :group 경계로 제한됩니다. :user 및 :instance 경계는 public_anonymous.yml을 참조하지 않습니다.
6단계: 인증 테스트 추가#
목표: 세분화된 PAT 권한이 엔드포인트에서 올바르게 적용되는지 확인합니다.
테스트 파일은 일반적으로 spec/requests/api/<resource>_spec.rb에 있습니다. 찾을 수 없는 경우 관련 사양 파일을 더 찾아봐야 할 수 있습니다.
이 테스트의 목적: 이 테스트는 다음을 확인합니다:
- 레거시(비세분화된) 개인 액세스 토큰이 엔드포인트에 대한 액세스를 계속 부여함
- 세분화된 PAT에서 필요한 권한이 부여된 사용자는 액세스가 허용됨
- 필요한 권한이 없는 사용자는 403 Forbidden 응답과 적절한 오류 메시지(
insufficient_granular_scope)로 액세스가 거부됨 - 인증 시스템이 엔드포인트의 권한 요구사항에 대해 세분화된 범위를 올바르게 평가
- 기능 플래그
granular_personal_access_tokens가 적절하게 적용됨(비활성화될 때 액세스 거부) :project및:group경계의 REST 엔드포인트에서 범위 없는 세분화된 PAT는 익명 호출자가 동일한 엔드포인트에 액세스할 수 있을 때마다 액세스를 허용합니다(공개 리소스 우회)
각 엔드포인트에 공유 예시 추가#
각 엔드포인트에 'authorizing granular token permissions' 공유 예시를 추가합니다. 이것은 인증 동작을 검증하는 재사용 가능한 테스트 도우미입니다:
it_behaves_like 'authorizing granular token permissions', :<permission_name> do
let(:boundary_object) { <boundary_object> }
let(:user) { <user> }
let(:request) do
<http_method> api("<endpoint_path>", personal_access_token: pat), params: <params_if_needed>
end
end
경계 개체 매핑#
boundary_object는 boundary_type과 일치해야 합니다:
| 경계 유형 | 경계 개체 |
|---|---|
:project |
project |
:group |
group |
:user |
:user |
:instance |
:instance |
중요: 경계 개체가 :project 또는 :group인 경우, 인증이 부여되려면 user가 해당 네임스페이스(프로젝트 또는 그룹)의 멤버여야 합니다.
7단계: 수동 검증#
목표: 머지 요청을 생성하기 전에 권한이 예상대로 작동하는지 확인하기 위해 로컬 환경에서 구현을 수동으로 테스트합니다.
전체 테스트 스위트를 실행하기 전에 Rails 콘솔에서 엔드포인트와 권한을 테스트하려면 이를 사용하십시오.
설정:
Rails 콘솔에서 사용자에 대한 세분화된 PAT를 생성하고 토큰으로 엔드포인트를 테스트하기 위한 URL을 복사합니다:
# 기능 플래그 활성화
Feature.enable(:granular_personal_access_tokens)
user = User.human.first
# 세분화된 토큰 생성
token = PersonalAccessTokens::CreateService.new(
current_user: user,
target_user: user,
organization_id: user.organization_id,
params: { expires_at: 1.month.from_now, scopes: ['granular'], granular: true, name: 'gPAT' }
).execute[:personal_access_token]
# 적절한 경계 개체 가져오기(project, group, :user, 또는 :instance)
project = user.projects.first
boundary = Authz::Boundary.for(project)
# 테스트 중인 권한으로 범위 생성(:read_job을 권한으로 대체)
scope = Authz::GranularScope.new(namespace: boundary.namespace, access: boundary.access, permissions: [:read_job])
# 토큰에 범위 추가
Authz::GranularScopeService.new(token).add_granular_scopes(scope)
# 토큰으로 API 엔드포인트 URL 복사(엔드포인트로 대체)
IO.popen('pbcopy', 'w') { |f| f.puts "curl \"http://#{Gitlab.host_with_port}/api/v4/projects/#{project.id}/jobs\" --request GET --header \"PRIVATE-TOKEN: #{token.token}\"" }
- 다른 터미널에 URL을 붙여넣으십시오. 성공해야 합니다.
