코드가 클라우드 Mac으로 푸시되면 CI는 일반적으로 즉시 코드를 체크아웃하고 빌드와 테스트를 수행합니다. 문제는 Git 커밋에 기록된 작성자 이름과 이메일이 수정 가능한 텍스트에 불과하다는 점입니다. 이 정보는 누가 해당 커밋을 만들었다고 주장하는지는 보여 주지만, 그 개발자가 실제로 객체에 서명했다는 사실까지 증명하지는 못합니다. 더 신뢰할 수 있는 방법은 커밋에 SSH 서명을 추가하고, CI에서 통제된 공개 키 목록을 사용해 병합 기준점부터 현재 커밋까지의 전체 범위를 검증하는 것입니다.
이 게이트는 코드 리뷰를 대체하지 않으며 코드의 안전성도 판단하지 않습니다. 대신 빌드 체인에 들어오는 Git 객체가 온전한지, 서명이 유효한지, 서명 키가 현재 코드 제출 권한을 가진 사람의 것인지라는 더 제한적이지만 중요한 문제를 해결합니다.
먼저 검증의 신뢰 경계 정의하기
커밋 검증에는 최소한 다음 세 단계의 판단이 필요합니다.
- Git 객체의 내용과 서명이 일치하는가.
- 서명 공개 키가 허용 서명자 목록에 포함되어 있는가.
- 해당 신원이 커밋 시점에도 제출 권한을 보유하고 있었는가.
첫 번째 단계는 암호학적 서명으로, 두 번째 단계는 저장소에서 관리하는 목록으로 확인합니다. 세 번째 단계에는 여전히 팀의 권한 회수 절차가 필요합니다. git log --show-signature만 실행한 뒤 “Good signature”가 표시된다고 해서 충분한 것은 아닙니다. 유효하지만 승인되지 않은 키도 유효한 서명을 만들 수 있기 때문입니다.
이메일은 표시와 알림에 사용하고, 공개 키는 진위 검증에 사용하며, 허용 서명자 목록은 권한 부여에 사용합니다. 이 세 가지를 같은 것으로 취급해서는 안 됩니다.
목록은 .ci/trusted_signers처럼 변경이 엄격하게 통제되는 별도 디렉터리에 두는 것이 좋습니다. 목록 자체를 수정할 때는 추가 리뷰를 요구해야 합니다. 그래야 제출자가 하나의 변경에서 자신의 공개 키를 추가하는 동시에 코드까지 통과시키는 일을 방지할 수 있습니다.
Git SSH 커밋 서명 구성하기
Git 2.34 이상에서는 SSH 키로 직접 서명할 수 있습니다. 먼저 개발자 로컬 환경에서 서명 형식과 공개 키 경로를 지정합니다.
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgSign true
여기서 구성하는 파일은 공개 키이며, 실제 서명은 이에 대응하는 비공개 키로 생성됩니다. 커밋을 만든 뒤에는 객체에 서명이 실제로 포함되어 있는지 먼저 확인할 수 있습니다.
git cat-file commit HEAD | sed -n '/^gpgsig /,/^[^ ]/p'
허용 서명자 파일의 각 줄에는 신원, 선택적 제약 조건, 공개 키가 들어갑니다. 신원에는 자주 바뀌는 표시 이름이 아니라 안정적인 팀 식별자를 사용해야 합니다.
ci-release namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
ios-team namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5BBBB...
CI가 저장소를 체크아웃한 뒤 Git이 이 파일을 사용하도록 설정합니다.
git config --local gpg.format ssh
git config --local gpg.ssh.allowedSignersFile .ci/trusted_signers
git verify-commit HEAD
비공개 키를 저장소에 넣어서는 안 됩니다. CI의 검증 단계에는 공개 키만 필요합니다. 비공개 키는 서명된 커밋이나 태그를 생성하는 환경에만 있어야 합니다.
전체 커밋 범위에 게이트 적용하기
HEAD만 검증하는 것은 흔한 실수입니다. 공격성 변경이나 승인되지 않은 변경이 이전 커밋에 숨겨진 뒤, 마지막에 추가된 서명 커밋이 겉으로 드러나는 상태만 덮을 수 있습니다. 게이트는 신뢰할 수 있는 기준점 이후의 모든 커밋을 순회해야 합니다.
#!/bin/bash
set -euo pipefail
base="${MERGE_BASE_SHA:?missing MERGE_BASE_SHA}"
head="${HEAD_SHA:?missing HEAD_SHA}"
git cat-file -e "${base}^{commit}"
git cat-file -e "${head}^{commit}"
count=0
while IFS= read -r commit; do
git verify-commit "$commit"
count=$((count + 1))
done < <(git rev-list --reverse "${base}..${head}")
printf 'verified_commits=%s\n' "$count"
MERGE_BASE_SHA는 CI가 대상 브랜치와 병합할 브랜치를 기준으로 계산하거나 신뢰할 수 있는 방식으로 전달해야 합니다. 이를 단순히 HEAD~1로 지정해서는 안 됩니다. 병합 커밋의 경우 기준점 선택이 팀 정책에 부합하는지도 확인해야 합니다. 그렇지 않으면 두 번째 부모 브랜치에서 유입된 객체가 검증에서 누락될 수 있습니다.
게이트 출력에는 커밋 해시와 실패한 단계를 기록해야 하지만, 전체 커밋 메시지나 환경 변수를 공개 로그에 복사해서는 안 됩니다. 실패 시 빌드를 중단하는 편이 출처가 확인되지 않은 산출물을 계속 생성하는 것보다 감사하기 쉽습니다.
얕은 복제, 태그, 키 교체 처리하기
얕은 복제로 인해 스크립트가 잘못 실패하는 경우가 많습니다. 기준 객체가 로컬에 없으면 rev-list가 전체 범위를 계산할 수 없습니다. 검증 전에 대상 브랜치의 필요한 이력을 추가로 가져오고, git cat-file -e로 기준점이 존재하는지 명시적으로 확인해야 합니다. 기준점을 찾지 못했을 때 HEAD만 검증하는 방식으로 축소해서는 안 됩니다.
| 상황 | 올바른 처리 | 사용하지 말아야 할 지름길 |
|---|---|---|
| 얕은 복제에 기준점이 없음 | 필요한 이력을 가져온 뒤 범위를 다시 계산 | 마지막 커밋만 검증 |
| 릴리스 태그 | 서명된 주석 태그를 사용하고 git verify-tag 실행 |
태그 이름만 확인 |
| 기존 키와 새 키의 인계 | 짧은 기간 동안 두 공개 키를 모두 유지 | 키를 즉시 교체해 과거 작업을 실패하게 함 |
| 키 유출 | 즉시 제거하고 해당 키가 서명한 범위를 재검토 | 표시 신원만 수정 |
| 팀을 떠난 구성원 | 허용 서명자 기록에서 삭제 | 일상적인 로그인 권한만 비활성화 |
키를 제거한 뒤에도 과거 커밋을 계속 통과시킬지 미리 결정해야 합니다. 가장 단순한 엄격 모드는 현재 목록을 기준으로 검증하는 방식이며 병합 게이트에 적합합니다. 과거 릴리스를 장기적으로 검증해야 한다면 적용 기간이 포함된 신뢰 기록을 보관하고, 릴리스 태그와 커밋 해시, 당시 사용한 목록 버전을 함께 아카이브해야 합니다.
적용 전 점검과 장애 진단
실제로 병합을 차단하기 전에는 관찰 기간을 먼저 운영할 수 있습니다. 이 기간에는 실패를 기록만 하되 서명되지 않은 릴리스를 허용해서는 안 됩니다. 관찰 기간에는 다음 항목을 중점적으로 확인해야 합니다.
- 개발자가 사용하는 Git 버전이 SSH 서명을 지원하는가.
- 일반 커밋, 병합 커밋, 자동 생성 커밋 모두에서 명확한 서명자를 찾을 수 있는가.
- CI가 고정된 깊이에 의존하지 않고 전체 기준 이력을 확보했는가.
- 허용 서명자 파일을 수정할 때 독립적인 리뷰가 필요한가.
- 릴리스 태그와 해당 태그가 가리키는 커밋을 각각 검증하는가.
- 봇 신원은 별도 키를 사용하며 개인과 키를 공유하지 않는가.
- 키 교체, 키 유출, 구성원의 팀 이탈에 실행 가능한 절차가 마련되어 있는가.
검증이 실패하면 먼저 git verify-commit --raw <hash>를 사용해 “객체에 서명이 없음”, “서명이 손상됨”, “공개 키가 허용 목록에 없음”을 구분합니다. 그런 다음 저장소 수준의 gpg.format, gpg.ssh.allowedSignersFile 설정과 목록에 기록된 네임스페이스, 키 유형, 줄바꿈을 확인합니다. 이렇게 하면 신원 권한 문제와 불완전한 Git 이력 문제를 분리할 수 있으며, 같은 커밋에 반복해서 다시 서명하는 일을 피할 수 있습니다.
커밋별 검증, 태그 검증, 목록 변경 리뷰가 모두 파이프라인에 포함되면 빌드 시스템은 검토 가능한 출처 체인을 갖게 됩니다. 이 체인은 코드에 결함이 없다는 사실을 증명하지는 못하지만, 어떤 객체가 어떤 승인된 키로 서명되었는지, 확인되지 않은 객체가 왜 후속 빌드로 넘어가지 못했는지를 명확하게 설명할 수 있습니다.
자주 묻는 질문
커밋 작성자 이메일이 맞으면 신뢰해도 되나요?
아닙니다. 이름과 이메일은 누구나 넣을 수 있는 일반 텍스트입니다. 서명으로 개인 키 보유를 확인하고 허용 서명자 목록으로 해당 키가 승인된 신원에 속하는지 별도로 판단해야 합니다.
브랜치의 마지막 커밋만 검증하면 충분한가요?
충분하지 않습니다. 기준 커밋 이후 추가된 모든 커밋이 최종 소스에 영향을 주므로 각각 git verify-commit을 실행해야 하며, 릴리스 태그에는 git verify-tag도 적용해야 합니다.
서명 키를 중단 없이 교체하려면 어떻게 하나요?
새 공개 키를 버전 관리되는 허용 목록에 먼저 추가하고 일정 기간 두 키를 함께 허용합니다. 새 키로 만든 커밋이 검증된 뒤 기존 키를 제거합니다.
다음 개발 작업을 위한 클라우드 Mac 구성
Oak M4, Oak M4 Plus 또는 Oak M4 Pro를 선택하고 팀의 위치에 따라 5개 물리 노드 중에서 대여 구성을 설정하세요.