This page contains information related to upcoming products, features, and functionality. It is important to note that the information presented is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the sole discretion of GitLab Inc.
Status Authors Coach DRIs Owning Stage Created
proposed @mcavoj @g.hickman @alan "devops govern" 2023-12-07

보안 정책을 위한 데이터베이스 읽기 모델

이 문서는 진행 중인 작업이며, 최적화된 정책 업데이트 및 전파를 위한 새로운 보안 정책 아키텍처 제안을 나타냅니다.

요약

보안 정책은 보안 정책 프로젝트의 YAML 파일에 저장됩니다. 이 접근 방식에는 Git을 사용한 정책의 버전 관리, 감사 가능 등 많은 장점이 있지만, 성능에 제약 사항이 있습니다. Git 리포지터리에서 읽는 것은 Gitaly에 대한 호출을 필요로 하기 때문에 유연한 기능을 구축하는 데 제한이 있을 수 있습니다.

동기

YAML에서 승인 규칙으로의 정책 동기화 현재 아주 성능이 뛌리지 않으며 삭제 및 재생성되는 승인 규칙 프로세스를 포함합니다. 이것이 필요하지 않더라도 재동기화가 트리거되는 이벤트는 완전히 다시 처리할 필요가 없습니다(예: 프로젝트에 사용자가 추가된 경우 승인 규칙의 승인자만 업데이트하면 됨). 현재 아키텍처는 다음과 같은 제한에 직면합니다:

  • 새로운 기능 구축이 어려움
    • Gitaly에서 YAML을 읽는 한계 때문에 업데이트할 때마다 제한된 정책 수를 유지합니다. 그러나 이 제한은 많은 사용 사례에 부족합니다.
  • 높은 리소스 소비
    • Security::ProcessScanResultPolicyWorker는 장시간 실행되는 워커로 Gitaly에 호출을 하는데, 승인 규칙을 삭제하고 생성하며, 프로젝트의 모든 오픈 MR을 업데이트합니다. 프로젝트에 오픈된 MR이 많은 경우 완료하는 데 몇 분이 소요될 수 있습니다. 현재 정책을 선택적으로 동기화할 수 있는 기능을 아직 가지고 있지 않습니다.
  • 장애 허용 성능 감소
    • 워커가 수행하는 모든 작업은 원자적이어야 하므로 한 단계가 실패하면 시스템의 최종 상태가 일관성이 없게 될 수 있습니다.
  • 승인 규칙 테이블과 scan_result_policies에 중복 데이터가 저장됨
    • 모든 MR에 승인 규칙 테이블에 YAML의 필드를 저장하기 때문에 중복되어 많은 추가 디스크 공간을 사용합니다.
    • scan_result_policies에서 project_id를 저장하기 때문에 보안 정책 리포지터리에 실제로 있는 정책 수가 적은데도 많은 레코드가 있습니다.
  • 접근 권한 관리로 보안 정책 디렉터리을 가져오는 데 어려움
    • 사용자들은 현재 보안 정책 프로젝트에 액세스하기 위해 프로젝트에 이미 액세스 권한이 있는 경우에도 해당 프로젝트에 적용되는 정책을 보기 위해 필요합니다. 데이터베이스에서 정책을 읽으면 문제가 해결됩니다.

목표

  1. Gitaly에 대한 호출을 줄이고 데이터베이스에서 읽는 것에 의존합니다. YAML 데이터는 데이터베이스 테이블에 복제되고 읽기 전용 ActiveRecord 모델을 통해 성능에 대한 고민 없이 기능을 구축할 수 있습니다.
  2. 승인 MR 규칙과 관련된 중복된 열을 approval_project_rulesapproval_merge_request_rules에서 제거합니다.
  3. 현재 프로세스를 변경하여 모든 관련 레코드를 삭제하고 재생성하는 대신에 영향을 받는 레코드만 업데이트/재생성합니다.
  4. MR 승인 정책 변경의 평균 전파 기간을 줄입니다.
  5. 보안 정책에서 수행하는 데이터베이스 쿼리 수를 줄입니다.

제안

현재 보안 정책 아키텍처

그룹이나 프로젝트에 연결된 보안 정책 프로젝트는 security_orchestration_policy_configurations 테이블에 해당 항목을 가지고 있습니다. 정책을 읽거나 쿼리하기 위해서는 YAML을 정책 프로젝트에서 읽어야 해서 Gitaly에 대한 RPC 호출이 필요합니다. 이것은 보안 정책이 많은 수의 프로젝트/그룹으로 구성되어 있을 때 성능이 나쁘게 작용합니다.

YAML 파일로 저장된 MR 승인 정책은 approval_project_rules 테이블을 통해 승인 규칙으로 동기화됩니다. 현재 테이블에는 MR 승인 정책과 관련된 이러한 필드가 저장되어 있습니다:

  • scanners
  • vulnerabilities_allowed
  • severity_levels
  • report_type
  • vulnerability_states

같은 필드들이 각 개별 MR에 연결되도록 approval_merge_request_rules에도 중복되어 저장됩니다. 이들은 중복되어 있으며 approval_project_rules의 이러한 필드 값이 변경되면 approval_merge_request_rules도 업데이트됩니다.

Security::ProcessScanResultPolicyWorker는 정책 프로젝트의 정책을 읽어서 승인 규칙으로 변환하는 역할을 합니다. 워커는 다음과 같은 순서대로 작업을 수행합니다:

  • approval_project_rulesapproval_merge_request_rules 삭제
  • 정책 프로젝트에서 YAML 읽기 (Gitaly 호출)
  • YAML을 사용하여 approval_project_rules에 행 생성
  • 각 개별 MR에 대해 approval_merge_request_rules 업데이트

YAML의 정책 변경 사항의 정확한 차이점을 효율적으로 얻을 수 없기 때문에 승인 규칙을 삭제하고 다시 생성합니다. 또한 YAML의 각 정책에 대해 고유 식별자가 없기 때문에 이는 Gitaly에 대한 호출을 수반하고 있습니다.

이 워커는 다음 이벤트가 발생할 때 호출됩니다:

  • 그룹에 정책이 구성된 프로젝트가 생성될 때
  • 사용자가 프로젝트에 추가/제거될 때
  • 프로젝트에 보호된 브랜치가 추가/제거될 때
  • 프로젝트에 대한 정책이 생성/업데이트/삭제될 때

이를 피하기 위해 우리는 scan_result_policies 테이블(Security::ScanResultPolicyRead 모델)을 만들었습니다. 이 테이블은 정책 프로젝트에서 MR 승인 정책을 읽기위한 읽기 모델로 사용되어야 하지만, 현재로서는 테이블에 필요한 모든 필드를 저장하지 않고 있습니다. 우리는 테이블에 role_approvers, license_statematch_on_inclusion (이전 match_on_inclusion_license)만 저장하고 있습니다.

er다이어그램 security_orchestration_policy_configurations ||--o{ scan_result_policies : " " security_orchestration_policy_configurations ||--o{ approval_project_rules : " " security_orchestration_policy_configurations ||--o{ approval_merge_request_rules : " " scan_result_policies ||--|| approval_project_rules : " " scan_result_policies ||--|| approval_merge_request_rules : " " security_orchestration_policy_configurations { int project_id int namespace_id string security_policy_management_project_id } scan_result_policies { int security_orchestration_policy_configuration_id int orchestration_policy_idx text license_states boolean match_on_inclusion int role_approvers int age_value smallint age_operator smallint age_interval jsonb vulnerability_attributes int project_id jsonb project_approval_settings smallint commits } approval_project_rules { int scan_result_policy_id int security_orchestration_policy_configuration_id int orchestration_policy_idx text scanners smallint vulnerabilities_allowed text severity_levels smallint report_type text vulnerability_states } approval_merge_request_rules { int merge_request_id int scan_result_policy_id int security_orchestration_policy_configuration_id int orchestration_policy_idx text scanners smallint vulnerabilities_allowed text severity_levels smallint report_type text vulnerability_states }

제안된 아키텍처

위에서 언급된 문제와 제한을 해결하기 위해 우리는 정책 YAML의 모든 필드를 DB에 저장해야 하며, 이는 정책의 최신 버전을 위해 YAML에서 읽는 대신 security_policies 테이블에서 읽을 수 있어야 합니다.

DB 스키마는 정책 YAML을 세밀하게 반영해야하며 다음과 같아야 합니다:

er다이어그램 security_orchestration_policy_configurations ||--|{ security_policies : " " security_policies ||--o{ scan_execution_policy_rules : " " security_policies ||--o{ scan_result_policy_rules : " " security_policies }o--o{ projects : "via security_policies_projects" scan_result_policy_rules ||--|| approval_group_rules : " " scan_result_policy_rules ||--|| approval_project_rules : " " scan_result_policy_rules ||--|| approval_merge_request_rules : " " scan_result_policy_rules ||--|| software_license_policies : " " scan_result_policy_rules ||--|| scan_result_policy_violations : " " security_orchestration_policy_configurations { int project_id int namespace_id int security_policy_management_project_id } security_policies { int security_orchestration_policy_configuration_id int policy_index text checksum text name text type text description boolean enabled jsonb policy_scope jsonb actions jsonb approval_settings } projects { int id text name } scan_execution_policy_rules { int security_policy_id int rule_index text checksum jsonb content } scan_result_policy_rules { int security_policy_id int rule_index int type text checksum jsonb content } approval_group_rules { int namespace_id int scan_result_policy_rule_id } approval_project_rules { int project_id int scan_result_policy_rule_id } approval_merge_request_rules { int merge_request_id int scan_result_policy_rule_id } software_license_policies { int project_id int scan_result_policy_rule_id } scan_result_policy_violations { int project_id int merge_request_id int scan_result_policy_rule_id }

이를 달성하기 위해 우리는 정책이 생성/업데이트/삭제되었을 때만 호출되는 새로운 워커(Security::ScanResultPolicies::SyncWorker)를 도입하고, Git 리포지터리에서 YAML을 읽어들여 security_policies 테이블과 그 아래의 scan_execution_policy_rulesscan_result_policy_rules 테이블에 항목으로 변환할 것입니다. 현재 YAML을 Git 리포지터리에서 읽는 곳 모두에서 우리는 테이블에서 읽어야 하며, 이는 정책의 최신 버전을 위한 SSoT로 작동합니다.

이를 통해 다음을 달성할 수 있습니다:

  • 테이블의 값들과 YAML에서 업데이트된 값들을 비교함으로써 정책에서 업데이트된 내용을 알 수 있음
  • DB 인덱스 추가로 성능 향상
  • approval_merge_request_rulesapproval_project_rules 테이블에 중복되는 데이터 저장을 줄여서 DB 크기를 줄임
  • scan_result_policies 행의 수를 줄임
  • scan_result_policies에 중복되는 데이터 저장을 줄여서 규칙 데이터가 분해되고 중복된 project_approval_settings가 없어지므로 DB 크기를 줄임

현재 ‘모든 보호된 브랜치에 대한 그룹 수준 MR 승인 규칙을 허용’에 대한 계속되는 노력 중입니다. 이것은 그룹 정책에 대해 approval_group_rules을 정의할 수 있게 해줄 것입니다. 이는 모든 프로젝트에 대해 approval_project_rules을 복사할 필요가 없어지므로 정책 변경을 전파하는 데 필요한 워커 수를 줄일 것입니다.

디자인 및 구현 세부 정보

단계 1: 새 테이블 추가

첫 번째 단계로 security_policies, scan_result_policy_rules, scan_execution_policy_rules 테이블을 도입해야 합니다. 이러한 테이블과 열은 YAML의 필드에 매핑됩니다.

단계 2: DB 테이블로 정책을 동기화하기 위한 새로운 워커 도입

새로운 워커는 YAML을 security_policies 테이블의 행으로 읽고 변환하는 책임을 질 것입니다. checksum을 사용하여 정책이 변경되어 다시 빌드가 필요한지, 아니면 순서만 바뀐 것인지를 결정할 수 있습니다.

우리는 변경 사항을 비교하고 업데이트된 열을 기반으로 정책 변경을 전파할 수 있습니다. 예를 들어:

  • actions가 업데이트되면, 우리는 결재 규칙의 결제자를 업데이트할 수 있습니다.
  • rules가 업데이트되면, 관련된 결재 규칙의 업데이트를 트리거할 수 있습니다.

단계 3: 모든 기존 정책을 security_policies 테이블로 이관

기존 정책을 모두 읽고 security_policies 테이블을 채우는 DB 이관을 도입해야 합니다.

단계 4: Security::ProcessScanResultPolicyWorker 업데이트하여 security_policies에서 읽기

이 워커를 수정하여 security_policies에서 읽고, 다음 이벤트에 대해서만 호출하도록합니다:

  • 프로젝트에서 사용자 추가/제거
  • 프로젝트에서 보호된 브랜치 추가/제거

이것은 워커의 책임을 security_policies에 연결된 결재 규칙만 해당 이벤트에 따라 업데이트하는 것으로 변경할 것입니다.

단계 5: 결재 규칙 테이블에서 열 삭제

이 단계에서는 security_policies로 이관된 열을 approval_project_rulesapproval_merge_request_rules에서 삭제할 것입니다.

단계 6: scan_result_policies 테이블 제거

이 시점에서 기존 테이블인 scan_result_policies를 제거할 수 있습니다. 왜냐하면 결재 규칙은 이제 scan_result_policy_rules 테이블을 통해 연결되기 때문입니다.

링크