InfoGrab DocsInfoGrab Docs

Secrets Manager OpenBao 클라이언트와 인증

요약

GitLab은 SecretsManagement::SecretsManagerClient를 통해 HTTP로 OpenBao와 통신합니다. 두 가지 인가 계층(Rails 정책과 OpenBao ACL)과 OpenBao 네임스페이스 구성은 Secrets Manager 개발 가이드라인을 참고합니다.

GitLab은 SecretsManagement::SecretsManagerClient를 통해 HTTP로 OpenBao와 통신합니다. 모든 요청은 GitLab이 직접 서명한 JWT를 함께 보냅니다. OpenBao는 CEL(Common Expression Language) 프로그램으로 각 JWT를 검사하고, 클레임을 해당 요청에 대한 ACL 정책 집합으로 변환합니다.

두 가지 인가 계층(Rails 정책과 OpenBao ACL)과 OpenBao 네임스페이스 구성은 Secrets Manager 개발 가이드라인을 참고합니다.

SecretsManagerClient#

파일: ee/lib/secrets_management/secrets_manager_client.rb

클라이언트는 Faraday를 감싸며 미리 인증하지 않습니다. 모든 요청이 OpenBao의 인라인 인증 헤더를 포함하므로, 각 호출이 한 번의 왕복으로 인증하고 실행합니다.

  • X-Vault-Inline-Auth-Path: 사용할 로그인 경로. 예를 들어 auth/user_jwt/cel/login입니다.
  • X-Vault-Inline-Auth-Parameter-token: base64로 인코딩한 JWT.
  • X-Vault-Inline-Auth-Parameter-role: base64로 인코딩한 역할 이름(비어 있으면 생략).

SecretsManagerClient.new는 다음을 받습니다.

매개변수 기본값 용도
jwt 없음 인라인 인증으로 보낼 인코딩된 JWT. 필수입니다.
role 'app' 인증할 때 사용하는 JWT 역할.
auth_namespace "" 로그인 경로의 기준이 되는 OpenBao 네임스페이스.
auth_mount 'gitlab_rails_jwt' 인증 대상 JWT 인증 마운트.
use_cel_auth false login 대신 마운트의 cel/login 경로에서 인증합니다.
namespace "" 데이터 플레인 요청의 기준이 되는 OpenBao 네임스페이스.
timeout nil 요청별 연결 및 읽기 타임아웃(초).

클라이언트 호스트와 기본 경로는 ee/config/initializers/secrets_manager.rb에서 설정하는 SecretsManagerClient.configure에서 가져옵니다.

네임스페이스 지정#

with_namespace는 다른 OpenBao 네임스페이스로 범위가 지정된 새 클라이언트를 반환합니다. with_auth_namespace는 작업하는 네임스페이스와 다른 네임스페이스에 로그인하는 클라이언트를 반환합니다.

client = SecretsManagerClient.new(jwt: jwt)
org_client = client.with_namespace(secrets_manager.org_path)
project_client = client.with_namespace(secrets_manager.full_project_namespace_path)

with_namespace는 클라이언트의 네임스페이스에 덧붙이지 않고 교체하므로, 항상 루트부터 슬래시로 연결한 전체 경로를 전달합니다.

클라이언트 생성 패턴 두 가지#

  • 권한이 높은 관리 클라이언트: GlobalSecretsManagerJwt 하위 클래스로 만들며 기본값인 role: 'app'과 auth_mount: 'gitlab_rails_jwt'를 사용한 다음, 작업 대상인 org, 루트 또는 엔티티 수준 네임스페이스의 전체 경로로 with_namespace 범위를 지정합니다. ProjectBaseService#base_secrets_manager_client와 GroupBaseService#base_secrets_manager_client가 이 클라이언트를 만듭니다.
  • 특정 마운트 하나로 범위가 지정된 CEL 클라이언트: *UserJwt, *ApiJwt 또는 PipelineJwt로 만들며 해당 마운트의 role, auth_mount, auth_namespace를 명시적으로 전달하고 use_cel_auth: true를 사용합니다. ProjectBaseService#user_client가 한 예입니다.

메서드 그룹#

그룹 메서드
네임스페이스 관리 enable_namespace, disable_namespace
시크릿 엔진 관리 enable_secrets_engine, disable_secrets_engine
인증 엔진 관리 enable_auth_engine, disable_auth_engine, configure_jwt
JWT 역할 구성 update_gitlab_rails_jwt_role, update_jwt_role, read_jwt_role, delete_jwt_role
CEL 역할 구성 update_jwt_cel_role, read_jwt_cel_role, delete_jwt_cel_role, cel_login_jwt
시크릿 값과 메타데이터 update_kv_secret, update_kv_secret_metadata, read_secret_metadata, delete_kv_secret, list_secrets, count_secrets
ACL 정책 get_policy, set_policy, delete_policy, list_policies, list_project_policies
기능 해석 capabilities_self
복구 및 상태 init_rotate_recovery, cancel_rotate_recovery, server_available?, generate_root_token_status, init_generate_root_token, update_generate_root_token, cancel_generate_root_token

capabilities_self는 인라인 인증을 사용하지 않는 유일한 메서드입니다. sys/capabilities-self는 저장된 토큰을 조회하여 호출자를 해석하지만, 인라인 인증 토큰은 토큰 저장소에 기록되지 않습니다. 그래서 클라이언트는 먼저 명시적으로 인증하고(login_with_jwt), 그 결과 토큰을 X-Vault-Token으로 사용해 capabilities-self를 호출한 다음 그 토큰을 폐기합니다.

오류 유형#

클라이언트는 OpenBao나 연결이 반환한 내용에 따라 네 가지 오류 클래스 중 하나를 발생시킵니다.

클래스 발생 조건
ApiError errors 배열이 있는 응답 본문, 또는 OpenBao의 raise_error 미들웨어가 클라이언트 오류로 바꾸는 4xx.
AuthenticationError 인라인 인증 실패. 항상 별도의 상태 코드로 나타나는 것이 아니라 X-Vault-Inline-Auth-Failed 응답 헤더로 알립니다.
ServiceUnavailableError 5xx 응답 또는 요청 타임아웃.
ConnectionError 그 밖의 모든 Faraday 연결 실패.

JWT 클래스 계층#

Secrets Manager는 상황마다 다른 JWT 하위 클래스를 발급하며, 다음 계층으로 구성됩니다.

디렉터리: ee/lib/secrets_management/

Gitlab::Ci::JwtBase
  GlobalSecretsManagerJwt (privileged, system-level)
    ProjectSecretsManagerJwt
      ProjectUserJwt
        ProjectApiJwt
    GroupSecretsManagerJwt
      GroupUserJwt
        GroupApiJwt

Gitlab::Ci::JwtV2
  PipelineJwt
클래스 파일 사용 용도 구별 클레임
GlobalSecretsManagerJwt global_secrets_manager_jwt.rb 프로젝트나 그룹으로 범위가 지정되지 않은 시스템 수준 작업. 예를 들어 복구 키 생성. sub: 'gitlab_secrets_manager', secrets_manager_scope: 'privileged'. TTL 30초.
ProjectSecretsManagerJwt project_secrets_manager_jwt.rb 프로젝트 범위의 권한이 높은 작업(프로비저닝, 관리 클라이언트)을 위한 기본 클래스. JSONWebToken::UserProjectTokenClaims로 프로젝트 및 사용자 감사 클레임을 추가합니다.
ProjectUserJwt project_user_jwt.rb UI와 GraphQL을 통한 시크릿 CRUD. sub: "user:{username}", secrets_manager_scope: 'user', role_id, member_role_id.
ProjectApiJwt project_api_jwt.rb 프로젝트 시크릿에 대한 비 CI API 액세스. ProjectUserJwt와 같은 클레임에 secrets_manager_scope: 'api'와 auth_via(API 호출에 사용한 토큰 유형)를 더합니다.
GroupSecretsManagerJwt group_secrets_manager_jwt.rb 그룹 범위의 권한이 높은 작업을 위한 기본 클래스. group_id, group_path, root_group_id, organization_id, organization_path.
GroupUserJwt group_user_jwt.rb UI와 GraphQL을 통한 그룹의 시크릿 CRUD. ProjectUserJwt와 같은 형태.
GroupApiJwt group_api_jwt.rb 그룹 시크릿에 대한 비 CI API 액세스. ProjectApiJwt와 같은 형태.
PipelineJwt pipeline_jwt.rb 파이프라인 job 중에 시크릿을 가져오는 CI 러너. secrets_manager_scope: 'pipeline', project_group_ids(그룹 수준 시크릿 액세스를 위한 프로젝트의 그룹 상위 계층).

role_id는 사용자의 유효 액세스 수준이며, 프로젝트에서는 max_member_access_for_project로, 그룹에서는 max_member_access_for_group(공유를 고려하며 only_concrete_membership: true)으로 읽습니다. member_role_id는 프로젝트나 그룹의 상위 계층을 따라가며 가장 가까운 사용자 지정 권한 할당을 찾습니다.

PipelineJwt는 GlobalSecretsManagerJwt 계열이 아니라 Gitlab::Ci::JwtV2를 확장하며 verify_path_not_burned!를 아무 동작도 하지 않도록 재정의합니다. OpenBao가 토큰 subject 접두사가 아니라 project_id와 project_group_ids 클레임으로 프로젝트 ID를 검증하기 때문입니다.

인증 마운트#

프로비저닝된 각 Secrets Manager 네임스페이스에는 JWT 인증 마운트가 세 개 있으며, 모두 CEL 역할로 인증합니다.

마운트 역할 사용처 해석되는 정책
pipeline_jwt all_pipelines CI 러너(PipelineJwt) pipelines/global, 그리고 환경, 브랜치 또는 결합 정책.
user_jwt all_users UI 및 GraphQL CRUD(*UserJwt) users/direct/user_{id}, users/direct/member_role_{id}, users/roles/{access_level}.
api_jwt all_api 비 CI API 액세스(*ApiJwt) user_jwt와 같은 주체 정책이며 api/ 접두사 아래에 있고 읽기 전용입니다.

네 번째 마운트인 gitlab_rails_jwt(역할 app)는 클라이언트의 기본값이며, CEL로 검증되는 주체가 아니라 권한이 높은 관리 작업(프로비저닝, 네임스페이스 및 정책 관리)에 사용합니다.

CEL 마운트 세 개는 프로비저닝 중에 모두 같은 방식으로 구성됩니다(ProjectSecretsManagers::ProvisionService#enable_auth이며 그룹에도 동일하게 구현). 인증 엔진을 활성화하고, configure_jwt를 호출해 GitLab OIDC 발급자를 지정한 다음, 해당 범위의 CEL 프로그램으로 update_jwt_cel_role을 호출합니다. ApiAuthConfigurator가 api_jwt 마운트에 대한 이 로직을 담당합니다. 프로비저닝 흐름과 API 마운트 백필 태스크가 모두 이를 호출하므로 둘이 서로 어긋날 수 없습니다.

CEL 프로그램#

CEL 프로그램은 ee/app/models/secrets_management/{project,group}_secrets_managers/ 아래의 모델 concern이 범위(프로젝트 또는 그룹)별로 생성합니다.

Concern 메서드 마운트
PipelineHelper pipeline_auth_cel_program pipeline_jwt
UserHelper user_auth_cel_program user_jwt
ApiHelper api_auth_cel_program api_jwt

각 프로그램은 CEL variables의 해시와 마지막 expression으로 이루어집니다. 표현식은 클레임(subject 접두사, secrets_manager_scope, 프로젝트 또는 그룹 ID, audience)을 검증하고, 오류 문자열이나 연결할 OpenBao 정책과 기록할 메타데이터를 나열한 pb.Auth 객체를 반환합니다.

정책 할당은 범위에 따라 다릅니다.

  • 파이프라인 CEL(프로젝트): 항상 pipelines/global이고, 해당 클레임이 설정되어 있으면 pipelines/env/{hex(environment)}, pipelines/branch/{hex(ref)}, pipelines/combined/env/{hex}/branch/{hex}가 추가됩니다.
  • 파이프라인 CEL(그룹): 그룹 수준 CI 액세스는 특정 브랜치 하나로 범위가 지정되지 않으므로, 브랜치 대신 보호 수준으로 이름을 붙인 정책을 ci_policy_name_for_environment(environment, protected:)로 만듭니다.
  • 사용자 및 API CEL(프로젝트와 그룹): users/direct/user_{id}(또는 api/users/direct/user_{id}), users/direct/member_role_{id}, users/roles/{access_level}이며, 클레임에 있는 uid, mrid, rid 중 존재하는 것으로 만듭니다.

사용자 및 API CEL 프로그램은 선택 사항인 secrets_manager_token_ttl 클레임(초)도 읽어 발급하는 OpenBao 토큰의 리스를 설정합니다. 클레임이 없거나 null이거나 양수가 아니면 기본값은 5분입니다.

ACL 정책#

파일: ee/lib/secrets_management/acl_policy.rb, ee/lib/secrets_management/acl_policy_path.rb

두 클래스가 Ruby에서 OpenBao ACL 정책을 모델링합니다. AclPolicy는 정책 name과 경로에서 AclPolicyPath로의 맵을 가집니다. AclPolicyPath는 capabilities(집합이며 예를 들어 create, read, update, delete, list)와 선택 사항인 allowed_parameters, denied_parameters, required_parameters, granted_by(OpenBao 주석 필드이며 사용자 ID로 설정), expired_at을 가집니다. AclPolicy#to_openbao_attributes는 OpenBao의 ACL 정책 API가 기대하는 JSON 형태로 직렬화합니다. SecretsManagerClient#get_policy와 #set_policy가 이름이 지정된 정책을 읽고 씁니다.

정책 이름 지정#

메서드 위치 생성되는 이름
policy_name_for_principal(principal_type:, principal_id:) SecretsManagers::UserHelper 관리(UI) 마운트용 users/direct/user_{id}, users/direct/member_role_{id} 또는 users/roles/{access_level}.
api_policy_name_for_principal(principal_type:, principal_id:) SecretsManagers::ApiHelper 읽기 전용 API 마운트용으로, 같은 이름 앞에 api/를 붙인 이름.
ci_policy_name(environment, branch)와 _global, _env, _branch, _combined 변형 ProjectSecretsManagers::PipelineHelper pipelines/global, pipelines/env/{hex}, pipelines/branch/{hex} 또는 pipelines/combined/env/{hex}/branch/{hex}.
ci_policy_name_for_environment(environment, protected:) GroupSecretsManagers::PipelineHelper pipelines/combined/{protected or unprotected}/{global or env/{hex}}.

OpenBao 정책 이름에는 임의의 문자를 안전하게 넣을 수 없으므로, 환경과 브랜치 값은 정책 이름에 들어가기 전에 BaseSecretsManager#hex(value.unpack1('H*'))로 16진수 인코딩합니다.

정책을 쓰는 시점#

  • 프로비저닝은 기본 역할마다 정책을 하나씩 씁니다(SecretsManagers::DefaultRolePoliciesHelper#create_default_role_policies). 프로젝트 프로비저닝은 관리 마운트의 경로에 대해 Owner(read, write, delete), Maintainer(read, write), Developer(read, create)를 부여합니다. Developer는 write가 아니라 create를 가지므로 시크릿을 추가할 수는 있지만 이미 있는 시크릿을 변경할 수는 없습니다. 그룹 프로비저닝은 그룹 부여가 모든 하위 프로젝트의 파이프라인에 미치므로 Owner에게만 부여합니다.
  • 시크릿 권한을 부여하거나 업데이트하면(SecretsPermissions::UpdateServiceHelpers#store_permission) 정책 두 개를 씁니다. 관리 정책(메타데이터 읽기, 쓰기, 삭제이며 값 읽기는 절대 포함하지 않음)과, read_value가 부여된 경우에만 api/ 접두사 아래의 읽기 전용 API 정책입니다. API 기능이 남아 있지 않으면 API 정책을 빈 채로 쓰지 않고 삭제합니다.
  • 모든 정책 쓰기는 정책 경로의 전체 기능 집합을 교체한 뒤 현재 집합을 다시 추가하므로, 기능을 철회하면 오래된 부여가 남지 않고 지워집니다.

비 CI API 액세스#

파일: ee/lib/api/secrets_management/access_tokens.rb, ee/app/services/secrets_management/api_access/issue_token_service.rb

REST 엔드포인트는 클라이언트가 읽을 때마다 GitLab을 거치지 않고 OpenBao에 직접 제시하는 수명이 짧은 JWT를 발급합니다.

  • POST /projects/:id/secrets_manager/access_token
  • POST /groups/:id/secrets_manager/access_token

두 경로 모두 기능 플래그와 create_secrets_manager_api_jwt 인가 권한 뒤에 있으며, Secrets Manager가 active 상태여야 합니다. 액세스는 토큰 기반 인증(개인 액세스 토큰, 프로젝트 또는 그룹 액세스 토큰, 서비스 계정 토큰, OAuth)으로 제한됩니다. 브라우저 세션은 폐기할 수 있는 자격 증명을 남기지 않으므로 거부합니다.

ApiAccess::IssueTokenService는 ProjectApiJwt 또는 GroupApiJwt(TTL 5분)를 만듭니다. 이 서비스는 external-secrets.io Vault 공급자 형식에 맞춘 연결 정보를 반환하므로 External Secrets Operator 같은 클라이언트가 응답을 그대로 사용할 수 있습니다. 서버 URL, 네임스페이스, KV 마운트 경로, 시크릿 기본 경로(explicit), CEL 인증 경로(api_jwt/cel), 역할(all_api), 토큰 자체가 포함됩니다.

이 액세스 토큰 발급 외에 비 CI REST 읽기 또는 쓰기 엔드포인트를 제공하겠다는 공개적인 약속은 아직 없습니다. 쓰기는 여전히 기존 GraphQL 뮤테이션을 거칩니다.

Secrets Manager OpenBao 클라이언트와 인증

GitLab v19.5
원문 보기

요약

GitLab은 SecretsManagement::SecretsManagerClient를 통해 HTTP로 OpenBao와 통신합니다. 두 가지 인가 계층(Rails 정책과 OpenBao ACL)과 OpenBao 네임스페이스 구성은 Secrets Manager 개발 가이드라인을 참고합니다.

GitLab은 SecretsManagement::SecretsManagerClient를 통해 HTTP로 OpenBao와 통신합니다. 모든 요청은 GitLab이 직접 서명한 JWT를 함께 보냅니다. OpenBao는 CEL(Common Expression Language) 프로그램으로 각 JWT를 검사하고, 클레임을 해당 요청에 대한 ACL 정책 집합으로 변환합니다.

두 가지 인가 계층(Rails 정책과 OpenBao ACL)과 OpenBao 네임스페이스 구성은 Secrets Manager 개발 가이드라인을 참고합니다.

SecretsManagerClient#

파일: ee/lib/secrets_management/secrets_manager_client.rb

클라이언트는 Faraday를 감싸며 미리 인증하지 않습니다. 모든 요청이 OpenBao의 인라인 인증 헤더를 포함하므로, 각 호출이 한 번의 왕복으로 인증하고 실행합니다.

  • X-Vault-Inline-Auth-Path: 사용할 로그인 경로. 예를 들어 auth/user_jwt/cel/login입니다.
  • X-Vault-Inline-Auth-Parameter-token: base64로 인코딩한 JWT.
  • X-Vault-Inline-Auth-Parameter-role: base64로 인코딩한 역할 이름(비어 있으면 생략).

SecretsManagerClient.new는 다음을 받습니다.

매개변수 기본값 용도
jwt 없음 인라인 인증으로 보낼 인코딩된 JWT. 필수입니다.
role 'app' 인증할 때 사용하는 JWT 역할.
auth_namespace "" 로그인 경로의 기준이 되는 OpenBao 네임스페이스.
auth_mount 'gitlab_rails_jwt' 인증 대상 JWT 인증 마운트.
use_cel_auth false login 대신 마운트의 cel/login 경로에서 인증합니다.
namespace "" 데이터 플레인 요청의 기준이 되는 OpenBao 네임스페이스.
timeout nil 요청별 연결 및 읽기 타임아웃(초).

클라이언트 호스트와 기본 경로는 ee/config/initializers/secrets_manager.rb에서 설정하는 SecretsManagerClient.configure에서 가져옵니다.

네임스페이스 지정#

with_namespace는 다른 OpenBao 네임스페이스로 범위가 지정된 새 클라이언트를 반환합니다. with_auth_namespace는 작업하는 네임스페이스와 다른 네임스페이스에 로그인하는 클라이언트를 반환합니다.

client = SecretsManagerClient.new(jwt: jwt)
org_client = client.with_namespace(secrets_manager.org_path)
project_client = client.with_namespace(secrets_manager.full_project_namespace_path)

with_namespace는 클라이언트의 네임스페이스에 덧붙이지 않고 교체하므로, 항상 루트부터 슬래시로 연결한 전체 경로를 전달합니다.

클라이언트 생성 패턴 두 가지#

  • 권한이 높은 관리 클라이언트: GlobalSecretsManagerJwt 하위 클래스로 만들며 기본값인 role: 'app'과 auth_mount: 'gitlab_rails_jwt'를 사용한 다음, 작업 대상인 org, 루트 또는 엔티티 수준 네임스페이스의 전체 경로로 with_namespace 범위를 지정합니다. ProjectBaseService#base_secrets_manager_client와 GroupBaseService#base_secrets_manager_client가 이 클라이언트를 만듭니다.
  • 특정 마운트 하나로 범위가 지정된 CEL 클라이언트: *UserJwt, *ApiJwt 또는 PipelineJwt로 만들며 해당 마운트의 role, auth_mount, auth_namespace를 명시적으로 전달하고 use_cel_auth: true를 사용합니다. ProjectBaseService#user_client가 한 예입니다.

메서드 그룹#

그룹 메서드
네임스페이스 관리 enable_namespace, disable_namespace
시크릿 엔진 관리 enable_secrets_engine, disable_secrets_engine
인증 엔진 관리 enable_auth_engine, disable_auth_engine, configure_jwt
JWT 역할 구성 update_gitlab_rails_jwt_role, update_jwt_role, read_jwt_role, delete_jwt_role
CEL 역할 구성 update_jwt_cel_role, read_jwt_cel_role, delete_jwt_cel_role, cel_login_jwt
시크릿 값과 메타데이터 update_kv_secret, update_kv_secret_metadata, read_secret_metadata, delete_kv_secret, list_secrets, count_secrets
ACL 정책 get_policy, set_policy, delete_policy, list_policies, list_project_policies
기능 해석 capabilities_self
복구 및 상태 init_rotate_recovery, cancel_rotate_recovery, server_available?, generate_root_token_status, init_generate_root_token, update_generate_root_token, cancel_generate_root_token

capabilities_self는 인라인 인증을 사용하지 않는 유일한 메서드입니다. sys/capabilities-self는 저장된 토큰을 조회하여 호출자를 해석하지만, 인라인 인증 토큰은 토큰 저장소에 기록되지 않습니다. 그래서 클라이언트는 먼저 명시적으로 인증하고(login_with_jwt), 그 결과 토큰을 X-Vault-Token으로 사용해 capabilities-self를 호출한 다음 그 토큰을 폐기합니다.

오류 유형#

클라이언트는 OpenBao나 연결이 반환한 내용에 따라 네 가지 오류 클래스 중 하나를 발생시킵니다.

클래스 발생 조건
ApiError errors 배열이 있는 응답 본문, 또는 OpenBao의 raise_error 미들웨어가 클라이언트 오류로 바꾸는 4xx.
AuthenticationError 인라인 인증 실패. 항상 별도의 상태 코드로 나타나는 것이 아니라 X-Vault-Inline-Auth-Failed 응답 헤더로 알립니다.
ServiceUnavailableError 5xx 응답 또는 요청 타임아웃.
ConnectionError 그 밖의 모든 Faraday 연결 실패.

JWT 클래스 계층#

Secrets Manager는 상황마다 다른 JWT 하위 클래스를 발급하며, 다음 계층으로 구성됩니다.

디렉터리: ee/lib/secrets_management/

Gitlab::Ci::JwtBase
  GlobalSecretsManagerJwt (privileged, system-level)
    ProjectSecretsManagerJwt
      ProjectUserJwt
        ProjectApiJwt
    GroupSecretsManagerJwt
      GroupUserJwt
        GroupApiJwt

Gitlab::Ci::JwtV2
  PipelineJwt
클래스 파일 사용 용도 구별 클레임
GlobalSecretsManagerJwt global_secrets_manager_jwt.rb 프로젝트나 그룹으로 범위가 지정되지 않은 시스템 수준 작업. 예를 들어 복구 키 생성. sub: 'gitlab_secrets_manager', secrets_manager_scope: 'privileged'. TTL 30초.
ProjectSecretsManagerJwt project_secrets_manager_jwt.rb 프로젝트 범위의 권한이 높은 작업(프로비저닝, 관리 클라이언트)을 위한 기본 클래스. JSONWebToken::UserProjectTokenClaims로 프로젝트 및 사용자 감사 클레임을 추가합니다.
ProjectUserJwt project_user_jwt.rb UI와 GraphQL을 통한 시크릿 CRUD. sub: "user:{username}", secrets_manager_scope: 'user', role_id, member_role_id.
ProjectApiJwt project_api_jwt.rb 프로젝트 시크릿에 대한 비 CI API 액세스. ProjectUserJwt와 같은 클레임에 secrets_manager_scope: 'api'와 auth_via(API 호출에 사용한 토큰 유형)를 더합니다.
GroupSecretsManagerJwt group_secrets_manager_jwt.rb 그룹 범위의 권한이 높은 작업을 위한 기본 클래스. group_id, group_path, root_group_id, organization_id, organization_path.
GroupUserJwt group_user_jwt.rb UI와 GraphQL을 통한 그룹의 시크릿 CRUD. ProjectUserJwt와 같은 형태.
GroupApiJwt group_api_jwt.rb 그룹 시크릿에 대한 비 CI API 액세스. ProjectApiJwt와 같은 형태.
PipelineJwt pipeline_jwt.rb 파이프라인 job 중에 시크릿을 가져오는 CI 러너. secrets_manager_scope: 'pipeline', project_group_ids(그룹 수준 시크릿 액세스를 위한 프로젝트의 그룹 상위 계층).

role_id는 사용자의 유효 액세스 수준이며, 프로젝트에서는 max_member_access_for_project로, 그룹에서는 max_member_access_for_group(공유를 고려하며 only_concrete_membership: true)으로 읽습니다. member_role_id는 프로젝트나 그룹의 상위 계층을 따라가며 가장 가까운 사용자 지정 권한 할당을 찾습니다.

PipelineJwt는 GlobalSecretsManagerJwt 계열이 아니라 Gitlab::Ci::JwtV2를 확장하며 verify_path_not_burned!를 아무 동작도 하지 않도록 재정의합니다. OpenBao가 토큰 subject 접두사가 아니라 project_id와 project_group_ids 클레임으로 프로젝트 ID를 검증하기 때문입니다.

인증 마운트#

프로비저닝된 각 Secrets Manager 네임스페이스에는 JWT 인증 마운트가 세 개 있으며, 모두 CEL 역할로 인증합니다.

마운트 역할 사용처 해석되는 정책
pipeline_jwt all_pipelines CI 러너(PipelineJwt) pipelines/global, 그리고 환경, 브랜치 또는 결합 정책.
user_jwt all_users UI 및 GraphQL CRUD(*UserJwt) users/direct/user_{id}, users/direct/member_role_{id}, users/roles/{access_level}.
api_jwt all_api 비 CI API 액세스(*ApiJwt) user_jwt와 같은 주체 정책이며 api/ 접두사 아래에 있고 읽기 전용입니다.

네 번째 마운트인 gitlab_rails_jwt(역할 app)는 클라이언트의 기본값이며, CEL로 검증되는 주체가 아니라 권한이 높은 관리 작업(프로비저닝, 네임스페이스 및 정책 관리)에 사용합니다.

CEL 마운트 세 개는 프로비저닝 중에 모두 같은 방식으로 구성됩니다(ProjectSecretsManagers::ProvisionService#enable_auth이며 그룹에도 동일하게 구현). 인증 엔진을 활성화하고, configure_jwt를 호출해 GitLab OIDC 발급자를 지정한 다음, 해당 범위의 CEL 프로그램으로 update_jwt_cel_role을 호출합니다. ApiAuthConfigurator가 api_jwt 마운트에 대한 이 로직을 담당합니다. 프로비저닝 흐름과 API 마운트 백필 태스크가 모두 이를 호출하므로 둘이 서로 어긋날 수 없습니다.

CEL 프로그램#

CEL 프로그램은 ee/app/models/secrets_management/{project,group}_secrets_managers/ 아래의 모델 concern이 범위(프로젝트 또는 그룹)별로 생성합니다.

Concern 메서드 마운트
PipelineHelper pipeline_auth_cel_program pipeline_jwt
UserHelper user_auth_cel_program user_jwt
ApiHelper api_auth_cel_program api_jwt

각 프로그램은 CEL variables의 해시와 마지막 expression으로 이루어집니다. 표현식은 클레임(subject 접두사, secrets_manager_scope, 프로젝트 또는 그룹 ID, audience)을 검증하고, 오류 문자열이나 연결할 OpenBao 정책과 기록할 메타데이터를 나열한 pb.Auth 객체를 반환합니다.

정책 할당은 범위에 따라 다릅니다.

  • 파이프라인 CEL(프로젝트): 항상 pipelines/global이고, 해당 클레임이 설정되어 있으면 pipelines/env/{hex(environment)}, pipelines/branch/{hex(ref)}, pipelines/combined/env/{hex}/branch/{hex}가 추가됩니다.
  • 파이프라인 CEL(그룹): 그룹 수준 CI 액세스는 특정 브랜치 하나로 범위가 지정되지 않으므로, 브랜치 대신 보호 수준으로 이름을 붙인 정책을 ci_policy_name_for_environment(environment, protected:)로 만듭니다.
  • 사용자 및 API CEL(프로젝트와 그룹): users/direct/user_{id}(또는 api/users/direct/user_{id}), users/direct/member_role_{id}, users/roles/{access_level}이며, 클레임에 있는 uid, mrid, rid 중 존재하는 것으로 만듭니다.

사용자 및 API CEL 프로그램은 선택 사항인 secrets_manager_token_ttl 클레임(초)도 읽어 발급하는 OpenBao 토큰의 리스를 설정합니다. 클레임이 없거나 null이거나 양수가 아니면 기본값은 5분입니다.

ACL 정책#

파일: ee/lib/secrets_management/acl_policy.rb, ee/lib/secrets_management/acl_policy_path.rb

두 클래스가 Ruby에서 OpenBao ACL 정책을 모델링합니다. AclPolicy는 정책 name과 경로에서 AclPolicyPath로의 맵을 가집니다. AclPolicyPath는 capabilities(집합이며 예를 들어 create, read, update, delete, list)와 선택 사항인 allowed_parameters, denied_parameters, required_parameters, granted_by(OpenBao 주석 필드이며 사용자 ID로 설정), expired_at을 가집니다. AclPolicy#to_openbao_attributes는 OpenBao의 ACL 정책 API가 기대하는 JSON 형태로 직렬화합니다. SecretsManagerClient#get_policy와 #set_policy가 이름이 지정된 정책을 읽고 씁니다.

정책 이름 지정#

메서드 위치 생성되는 이름
policy_name_for_principal(principal_type:, principal_id:) SecretsManagers::UserHelper 관리(UI) 마운트용 users/direct/user_{id}, users/direct/member_role_{id} 또는 users/roles/{access_level}.
api_policy_name_for_principal(principal_type:, principal_id:) SecretsManagers::ApiHelper 읽기 전용 API 마운트용으로, 같은 이름 앞에 api/를 붙인 이름.
ci_policy_name(environment, branch)와 _global, _env, _branch, _combined 변형 ProjectSecretsManagers::PipelineHelper pipelines/global, pipelines/env/{hex}, pipelines/branch/{hex} 또는 pipelines/combined/env/{hex}/branch/{hex}.
ci_policy_name_for_environment(environment, protected:) GroupSecretsManagers::PipelineHelper pipelines/combined/{protected or unprotected}/{global or env/{hex}}.

OpenBao 정책 이름에는 임의의 문자를 안전하게 넣을 수 없으므로, 환경과 브랜치 값은 정책 이름에 들어가기 전에 BaseSecretsManager#hex(value.unpack1('H*'))로 16진수 인코딩합니다.

정책을 쓰는 시점#

  • 프로비저닝은 기본 역할마다 정책을 하나씩 씁니다(SecretsManagers::DefaultRolePoliciesHelper#create_default_role_policies). 프로젝트 프로비저닝은 관리 마운트의 경로에 대해 Owner(read, write, delete), Maintainer(read, write), Developer(read, create)를 부여합니다. Developer는 write가 아니라 create를 가지므로 시크릿을 추가할 수는 있지만 이미 있는 시크릿을 변경할 수는 없습니다. 그룹 프로비저닝은 그룹 부여가 모든 하위 프로젝트의 파이프라인에 미치므로 Owner에게만 부여합니다.
  • 시크릿 권한을 부여하거나 업데이트하면(SecretsPermissions::UpdateServiceHelpers#store_permission) 정책 두 개를 씁니다. 관리 정책(메타데이터 읽기, 쓰기, 삭제이며 값 읽기는 절대 포함하지 않음)과, read_value가 부여된 경우에만 api/ 접두사 아래의 읽기 전용 API 정책입니다. API 기능이 남아 있지 않으면 API 정책을 빈 채로 쓰지 않고 삭제합니다.
  • 모든 정책 쓰기는 정책 경로의 전체 기능 집합을 교체한 뒤 현재 집합을 다시 추가하므로, 기능을 철회하면 오래된 부여가 남지 않고 지워집니다.

비 CI API 액세스#

파일: ee/lib/api/secrets_management/access_tokens.rb, ee/app/services/secrets_management/api_access/issue_token_service.rb

REST 엔드포인트는 클라이언트가 읽을 때마다 GitLab을 거치지 않고 OpenBao에 직접 제시하는 수명이 짧은 JWT를 발급합니다.

  • POST /projects/:id/secrets_manager/access_token
  • POST /groups/:id/secrets_manager/access_token

두 경로 모두 기능 플래그와 create_secrets_manager_api_jwt 인가 권한 뒤에 있으며, Secrets Manager가 active 상태여야 합니다. 액세스는 토큰 기반 인증(개인 액세스 토큰, 프로젝트 또는 그룹 액세스 토큰, 서비스 계정 토큰, OAuth)으로 제한됩니다. 브라우저 세션은 폐기할 수 있는 자격 증명을 남기지 않으므로 거부합니다.

ApiAccess::IssueTokenService는 ProjectApiJwt 또는 GroupApiJwt(TTL 5분)를 만듭니다. 이 서비스는 external-secrets.io Vault 공급자 형식에 맞춘 연결 정보를 반환하므로 External Secrets Operator 같은 클라이언트가 응답을 그대로 사용할 수 있습니다. 서버 URL, 네임스페이스, KV 마운트 경로, 시크릿 기본 경로(explicit), CEL 인증 경로(api_jwt/cel), 역할(all_api), 토큰 자체가 포함됩니다.

이 액세스 토큰 발급 외에 비 CI REST 읽기 또는 쓰기 엔드포인트를 제공하겠다는 공개적인 약속은 아직 없습니다. 쓰기는 여전히 기존 GraphQL 뮤테이션을 거칩니다.