Secrets Manager 워커와 정책
GitLab v19.5요약
Secrets Manager는 ee/app/workers/secrets_management/ 아래의 Sidekiq 워커로 백그라운드 작업을 실행합니다. 과금 이벤트의 비즈니스 의미는 Secrets Manager 이행 및 자격을 참고합니다.
Secrets Manager는 ee/app/workers/secrets_management/ 아래의 Sidekiq 워커로 백그라운드 작업을 실행합니다.
인가는 두 계층으로 실행됩니다.
Rails 정책은 ee/app/policies/secrets_management/ 아래에 있습니다.
OpenBao 액세스 제어 목록(ACL)은 호출자의 JSON Web Token(JWT)을 기준으로 확인합니다.
OpenBao는 감사 로그 줄을 전달하기 위해 내부 API를 통해 GitLab을 다시 호출하기도 합니다.
워커#
| 워커 | 용도 | 큐에 넣는 주체 |
|---|---|---|
ProvisionProjectSecretsManagerTaskWorker |
OpenBao에서 프로젝트 Secrets Manager를 프로비저닝합니다 | ProjectSecretsManagers::InitializeService, 그리고 재시도 시 프로젝트 유지 관리 작업 크론 워커 |
ProvisionGroupSecretsManagerTaskWorker |
OpenBao에서 그룹 Secrets Manager를 프로비저닝합니다 | GroupSecretsManagers::InitializeService, 그리고 재시도 시 그룹 유지 관리 작업 크론 워커 |
ProvisionProjectSecretsManagerWorker |
위의 작업 워커로 대체된 레거시 직접 프로비저닝 워커입니다 | 코드베이스 어디에서도 더 이상 큐에 넣지 않습니다 |
ProvisionGroupSecretsManagerWorker |
위의 작업 워커로 대체된 레거시 직접 프로비저닝 워커입니다 | 코드베이스 어디에서도 더 이상 큐에 넣지 않습니다 |
DeprovisionProjectSecretsManagerWorker |
프로젝트 Secrets Manager를 프로비저닝 해제합니다 | ProjectSecretsManagers::InitiateDeprovisionService, 그리고 재시도 시 프로젝트 유지 관리 작업 크론 워커 |
DeprovisionGroupSecretsManagerWorker |
그룹 Secrets Manager를 프로비저닝 해제합니다 | GroupSecretsManagers::InitiateDeprovisionService, 그리고 재시도 시 그룹 유지 관리 작업 크론 워커 |
ProjectSecretsManagerMaintenanceTasksCronWorker |
오래된 프로젝트 프로비저닝 또는 프로비저닝 해제 유지 관리 작업을 재시도합니다 | 크론 |
GroupSecretsManagerMaintenanceTasksCronWorker |
오래된 그룹 프로비저닝 또는 프로비저닝 해제 유지 관리 작업을 재시도합니다 | 크론 |
ProjectSecretsManagerReapOrphanTasksCronWorker |
재시도 횟수를 모두 소진한 프로젝트 유지 관리 작업을 종료합니다 | 크론 |
GroupSecretsManagerReapOrphanTasksCronWorker |
재시도 횟수를 모두 소진한 그룹 유지 관리 작업을 종료합니다 | 크론 |
ReconcileNamespaceSecretCountWorker |
네임스페이스 하나의 캐시된 시크릿 수를 갱신합니다 | ReconcileNamespaceSecretCountsCronWorker, 그리고 네임스페이스의 시크릿 수를 변경하는 서비스 |
ReconcileNamespaceSecretCountsCronWorker |
Secrets Manager가 있는 모든 네임스페이스에 대해 ReconcileNamespaceSecretCountWorker를 큐에 넣습니다 |
크론 |
EmitSecretsStoredBillableEventWorker |
루트 네임스페이스 하나에 대한 시크릿 저장 과금 이벤트를 내보냅니다 | EmitSecretsStoredBillableEventCronWorker |
EmitSecretsStoredBillableEventCronWorker |
저장된 시크릿이 있는 모든 루트 네임스페이스에 대해 EmitSecretsStoredBillableEventWorker를 큐에 넣습니다 |
크론 |
SecretRotationReminderBatchWorker |
더 이상 사용되지 않으며 아무 작업도 하지 않고, 아래의 두 워커로 대체되었습니다 | 없음. 더 이상 예약되지 않습니다. |
ProjectSecretRotationReminderBatchWorker |
프로젝트 시크릿의 로테이션 알림을 보냅니다 | 크론 |
GroupSecretRotationReminderBatchWorker |
그룹 시크릿의 로테이션 알림을 보냅니다 | 크론 |
AuditLogWorker |
OpenBao 감사 로그 한 줄을 GitLab 감사 이벤트와 과금 이벤트로 처리합니다 | API::Internal::SecretsManager |
과금 이벤트의 비즈니스 의미는 Secrets Manager 이행 및 자격을 참고합니다. 프로비저닝 해제 유지 관리 작업을 만드는 삭제 및 이전 흐름은 Secrets Manager 상태와 부수 효과를 참고합니다.
모든 워커는 feature_category :secrets_management와 idempotent!를 설정합니다.
프로비저닝 및 프로비저닝 해제 워커는 OpenBao를 호출하므로 worker_has_external_dependencies!도 설정합니다.
크론 일정#
크론 항목은 ee/config/schedule.yml에 있습니다.
| 워커 | 일정 |
|---|---|
ProjectSecretsManagerMaintenanceTasksCronWorker |
매분 |
GroupSecretsManagerMaintenanceTasksCronWorker |
매분 |
ProjectSecretsManagerReapOrphanTasksCronWorker |
15분마다 |
GroupSecretsManagerReapOrphanTasksCronWorker |
15분마다 |
ProjectSecretRotationReminderBatchWorker |
매분 |
GroupSecretRotationReminderBatchWorker |
매분 |
ReconcileNamespaceSecretCountsCronWorker |
매일 02:23 |
EmitSecretsStoredBillableEventCronWorker |
매일 04:30 |
유지 관리 작업 재시도 및 정리 규칙#
ProjectSecretsManagerMaintenanceTasksCronWorker와 GroupSecretsManagerMaintenanceTasksCronWorker는 모두 SecretsManagement::BaseMaintenanceTasksCronWorker를 상속합니다.
실행할 때마다 processable(last_processed_at이 STALE_THRESHOLD보다 오래됨)이면서 retryable(retry_count가 MAX_RETRIES 미만)인 작업을 선택합니다.
그런 다음 작업마다 해당하는 프로비저닝 또는 프로비저닝 해제 워커를 다시 큐에 넣습니다.
| 상수 | 값 |
|---|---|
BATCH_SIZE |
100 |
STALE_THRESHOLD |
5분 |
MAX_RETRIES |
3 |
각 작업은 독립적으로 재시도합니다. 한 작업에서 발생한 StandardError는 추적되며 배치의 나머지 작업을 멈추지 않습니다.
MAX_RETRIES에 도달한 작업은 retryable 범위에서 빠지며 크론 워커가 다시 재시도하지 않습니다.
SecretsManagement::BaseReapOrphanTasksCronWorker를 상속하는 ProjectSecretsManagerReapOrphanTasksCronWorker와 GroupSecretsManagerReapOrphanTasksCronWorker가 이렇게 멈춘 행에 대한 안전망입니다.
이 워커는 retry_count가 MAX_RETRIES 이상이고 last_processed_at이 GRACE_PERIOD(30분)보다 오래된 작업을 선택합니다.
그런 다음 작업의 action이 provision이었는지 deprovision이었는지와 관계없이 프로비저닝 해제 서비스를 직접 호출합니다.
프로비저닝 해제 서비스는 작업의 스냅샷 경로에 있는 OpenBao 상태를 모두 정리하고, Secrets Manager 행이 아직 있으면 삭제하며, 작업도 삭제합니다.
이 과정을 거치면 부모 프로젝트나 그룹에는 Secrets Manager도 유지 관리 작업도 남지 않으므로 사용자가 다시 초기화할 수 있습니다.
GRACE_PERIOD는 STALE_THRESHOLD와 프로비저닝 및 프로비저닝 해제 리스 기간보다 모두 깁니다.
따라서 정리 워커는 자연스럽게 재시도될 워커나 스스로 만료될 리스와 경합하지 않습니다.
로테이션 알림 워커#
SecretsManagement::ProjectSecretRotationReminderBatchWorker와 SecretsManagement::GroupSecretRotationReminderBatchWorker는 모두 SecretsManagement::BaseSecretRotationReminderBatchWorker를 상속합니다.
실행할 때마다 서비스 클래스(ProjectSecretRotationBatchReminderService 또는 GroupSecretRotationBatchReminderService)를 반복해서 호출합니다.
이 반복은 30초의 MAX_RUNTIME으로 제한되며, 배치가 서비스의 배치 크기보다 적은 수의 처리 행과 건너뛴 행을 반환하면 멈춥니다.
SecretsManagement::SecretRotationReminderBatchWorker는 더 이상 사용되지 않습니다. 아무 작업도 하지 않으며 더 이상 예약되지 않습니다.
이전 버전이 큐에 넣은 job이 계속 실행될 수 있도록 이 클래스는 제거될 때까지 유지됩니다.
로테이션 알림 크론은 로테이션 정보를 기록한 뒤 시크릿 업데이트가 실패할 때 남는 고아 로테이션 정보 행도 정리합니다. 이러한 고아 행은 예상된 것이며 해결해야 할 문제가 아닙니다.
인가 정책#
정책 클래스는 ee/app/policies/secrets_management/ 아래에 있습니다.
모든 정책 클래스는 자체적으로 권한을 정의하지 않고 부모 리소스의 정책에 전적으로 위임합니다.
| 정책 | 위임 대상 |
|---|---|
ProjectSecretsManagerPolicy |
@subject.project를 통한 프로젝트의 ProjectPolicy |
GroupSecretsManagerPolicy |
@subject.group을 통한 그룹의 GroupPolicy |
ProjectSecretPolicy |
@subject.project를 통한 프로젝트의 ProjectPolicy |
GroupSecretPolicy |
@subject.group을 통한 그룹의 GroupPolicy |
ProjectSecretsPermissionPolicy |
@subject.resource를 통한 권한의 리소스 |
GroupSecretsPermissionPolicy |
@subject.resource를 통한 권한의 리소스 |
NamespaceEnrollmentPolicy |
@subject.namespace를 통한 등록의 네임스페이스 |
실제 권한 규칙은 EE::ProjectPolicy와 EE::GroupPolicy에 있습니다.
둘 다 같은 구조를 따릅니다.
- 조건 하나가 대상에 대해 Secrets Manager를 사용할 수 있는지 확인합니다(
SecretsManagement::Availability.for_project?또는.for_group?). 사용할 수 없으면admin_project_secrets_manager,read_project_secrets,create_project_secrets,update_project_secrets,delete_project_secrets,create_secrets_manager_api_jwt(프로젝트), 또는provision_secrets_manager,read_secrets_manager,read_secrets_permission,update_secrets_permission,delete_secrets_permission(그룹) 같은 권한이 모두 차단됩니다. - 자격 인식이 활성화되어 있으면 추가 조건이
SecretsManagement::Entitlement.for를 호출해 자격 상태에 따라 읽기, 쓰기, JWT 발급을 차단합니다. - 최상위 그룹이
trial_eligible이고 네임스페이스가 베타 코호트에 속하지 않으면, 최상위 그룹이 아닌 모든 프로젝트와 모든 그룹의 조건이 자격 인식 경로에서 읽기와 삭제를 차단합니다. 트라이얼은 최상위 그룹만 시작할 수 있기 때문입니다. EE::GroupPolicy에서는 별도의 등록 허용 조건이 등록 및 트라이얼 권한(create_secrets_manager_enrollment,delete_secrets_manager_enrollment,read_secrets_manager_enrollment,enable_secrets_manager_add_on,start_secrets_manager_trial)을 제어합니다.
Rails 권한을 통과하는 것만으로는 충분하지 않습니다. OpenBao ACL 정책은 두 번째 독립 계층입니다. 작업이 성공하려면 호출자의 JWT에도 해당 작업에 맞는 OpenBao 정책이 있어야 합니다. Rails 권한은 있지만 일치하는 OpenBao 정책이 없는 사용자는 OpenBao에서 여전히 권한 오류를 받습니다.
내부 API#
ee/lib/api/internal/secrets_manager.rb는 POST /api/v4/internal/secrets_manager/audit_logs를 노출합니다.
사용자가 아니라 OpenBao가 이 엔드포인트를 호출해 작업마다 감사 로그 한 줄을 전달합니다.
요청 처리는 다음과 같습니다.
- 요청 본문은 1메가바이트로 제한됩니다.
- 인증은
Gitlab-Openbao-Auth-Token헤더와 비교하는 공유 시크릿으로 이루어집니다. 이 시크릿은Gitlab.config.openbao.authentication_token_secret_file_path의 경로에 있는 파일에서 읽습니다. 이 경로는Pathname#realpath로 해석하고 허용 루트 경로 목록(Rails.root, 또는 개발 및 테스트 환경이 아니면/etc/gitlab/)과 대조해 확인하여 심볼릭 링크 기반 경로 탐색을 차단합니다. - OpenBao는 작업마다
request유형 하나와response유형 하나, 두 줄을 내보냅니다. 결과는response줄에만 있으므로request줄은 처리 전에 버립니다. - 처리는 비동기입니다. 엔드포인트는
SecretsManagement::AuditLogWorker를 큐에 넣고 데이터베이스를 기다리지 않고202 Accepted를 반환합니다.
감사 로그 처리#
ActiveModel을 기반으로 하는 SecretsManagement::AuditLog가 OpenBao의 원시 JSON을 파싱합니다.
작업(create, read, update, delete)과 경로가 시크릿의 데이터 경로 또는 메타데이터 경로와 일치했는지에 따라, 프로젝트와 그룹 범위 모두에서 각 줄을 여덟 가지 이벤트 유형 중 하나로 매핑합니다.
secrets_manager_read_project_secret
secrets_manager_create_project_secret
secrets_manager_update_project_secret
secrets_manager_delete_project_secret
secrets_manager_read_group_secret
secrets_manager_create_group_secret
secrets_manager_update_group_secret
secrets_manager_delete_group_secret
생성과 업데이트는 모두 시크릿의 메타데이터 경로를 update 작업으로 기록합니다.
AuditLog는 custom_metadata에 update_completed_at이 있는 메타데이터 쓰기만 기록하며, 이 값은 업데이트의 마지막 쓰기만 설정합니다. 따라서 생성은 데이터 경로 쓰기에서 발생한 생성 이벤트만 기록하고, 업데이트는 한 번만 기록합니다.
ProjectSecret과 GroupSecret은 ActiveRecord 모델이 아니므로 그 자체로 감사 대상이 될 수 없습니다.
대신 AuditLog#log!는 프로젝트 또는 그룹을 범위와 대상으로 하여 Gitlab::Audit::Auditor.audit를 호출해 GitLab 감사 이벤트를 만듭니다.
Sidekiq은 쓰기가 이미 커밋된 job을 다시 전달할 수 있으며, 감사 테이블에는 중복 삽입을 막는 자연 키 보호가 없습니다.
따라서 SecretsManagement::AuditLogWorker는 OpenBao의 요청 ID를 키로 하고 6시간 동안 캐시되는 중복 처리 방지 장치 두 개로 이 처리를 감쌉니다.
- 마커 하나는 감사 이벤트 쓰기 자체를 보호합니다.
- 두 번째 마커는 시크릿 읽기 과금 이벤트의 발생을 보호합니다. 로컬 과금 경로에는 다운스트림에서 중복을 제거할 자체 이벤트 ID가 없기 때문입니다.
내부 이벤트 추적#
Secrets Manager는 ee/config/events/ 아래의 이벤트 정의와 함께 Gitlab::InternalEventsTracking으로 제품 사용량을 추적합니다.
등록을 제외한 예로는 create_ci_secret, visit_project_secrets_manager, visit_group_secrets_manager, generate_id_token_for_secrets_manager_authentication, enable_ci_secrets_manager_for_project, enable_ci_secrets_manager_for_group, disable_ci_secrets_manager_for_group이 있습니다.
자격 거부 텔레메트리는 SecretsManagement::Entitlement::DenialTelemetry에 있습니다.
등록 및 트라이얼 이벤트는 Secrets Manager 등록을 참고합니다.