Devin.KR

산출물 무결성과 출처

55분 안팎

학습 목표

변경 ID·digest·테스트 결과를 묶습니다.

개념

파일을 받았다는 사실만으로 실행하지 않습니다

안내판 이미지 tar와 릴리스 명세를 전달받았을 때 서로 같은 실행의 결과인지 먼저 확인해야 합니다. 이 레슨은 전달 중 내용 변경을 탐지하고 변경 ID·이미지 ID·테스트 요약을 한 묶음으로 읽습니다. 파일 이름이 같다는 이유로 예전 JAR나 다른 tar를 사용하는 실수를 줄이는 것이 목표입니다. 체크섬 검증을 제작자의 신원 확인과 혼동하지 않으며 후속 배포가 사용할 자료의 경계를 구체적으로 정의합니다.

해시 대상을 먼저 확인합니다

SHA256은 입력 바이트에서 계산하는 값입니다. tar의 archiveSha256은 이미지 아카이브 파일 전체에 대한 해시이고 jarSha256은 JAR 파일에 대한 해시입니다. imageDigest는 Docker inspect가 반환한 로컬 이미지 config ID이며 앞의 두 파일 해시와 같은 대상으로 계산하지 않습니다. 모두 64자리처럼 보인다는 이유로 서로 대체할 수 없습니다. 필드 이름과 실제 계산 위치를 함께 읽으면 어떤 파일을 비교해야 하는지 분명해집니다.

로컬 이미지와 레지스트리 식별을 구분합니다

이번 명세는 digestKind=local-image-id이며 registryDigest는 null입니다. imageRef와 imageDigest는 같은 로컬 이미지 ID로 맞춥니다. 레지스트리 manifest 또는 index digest를 만들어 기록하지 않습니다. 다음 daemon에서는 전달 tar를 검증하고 docker load한 뒤 inspect의 실제 ID와 플랫폼을 비교합니다. 로컬 config ID를 repository@digest에 붙여 pull하려는 시도는 다른 종류의 식별값을 사용한 오류입니다.

명세의 출처 연결을 읽습니다

changeId는 검사 대상 변경을 연결하고 testSummary는 그 실행의 tests, failures, errors, skipped, executed를 담습니다. imageDigest는 빌드 결과를 식별하고 imageArchive와 archiveSha256은 전달 파일을 가리킵니다. 파일에 값이 있다는 사실만으로 참이라고 하지 않습니다. 같은 실행에서 생성된 기록과 전달 내용 비교가 필요합니다. 필드만 나열한 문서보다 이 연결 관계를 설명할 수 있어야 다음 사람이 재현할 산출물을 올바르게 고릅니다.

체크섬 목록의 파일 범위를 고정합니다

checksums.json은 release.json, test-summary.json, notice-image.tar의 해시만 담습니다. 검증기는 키 집합이 정확히 이 세 이름인지 확인하여 임의 절대 경로나 상위 폴더 파일을 읽지 않습니다. 목록 자체를 목록에 넣으면 자기 해시를 다시 바꾸는 문제가 생기므로 제외합니다. 그 대신 목록의 신뢰는 전달 채널과 접근 권한으로 다룹니다. 파일 범위를 명확히 하면 zip을 받은 위치가 달라도 같은 상대 이름으로 검사할 수 있습니다.

원본 바이트를 다시 계산합니다

verify는 전달받은 각 파일을 실제로 읽어 SHA256을 계산하고 목록의 값과 비교합니다. JSON을 파싱한 뒤 다시 정렬해서 해시하지 않습니다. 공백 하나나 마지막 줄바꿈 하나도 원본 바이트 변경으로 탐지해야 하기 때문입니다. 의미가 같더라도 재직렬화하면 체크섬이 달라질 수 있습니다. 이는 오류가 아니라 전달받은 바이트가 생성 시점과 달라졌다는 관측이며 변경 이유를 검토한 뒤 새 릴리스로 발급할지 판단합니다.

내용 검사도 함께 수행합니다

해시가 일치하면 다음으로 명세와 요약의 관계를 확인합니다. 릴리스의 testSummary와 독립 test-summary.json이 같아야 하며 실행 수가 있고 실패·오류가 없어야 합니다. archiveSha256은 tar 실측값과 맞아야 하고 변경 ID와 이미지 ID의 형식도 확인합니다. 체크섬만 맞으면 의미가 틀린 파일도 통과할 수 있으므로 구조와 정책 조건을 별도로 검사합니다. 이 과제는 fixture에서 이런 구분을 연습하도록 설계되어 있습니다.

변조 fixture의 의미를 이해합니다

검사기는 원본 묶음을 seal한 뒤 verify 성공을 확인합니다. 이어 release.json 끝에 공백 하나를 추가하고 다시 verify하여 checksum mismatch를 기대합니다. 공백 추가 뒤 JSON은 여전히 읽히지만 원본 바이트는 달라집니다. 따라서 JSON 파싱만 검사하는 구현으로는 이 요구를 충족하지 못합니다. starter의 verify에는 해시 비교가 꺼져 있어 이 fixture를 거부하지 못합니다. 조건을 복구하여 변형을 실제로 차단합니다.

불일치 때 목록을 다시 만들지 않습니다

파일이 달라졌는데 seal을 다시 호출하면 변형된 파일에 맞는 새 체크섬을 만들어 검사 목적을 지웁니다. 수신자의 작업은 verify이며 seal은 생성자가 전달 묶음을 완성할 때 수행합니다. checksum mismatch가 나오면 파일을 격리하고 원본 전달 경로와 생성 실행을 확인합니다. 신뢰한 생성자에게 다시 받거나 검토된 변경으로 재발급합니다. 오류를 없애려고 기대값을 새 값으로 덮어쓰는 것은 원인 설명과 다른 행동입니다.

해시는 서명이 아닙니다

누군가 release.json과 checksums.json을 함께 바꾸면 일반 체크섬 비교는 통과할 수 있습니다. 이 레슨의 완료를 공급망 신뢰 전체를 해결한 것으로 표현하지 않습니다. 제작자 인증에는 별도 신뢰 근거와 서명 검증 또는 접근 통제가 필요합니다. changeId의 형식 검사도 실제 저장소 출처를 인증하지 않습니다. 무엇을 탐지하고 무엇을 탐지하지 못하는지 정확히 적으면 동료가 체크섬 결과를 지나치게 믿는 실수를 줄일 수 있습니다.

큰 파일은 나누어 읽습니다

sha 함수는 파일을 1MiB씩 읽어 hashlib 객체에 갱신합니다. 이미지 tar 전체를 메모리에 한 번에 올리지 않아 파일이 커져도 메모리 사용을 제한합니다. 청크 경계가 달라져도 같은 순서의 바이트라 최종 SHA256은 동일합니다. 파일을 해시하는 동안 다른 프로세스가 내용을 바꾸지 않도록 CI 산출물을 완성한 뒤 전달합니다. 해시 계산이 끝나기 전에 파일을 업로드하는 경쟁도 전송 절차에서 피해야 합니다.

전달과 실행 검사를 분리합니다

PASS integrity는 파일 바이트와 교육용 명세 정책을 확인한 결과입니다. tar가 실제 Docker 이미지이고 서비스가 응답하는지는 별도 도구 검사가 필요합니다. fixture의 tar에는 가짜 표식이 있으므로 load 입력으로 쓰지 않습니다. 미션의 실제 경로만 docker save로 tar를 만들며 후속 환경은 load와 inspect를 수행해야 합니다. 파일 검사가 성공했다고 /health나 데이터 보존까지 확인했다고 말하지 않습니다.

오류 메시지를 대상 파일에 연결합니다

checksum mismatch: release.json은 명세 원본 바이트가 목록과 다르다는 뜻입니다. archive mismatch는 명세 안의 해시와 tar의 관계가 어긋났다는 뜻이며, invalid test summary는 진행 정책이나 중복 요약이 맞지 않다는 뜻입니다. unexpected checksum paths는 파일 집합이 계약과 다릅니다. 각 경우에 파일을 덮어쓰기 전에 어느 관계가 깨졌는지 기록합니다. 오류를 한꺼번에 무결성 실패라고만 쓰면 수정해야 할 경계가 보이지 않습니다.

제출물을 받는 사람의 순서로 정리합니다

제출자는 네 파일과 실제 생성 명령, 테스트 범위, 플랫폼, 실행 검증 여부를 함께 남깁니다. 수신자는 먼저 체크섬을 확인하고 명세의 내용 관계를 검사한 뒤 필요한 도구로 이미지와 서비스를 확인합니다. 전체 저장소나 비밀 환경 파일을 전달 자료에 덧붙이지 않습니다. 서재에서는 sha256sum 목록 검사의 배경을 더 읽고, 여기서는 Python 구현으로 운영체제별 명령 차이 없이 같은 파일 비교 계약을 완성합니다.

따라하기

원본 바이트 해시

같은 JSON 의미라도 원본 공백이 다르면 해시가 달라집니다. 아래 출력은 비교 코드를 직접 실행한 결과입니다.

import hashlib
a = b'{"changeId":"abc"}'
b = a + b' '
print(hashlib.sha256(a).hexdigest() == hashlib.sha256(b).hexdigest())

실행 결과

False

비교 관문 복구

ci.py의 verify에서 False로 꺼진 해시 비교를 sha(directory/name)과 expected 비교로 고칩니다.

python3 -m py_compile ci.py

변형 거부 확인

수정 뒤 fixture를 실행하여 integrity original과 integrity tamper rejected가 모두 통과하는지 봅니다. 기록은 solution의 직접 실행 결과입니다.

bash check.sh

실행 결과

PASS gate pass
PASS log pass
PASS gate fail
PASS log fail
PASS report success
PASS report failure
PASS report error
PASS report zero
PASS report all-skipped
PASS report negative
PASS integrity original
PASS integrity tamper rejected
PASS CI artifact verified
PASS pipeline ok
PASS order ok
PASS fresh manifest ok
PASS pipeline failed
PASS order failed
PASS fresh manifest failed
PASS pipeline zero
PASS order zero
PASS fresh manifest zero
PASS pipeline skipped
PASS order skipped
PASS fresh manifest skipped
RESULT: 0 failure(s); fixture only, no Docker build

수신 묶음 검증

미션에서 실제 생성한 artifacts를 읽습니다. 목록을 재생성하지 않고 verify만 호출하여 불일치한 파일 이름과 관계를 조사합니다.

python3 -B ci.py verify artifacts

확인 문제

실습

ci.py의 verify 바이트 비교를 복구하여 공백 하나가 붙은 명세를 거부합니다. 수신 검증 중 seal을 다시 호출하지 않습니다. 검사 코드는 수정하지 않습니다. 로컬 fixture는 실제 Docker 실행을 입증하지 않습니다.

시작 코드·테스트 내려받기

실행 명령

bash check.sh

기대 결과

모든 PASS와 RESULT: 0 failure(s); fixture only, no Docker build

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • CI에서 실패한 테스트 이후의 배포 동작을 설명합니다.