LabHub
배우기 러닝패스 코스

CI/CD 파이프라인 · 파이프라인의 비밀 취급 · 이론

비밀은 로그와 레이어에 남는다

LabHub 에서 이어서 보기

한 줄 요약

파이프라인에서 비밀이 새는 자리는 거의 정해져 있다. 빌드 로그, 이미지 레이어와 설정 이력, 저장소 커밋 이력, 산출물 네 곳이다. 그리고 한 번 샌 비밀에 대한 대응은 지우는 것이 아니라 폐기하고 새로 발급하는 것이다.

왜 이게 필요했나

비밀을 다루는 실수는 대개 악의가 아니라 편의에서 나온다. 디버깅하려고 환경 변수를 통째로 찍었고, 빌드 인자로 토큰을 넘기는 것이 가장 간단했고, 설정 파일에 값을 넣은 채로 커밋했다. 각각은 한 사람이 몇 분 아끼려고 한 일인데, 결과는 그 값을 읽을 수 있는 사람의 범위가 통째로 넓어지는 것이다.

네 자리를 하나씩 보자.

마스킹은 대책이 아니라 최후의 방어선이다

CI 플랫폼들은 로그에서 알려진 비밀 값을 가려 준다. 도움은 되지만 대책으로 삼으면 안 된다. 이유는 단순하다. 마스킹은 아는 문자열과 똑같은 것 만 가릴 수 있다. 값이 조금이라도 변형되면 그대로 보인다.

그래서 순서는 이렇다. 먼저 찍지 않게 만들고, 마스킹은 실수를 줄여 주는 마지막 그물로 둔다. GitHub 은 자체 비밀이 아닌 민감한 값도 ::add-mask:: 로 등록하라고 권하는데, 이것도 같은 한계를 그대로 안고 있다. 그리고 포크 저장소에서 촉발된 워크플로에는 GITHUB_TOKEN 을 제외하고 비밀이 전달되지 않는다. 이것은 마스킹보다 훨씬 강한 보호이고, 경계를 나누는 쪽이 가리는 쪽보다 낫다는 원칙을 그대로 보여 준다.

빌드 인자와 런타임 비밀

Docker 공식 문서의 문장이 결론을 그대로 적어 준다. "빌드 인자와 환경 변수는 빌드에 비밀을 넘기기에 적절하지 않다. 최종 이미지에 남기 때문이다." 값을 쓰고 지워도 이미지가 담고 있는 빌드 기록에 남는다.

대신 쓰는 것이 빌드 시점의 비밀 마운트다.

# 값은 그 RUN 명령이 도는 동안에만 존재하고, 레이어에 남지 않는다RUN --mount=type=secret,id=npmtoken \    NPM_TOKEN="$(cat /run/secrets/npmtoken)" npm ci

기본 마운트 위치는 /run/secrets/<id> 이고, 그 명령이 끝나면 사라지며 이미지 레이어에 보존되지 않는다.

런타임 비밀은 또 다른 이야기다. 실행할 때 필요한 값은 이미지가 아니라 실행 환경이 준다. 환경 변수로 주면 편하지만, 프로세스 목록과 자식 프로세스, 오류 보고, 덤프에 함께 실린다. 파일로 주는 편이 대개 낫고, 그때는 두 가지를 정한다. 권한(그 프로세스만 읽을 수 있게 좁히고 디렉터리 권한까지 본다)과 수명(언제 사라지는가, 회전하면 다시 읽는가).

짧은 수명이 구조적으로 낫다

수명이 긴 자격증명은 "언제 샜는지 모른다" 는 문제를 안고 있다. 몇 달 전에 로그로 새어 나간 토큰이 지금도 유효하다. 수명이 짧은 자격증명은 새어 나간 값의 쓸모에 시효를 건다. 유출을 못 막는 대신 피해 창을 몇 분으로 줄인다.

같은 이유로 권한 범위도 좁게 준다. 파이프라인이 읽기만 필요하면 읽기만 준다. 이것은 유출을 막는 조치가 아니라 유출됐을 때의 피해를 정하는 조치다.

유출 뒤의 대응은 폐기와 재발급이다

여기서 가장 자주 틀린다. 값이 커밋에 들어갔을 때 사람들은 이력을 고쳐 지우려 한다. 이력 수정은 이미 복제된 사본, 포크, 캐시, 로그, 백업에는 아무 영향을 주지 않는다. 그 사이에 값을 읽은 사람이 있었는지 알 수 없다는 것이 핵심이다.

그래서 순서는 언제나 같다. 먼저 그 자격증명을 폐기한다. 새 값을 발급한다. 쓰는 곳을 바꾼다. 그다음에야 이력 정리와 재발 방지를 이야기한다. 이력에서 지우는 일은 첫 단계가 아니고, 대개 가장 덜 중요한 단계다.

현장에서 만나는 모습

참고

다음 실습에서 할 것

비밀이 새는 네 자리를 셸과 git, skopeo 로 하나씩 재현하고 막는다. 먼저 환경 변수를 찍는 단계를 넣어 로그에 값이 남는 것을 확인하고, 마스킹 흉내를 낸 필터를 붙인 다음 base64 로 바꿔 찍어 그 필터를 그대로 통과시키는 반례를 만든다. 그다음 /opt/images 의 oci-archive 를 skopeo inspect --raw 로 열어 이미지가 파일 시스템만이 아니라 설정과 이력을 함께 담는다는 것을 눈으로 확인한다. 커밋 이력 쪽은 값을 커밋했다가 지운 뒤 옛 커밋에서 그대로 꺼내 보는 것으로 다룬다. 마지막에는 비밀 파일의 권한과 수명을 검사하는 스크립트와, 유출 대응 절차를 폐기부터 적은 점검표를 만든다.