인증 코드 리뷰 가이드라인
이 페이지는 정책 변경, 권한 정의 및 인증 로직과 관련된 머지 요청을 리뷰를 위해 준비하는 방법에 대한 Govern:Authorization 팀의 지침을 제공합니다. 역할 정의 YAML 파일 (config/authz/roles/*.yml)은 각 역할이 가지는 권한에 대한 단일 진실의 원천입니다.
이 페이지는 정책 변경, 권한 정의 및 인증 로직과 관련된 머지 요청을 리뷰를 위해 준비하는 방법에 대한 Govern:Authorization 팀의 지침을 제공합니다.
역할 YAML 파일이 진실의 원천입니다#
역할 정의 YAML 파일 (config/authz/roles/*.yml)은 각 역할이 가지는 권한에 대한 단일 진실의 원천입니다. 정책 파일에는 역할 조건에 기반하여 권한을 부여하는 enable 규칙이 포함되어서는 안 됩니다. 대신, 권한을 적절한 역할 YAML 파일에 추가하고 기능 또는 설정이 사용 불가능할 때 액세스를 제한하기 위해 정책에서 prevent 규칙을 사용하십시오.
# 나쁜 예 - 정책 파일에서 역할에 대한 권한 활성화
rule { developer & model_registry_enabled }.policy do
enable :write_model_registry
end
# 좋은 예 - 권한이 developer 역할 YAML에 있음
# (config/authz/roles/developer.yml)
# raw_permissions:
# - write_model_registry
#
# 정책은 기능이 사용 불가능할 때만 제한:
rule { ~model_registry_enabled }.prevent :write_model_registry
이 패턴은 역할 권한을 다음과 같이 만듭니다:
-
기계 판독 가능: GATE와 같은 외부 시스템이 정책 로직을 평가하지 않고도 역할의 권한을 결정할 수 있습니다.
-
열거 가능: 하나의 YAML 파일을 읽어서 역할이 가지는 모든 권한을 볼 수 있습니다.
-
예측 가능: 역할은 항상 전체 권한 집합으로 시작합니다. 조건은 액세스를 제거하기만 하며, 확장하지 않습니다.
파일 구성#
동일한 조건에 대한 모든 prevent 규칙은 하나의 .policy 블록에 있어야 하며, 파일 전체에 분산되어서는 안 됩니다.
# 나쁜 예 - 동일한 조건에 대한 prevent가 파일 전체에 분산됨
rule { ~security_dashboard_enabled }.prevent :read_vulnerability
rule { ~security_dashboard_enabled }.prevent :admin_vulnerability
# 좋은 예 - 동일한 조건에 대한 모든 prevent가 함께 그룹화됨
rule { ~security_dashboard_enabled }.policy do
prevent :admin_vulnerability
prevent :read_vulnerability
end
안티패턴#
기본 정책에서 권한을 활성화하지 마십시오#
BasePolicy는 모든 다른 정책에 의해 상속되므로, 여기서 활성화된 모든 권한은 시스템의 모든 개체에서 암묵적으로 사용 가능합니다. 권한이 어떤 리소스에 대해 인증되는지에 대한 제약이 없으므로 이는 모호성과 보안 위험을 만듭니다.
동적 권한 정의 피하기#
동적으로 정의된 권한은 코드베이스에서 추적하기 어렵습니다. 명시적으로 선언되지 않고 런타임에 생성된 권한은 권한 이름 검색 시 결과가 없어 이름 변경이나 제거가 완료되었는지 확인이 불가능합니다.
# 나쁜 예 - 권한 이름이 동적으로 구성되어 검색할 수 없으며,
# 실제로 어디에서도 사용되지 않는 권한을 활성화/방지할 수 있습니다.
readonly_features.each do |feature|
prevent :"create_#{feature}"
prevent :"update_#{feature}"
prevent :"admin_#{feature}"
end
# 좋은 예 - 각 방지가 명시적으로 선언됨
rule { read_only }.policy do
prevent :create_issue
prevent :update_issue
prevent :admin_issue
# ... 권한당 한 줄
end
조건에서 잘못된 :scope 사용 피하기#
모든 condition은 캐시됩니다. :scope 옵션은 DeclarativePolicy에 캐시 키가 무엇인지 알려줍니다 — 잘못 설정하면 캐시된 결과가 너무 광범위하게 공유되어 한 사용자의 결과가 다른 컨텍스트로 유출되는 버그가 발생합니다.
규칙은 다음과 같습니다:
scope: :user는 조건이 사용자 데이터만 읽는 경우에만 사용 — 주제 데이터 없음.scope: :subject는 조건이 주제 데이터만 읽는 경우에만 사용 — 사용자 데이터 없음.scope: :global은 조건이 사용자나 주제 데이터가 필요 없는 경우에만 사용.- 조건이 사용자와 주제 데이터 모두를 읽는 경우
:scope를 생략(기본값).
참조: DeclarativePolicy 캐시 공유 범위
# 나쁜 예 - scope: :user는 결과가 사용자별로 캐시되어 모든 주제에서 공유되지만,
# 조건이 @subject를 읽으므로 다른 프로젝트가 잘못된 캐시 결과를 가져옵니다
condition(:security_dashboard_enabled, scope: :user) do
@subject.security_dashboard_enabled?
end
# 좋은 예 - 주제 데이터만 읽으므로 scope: :subject가 올바름
condition(:security_dashboard_enabled, scope: :subject) do
@subject.security_dashboard_enabled?
end
# 좋은 예 - 사용자 데이터만 읽으므로 scope: :user가 올바름
condition(:admin_user, scope: :user) do
@user.admin?
end
# 좋은 예 - 사용자와 주제 모두 읽으므로 범위가 선언되지 않음
condition(:member_with_access) do
@subject.member?(@user)
end
# 좋은 예 - 사용자나 주제가 필요 없으므로 scope: :global이 올바름
condition(:default_project_deletion_protection, scope: :global) do
::Gitlab::CurrentSettings.current_application_settings
.default_project_deletion_protection
end
예시 수정: MR !224604
중간 ability를 통한 권한 연쇄 피하기#
중간 ability를 통한 권한 연쇄를 피하십시오. 예를 들어 read_security_resource가 read_vulnerability를 활성화하는 경우입니다. 연쇄는 여러 단계의 간접 참조를 추적하지 않고는 어떤 역할이 어떤 권한을 가지는지 이해하기 어렵게 만듭니다.
대신, 각 권한을 적절한 역할 YAML 파일에 직접 추가하십시오.
# 나쁜 예 - 중간 ability가 다른 권한으로 분기됨
rule { can?(:read_security_resource) }.enable :read_vulnerability
rule { can?(:read_security_resource) }.enable :read_security_dashboard
# 좋은 예 - 각 권한이 역할 YAML에 직접 있음
# (config/authz/roles/developer.yml)
# raw_permissions:
# - read_vulnerability
# - read_security_dashboard
예외: 비공개 권한#
비공개 권한(밑줄 접두사)은 중간 ability를 통한 캐스케이딩이 올바른 유일한 경우입니다.
_read_authored_issue와 같은 비공개 권한은 역할 YAML 정의에서 역할에 할당된 다음,
더 넓은 공개 권한을 활성화하기 위해 정책의 주체 수준 조건과 결합됩니다. 이 패턴은 의도적입니다:
-
비공개 권한은 역할의 조건부 기능을 명시적이고 기계 판독 가능하게 만들어, 권한 상승 확인과 커스텀 역할 구성에 필요합니다.
-
캐스케이딩은 항상 정확히 한 단계만 깊습니다: 비공개 권한 + 조건이 공개 권한을 활성화합니다. 더 깊은 체인은 여전히 허용되지 않습니다.
# 좋은 예 - 비공개 권한이 조건으로 더 넓은 권한을 제어
rule { can?(:_read_authored_issue) & is_author }.enable :read_issue
rule { can?(:_read_assigned_issue) & is_assignee }.enable :read_issue
# 나쁜 예 - 비공개가 아닌 중간 ability를 통한 캐스케이딩
rule { can?(:read_security_resource) }.enable :read_vulnerability
중첩된 조건 피하기#
단일 rule에서 &로 역할 확인과 설정/플래그 확인을 결합하지 마십시오. 대신, 권한을 역할 YAML 파일에 추가하고 조건이 충족되지 않을 때 제한하기 위한 별도의 rule로 prevent를 사용하십시오. 참조
# 나쁜 예 - 단일 규칙에서 역할과 설정 확인을 혼합
rule { developer & model_registry_enabled }.policy do
enable :write_model_registry
end
# 좋은 예 - 권한이 developer 역할 YAML에 있고, 정책은 방지만 함
rule { ~model_registry_enabled }.prevent :write_model_registry
admin | owner 규칙 피하기#
admin 사용자는 condition(:owner)에 대해 true를 반환하므로 admin | owner 규칙을 정의할 필요가 없습니다. 조직 소유자에 대해서도 마찬가지입니다. 권한은 정책에서 활성화하는 것보다 역할 YAML 파일에 있어야 합니다.
# 나쁜 예 - 중복된 admin/조직 소유자 확인 및 정책에서 활성화
rule { admin | organization_owner | owner }.enable :delete_project
# 좋은 예 - 권한이 owner 역할 YAML에 있음
# (config/authz/roles/owner.yml)
# raw_permissions:
# - delete_project
예시#
역할 YAML 및 prevent를 사용하기 위한 결합된 조건 리팩토링#
# 나쁜 예 - 모든 조건이 참일 때만 권한이 활성화되므로, 역할의
# 액세스가 기능 플래그 및 기타 조건에 따라 증가합니다. 인증 로직은
# 액세스를 제거해야 하며, 확장해서는 안 됩니다.
rule { can?(:developer_access) & user_confirmed? }.policy do
enable :create_pipeline
end
rule { ai_flow_triggers_enabled & (amazon_q_enabled | duo_workflow_available) & can?(:developer_access) & can?(:create_pipeline) }.policy do
enable :trigger_ai_flow
end
# 좋은 예 - trigger_ai_flow가 developer 역할 YAML에 있음.
# (config/authz/roles/developer.yml)
# raw_permissions:
# - trigger_ai_flow
#
# 각 조건이 만족되지 않을 때 독립적으로 방지하여,
# 역할의 기본 권한을 항상 열거할 수 있습니다.
rule { ~user_confirmed? }.prevent :trigger_ai_flow
rule { ~ai_flow_triggers_enabled }.prevent :trigger_ai_flow
rule { ~amazon_q_enabled & ~duo_workflow_available }.prevent :trigger_ai_flow
