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
를 저장하기 때문에 보안 정책 리포지터리에 실제로 있는 정책 수가 적은데도 많은 레코드가 있습니다.
-
접근 권한 관리로 보안 정책 디렉터리을 가져오는 데 어려움
- 사용자들은 현재 보안 정책 프로젝트에 액세스하기 위해 프로젝트에 이미 액세스 권한이 있는 경우에도 해당 프로젝트에 적용되는 정책을 보기 위해 필요합니다. 데이터베이스에서 정책을 읽으면 문제가 해결됩니다.
목표
- Gitaly에 대한 호출을 줄이고 데이터베이스에서 읽는 것에 의존합니다. YAML 데이터는 데이터베이스 테이블에 복제되고 읽기 전용 ActiveRecord 모델을 통해 성능에 대한 고민 없이 기능을 구축할 수 있습니다.
- 승인 MR 규칙과 관련된 중복된 열을
approval_project_rules
및approval_merge_request_rules
에서 제거합니다. - 현재 프로세스를 변경하여 모든 관련 레코드를 삭제하고 재생성하는 대신에 영향을 받는 레코드만 업데이트/재생성합니다.
- MR 승인 정책 변경의 평균 전파 기간을 줄입니다.
- 보안 정책에서 수행하는 데이터베이스 쿼리 수를 줄입니다.
제안
현재 보안 정책 아키텍처
그룹이나 프로젝트에 연결된 보안 정책 프로젝트는 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_rules
및approval_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_state
및 match_on_inclusion
(이전 match_on_inclusion_license
)만 저장하고 있습니다.
제안된 아키텍처
위에서 언급된 문제와 제한을 해결하기 위해 우리는 정책 YAML의 모든 필드를 DB에 저장해야 하며, 이는 정책의 최신 버전을 위해 YAML에서 읽는 대신 security_policies
테이블에서 읽을 수 있어야 합니다.
DB 스키마는 정책 YAML을 세밀하게 반영해야하며 다음과 같아야 합니다:
이를 달성하기 위해 우리는 정책이 생성/업데이트/삭제되었을 때만 호출되는 새로운 워커(Security::ScanResultPolicies::SyncWorker
)를 도입하고, Git 리포지터리에서 YAML을 읽어들여 security_policies
테이블과 그 아래의 scan_execution_policy_rules
및 scan_result_policy_rules
테이블에 항목으로 변환할 것입니다. 현재 YAML을 Git 리포지터리에서 읽는 곳 모두에서 우리는 테이블에서 읽어야 하며, 이는 정책의 최신 버전을 위한 SSoT로 작동합니다.
이를 통해 다음을 달성할 수 있습니다:
- 테이블의 값들과 YAML에서 업데이트된 값들을 비교함으로써 정책에서 업데이트된 내용을 알 수 있음
- DB 인덱스 추가로 성능 향상
-
approval_merge_request_rules
및approval_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_rules
와 approval_merge_request_rules
에서 삭제할 것입니다.
단계 6: scan_result_policies
테이블 제거
이 시점에서 기존 테이블인 scan_result_policies
를 제거할 수 있습니다. 왜냐하면 결재 규칙은 이제 scan_result_policy_rules
테이블을 통해 연결되기 때문입니다.