VMArm 기술 기록

클라우드 Mac CI에서 외부 PR을 안전하게 격리하는 방법

클라우드 Mac CI에서 외부 PR을 안전하게 격리하는 방법

팀이 외부 기여자의 PR 제출을 허용하면 클라우드 Mac CI가 다뤄야 할 문제는 더 이상 “코드가 컴파일되는가”에 그치지 않습니다. 빌드 스크립트 자체도 검토가 필요한 입력입니다. 환경 변수를 읽고, 사용자 디렉터리를 탐색하고, 공유 캐시를 변경하거나 이후 작업에서 사용할 도구를 바꿔치기할 수도 있습니다. 따라서 보안 목표를 명확히 해야 합니다. 외부 PR은 컴파일과 테스트를 검증할 수 있어야 하지만, 릴리스 자격 증명에는 접근할 수 없어야 하며 생성된 산출물을 곧바로 정식 릴리스에 전달해서도 안 됩니다.

두 개의 신뢰 경로부터 분리하기

작업을 “비밀 없는 검증”과 “신뢰할 수 있는 릴리스”라는 두 경로로 나눕니다. 전자는 외부 PR을 받아 의존성 해결, 컴파일, 정적 검사, 실제 자격 증명이 필요 없는 테스트만 실행합니다. 후자는 검토와 병합이 끝난 고정 커밋만 받아 서명, 아카이브 및 배포를 담당합니다.

VMArm의 전용 물리 노드에서는 두 경로에 서로 다른 macOS 시스템 사용자를 지정할 수 있습니다. 검증 사용자는 관리자 그룹에 포함하지 않고, 릴리스 인증서를 저장하지 않으며, 릴리스 사용자의 홈 디렉터리를 읽지 못하게 합니다. 릴리스 사용자 역시 검증 사용자가 쓸 수 있는 스크립트, 캐시 또는 도구 디렉터리를 재사용하지 않아야 합니다.

시스템 사용자 격리는 가상 머신 경계가 아닙니다. 일반 스크립트가 실수로 자격 증명을 읽거나 파일을 오염시킬 가능성을 크게 줄여 주지만, 시스템 업데이트, 최소 권한 원칙, 신뢰할 수 없는 코드에 대한 수동 검토를 대신할 수는 없습니다.

권장 경계는 다음과 같습니다.

항목 비밀 없는 검증 신뢰할 수 있는 릴리스
코드 출처 외부 PR의 고정 커밋 검토된 브랜치의 고정 커밋
코드 서명 비활성화 릴리스 절차에 따라 활성화
작업 공간 매번 새로 생성 별도 디렉터리
캐시 폐기 가능, 신뢰 영역 간 공유 금지 신뢰할 수 있는 작업끼리만 공유
빌드 산출물 검사에만 사용 다시 빌드한 뒤 배포
자격 증명 주입하지 않음 필요할 때만 단시간 주입

최소 권한 작업 공간 구성하기

runner가 사용자 홈 디렉터리의 루트에서 장기간 빌드하도록 두지 마십시오. 검증 작업 전용 루트 디렉터리를 만들고 다른 로컬 사용자가 읽지 못하도록 설정합니다. 작업을 시작할 때 임시 디렉터리를 생성하고, 성공 여부와 관계없이 종료 시 정리합니다.

set -euo pipefail

umask 077
RUN_ROOT="$HOME/ci-untrusted"
mkdir -p "$RUN_ROOT"
chmod 700 "$RUN_ROOT"

WORK_DIR="$(mktemp -d "$RUN_ROOT/job.XXXXXX")"
cleanup() {
  chmod -R u+rwX "$WORK_DIR" 2>/dev/null || true
  rm -rf "$WORK_DIR"
}
trap cleanup EXIT INT TERM

cd "$WORK_DIR"
printf 'user=%s\nworkspace=%s\n' "$(id -un)" "$WORK_DIR"

rm -rf를 실행하기 전에 경로가 현재 작업에서 생성한 임시 디렉터리에서 비롯되었는지 반드시 확인해야 합니다. 브랜치 이름을 직접 이어 붙여 경로를 만들면 안 됩니다. 브랜치 이름에는 슬래시, 공백 또는 의도적으로 조작된 문자가 들어갈 수 있으므로 표시용 텍스트로는 적합하지만, 별도 처리 없이 파일 경로에 사용해서는 안 됩니다.

소스 코드를 체크아웃한 뒤 커밋 해시를 기록하고 로그에 출력합니다. 이후 신뢰 경로에서는 계속 변경될 수 있는 브랜치 이름에만 의존하지 말고, 이 불변 식별자를 기준으로 코드를 다시 가져와야 합니다.

빌드 전에 환경에 비밀이 없음을 입증하기

“비밀을 적극적으로 사용하지 않는다”는 것과 “작업이 비밀을 읽을 수 없다”는 것은 다릅니다. runner 서비스가 시작 환경을 상속할 수 있고, Shell 초기화 파일이 토큰을 추가할 수도 있습니다. 프로젝트 스크립트를 호출하기 전에 변수 이름을 검사하고 빌드 명령에는 필요한 변수만 전달해야 합니다.

blocked='TOKEN|SECRET|PASSWORD|PRIVATE|SIGNING|AUTH|CREDENTIAL'
if env | cut -d= -f1 | grep -Eiq "$blocked"; then
  printf '%s\n' "blocked environment variable detected" >&2
  exit 70
fi

env -i \
  HOME="$HOME" \
  PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
  TMPDIR="$WORK_DIR/tmp" \
  xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -derivedDataPath "$WORK_DIR/DerivedData" \
    CODE_SIGNING_ALLOWED=NO \
    build

실제 프로젝트가 패키지 관리자나 사용자 정의 도구에 의존한다면 전체 로그인 환경을 상속하지 말고, 검토를 마친 도구 디렉터리를 PATH에 명시적으로 추가해야 합니다. 변수 이름 검사는 최소한의 방어선일 뿐입니다. 민감한 값은 구성 파일, 키체인, 프록시 설정, Git 자격 증명 도우미에도 존재할 수 있습니다. 따라서 검증 사용자는 빌드할 때마다 변수 몇 개를 임시로 삭제하는 방식이 아니라 처음부터 비어 있는 상태로 유지해야 합니다.

의존성과 캐시 처리하기

외부 코드는 캐시에 내용을 쓸 수 있으며, 이후 신뢰할 수 있는 작업이 같은 캐시를 읽으면 오염의 영향을 받을 수 있습니다. 가장 안전한 방법은 신뢰 영역별로 캐시를 분리하는 것입니다. 외부 PR의 경우 저장소나 작업 번호별로 한 번 더 나누는 것이 좋습니다. 검증 경로에서 릴리스 사용자의 SwiftPM 캐시, 컴파일 모듈 캐시 또는 사용자 정의 바이너리 도구 캐시에 쓰지 못하게 하십시오.

캐시를 복원한 뒤에는 소유자와 권한을 확인하고, 작업 공간 밖을 가리키는 심볼릭 링크를 거부해야 합니다. 실행 도구는 가급적 읽기 전용 기준 디렉터리에서 가져와야 합니다. 프로젝트가 빌드 중 생성한 스크립트는 현재 임시 작업 공간 안에서만 실행할 수 있어야 합니다.

산출물이 신뢰 경계를 넘지 못하게 하기

외부 PR의 컴파일 성공은 해당 커밋이 검증 조건을 통과했다는 사실만 보여 줄 뿐, 생성된 아카이브를 릴리스해도 된다는 뜻은 아닙니다. 검증 작업은 빌드 단계를 바꾸거나 출력 내용을 수정하거나 아카이브에 추가 파일을 삽입할 수 있습니다. 따라서 비밀 없는 경로에서 생성한 앱 패키지, 아카이브 및 내보내기 디렉터리를 신뢰할 수 있는 릴리스 작업으로 넘겨서는 안 됩니다.

신뢰 경로에서는 다음 단계를 수행해야 합니다.

  1. 검토 시스템에서 확인한 커밋 해시를 받습니다.
  2. 릴리스 사용자의 새 작업 공간에 해당 커밋을 다시 체크아웃합니다.
  3. 현재 커밋이 승인된 값과 완전히 일치하는지 확인합니다.
  4. 신뢰할 수 있는 캐시 또는 빈 캐시에서 의존성을 다시 해결합니다.
  5. 테스트를 실행하고 소스 코드에서 아카이브를 다시 생성합니다.
  6. 서명이 필요한 짧은 시간 동안만 자격 증명을 제공합니다.
  7. 커밋 해시, 의존성 잠금 파일의 다이제스트 및 최종 산출물의 다이제스트를 기록합니다.

검증 경로에서 테스트 보고서를 전달해야 한다면 실행할 수 없는 데이터만 전달하고, 신뢰할 수 있는 쪽에서 엄격한 형식으로 파싱해야 합니다. Shell 스크립트, 플러그인, 컴파일 캐시 또는 앱 패키지에 직접 포함될 수 있는 파일은 전달하지 마십시오.

실패 시 정리와 감사를 인수 기준에 포함하기

격리 설계는 실패 경로에서 가장 쉽게 무너집니다. 빌드가 취소되거나 프로세스가 시간 초과되거나 머신이 재시작된 뒤에도 임시 디렉터리, 백그라운드 프로세스 및 로그가 남을 수 있습니다. runner는 각 작업을 시작하기 전에 이전 작업의 잔여물을 확인하고, 종료 단계에서 해당 작업에 속한 자식 프로세스를 종료해야 합니다.

운영에 적용하기 전에 다음 체크리스트로 검증할 수 있습니다.

마지막으로 능동적인 모의 훈련을 한 번 수행하십시오. 테스트 PR을 제출하고 빌드 스크립트가 일반적인 민감 변수 읽기, 릴리스 디렉터리 접근, 공유 캐시에 파일 쓰기를 시도하도록 합니다. 올바른 결과는 스크립트가 “아무것도 찾지 못한 것”이 아니라, 권한과 프로세스 설계상 해당 경로에 처음부터 도달할 수 없는 것입니다. 이 단계까지 마쳐야 외부 기여 검증과 정식 릴리스가 실질적으로 분리됩니다.

자주 묻는 질문

외부 PR에서 코드 서명만 끄면 충분한가요?

아닙니다. 비밀 환경 변수와 자격 증명을 제거하고 사용자, 작업 공간, 쓰기 가능한 캐시를 분리하며 배포 계정 접근도 차단해야 합니다.

외부 PR 작업이 만든 아카이브를 그대로 배포해도 되나요?

권장하지 않습니다. 승인된 커밋 식별자를 신뢰 구간에서 다시 받아 소스부터 재빌드해야 신뢰하지 않는 바이너리가 경계를 넘지 않습니다.

독점 물리 Mac 노드

다음 Xcode 빌드에 사용할 상시 가동 클라우드 Mac을 선택하세요

칩, 메모리, 저장 공간, 결제 주기 및 노드 지역을 확인한 후 구성 절차를 시작하세요. 실제 사용 가능 여부는 콘솔에서 실시간으로 반환되는 상태를 기준으로 합니다.

클라우드 Mac 구성 선택