[서버 침해 대응기 시리즈] 1인 개발자가 크립토재킹 당하고, 서버를 통째로 새로 만든 5일간의 기록
- 1편.내 서버가 6주째 몰래 채굴당하고 있었습니다
- 2편. docker.sock을 Jenkins한테도 AI한테도 주지 마세요 (socket-proxy)
- 3편. 7개 도메인 무중단 HTTPS 이전기 (nginx + certbot)
- 4편. Jenkins 자동배포, 보안이랑 403 싸운 이야기 ← 지금 글
- 5편. 그래서 보안과 편의 사이, 어디에 서야 할까
자, 이제 마지막 관문이에요.
자동배포.
코드 push하면 알아서 빌드되고 배포되는 거요.
이거 하나 붙이면 끝이라고 생각했어요.
솔직히 30분이면 될 줄 알았거든요.
근데요.
2편에서 세운 그 문지기(socket-proxy)가.
제 앞을 막아섰습니다.
시작은 가벼웠어요
Jenkins에 빌드 작업을 하나 만들었어요.
GitHub 연결하고.
빌드 한 번 돌려봤죠.
그리고 첫 번째 벽.
ERROR: unable to upgrade to tcp, received 403
거부당했다는 뜻이에요.
빌드 엔진(buildkit)이 뭔가를 하려는데.
문지기가 "안 돼" 한 거예요.
어? 이상하죠?
빌드도 못 하게 막으면 어쩌라는 거야.
알고 보니 문지기가 일을 잘한 거였어요
원인을 파보니까.
요즘 도커 빌드 엔진(buildkit)은.
빌드할 때 별도 컨테이너를 띄우고 거기 들어가서 작업해요.
근데 "컨테이너에 들어가는" 그게 바로 EXEC이거든요.
2편에서 제가 무조건 막은 그거요.
침해당한 그 무기.
즉 문지기가 정상 작동한 거예요.
EXEC을 막으랬더니 진짜 막은 거죠.
근데 그게 제 빌드까지 막은 거고요.
해결은 의외로 간단했어요.
옛날 방식 빌드 엔진을 쓰면 돼요.
DOCKER_BUILDKIT=0
이 한 줄로 EXEC 안 쓰는 옛날 빌더로 바꿨어요.
빌드 통과.
좋아, 이제 됐다 싶었죠.
근데 이게 시작이었어요.
403이 계속 나왔어요
빌드 다음은 배포예요.
새로 만든 이미지로 컨테이너를 교체하는 단계요.
그랬더니 또.
403 Forbidden
문지기 로그를 봤어요.
이번엔 네트워크를 확인하려다 막혔더라고요.
그래서 네트워크 권한을 열었어요.
다시 빌드.
또 403.
이번엔 볼륨(데이터 저장 공간)을 확인하려다 막혔어요.
볼륨 권한 열었어요.
다시 빌드.
또 403...

두더지 잡기 게임 같았어요
이게 딱 두더지 잡기였어요.
하나 막으면 옆에서 또 튀어나오고.
네트워크 → 볼륨 → 컨테이너 시작/정지...
문지기한테 "이것도 통과" "저것도 통과"를 계속 추가하는 거예요.
여기서 살짝 현타가 왔어요.
어?
이러다 결국 다 열어주는 거 아니야?
그럼 문지기 세운 의미가 없잖아.
보안하려고 막았는데.
배포하려고 다 여는 아이러니.
근데 여기서 제가 지킨 원칙이 하나 있어요.
EXEC만은 끝까지 안 열었어요.
나머지는 정상 배포에 필요한 것들이라 열어도.
침해의 핵심 무기였던 EXEC.
그것만은 막아둔 거죠.
진짜 어이없는 범인도 있었어요
403들을 하나씩 잡고 있는데.
갑자기 다른 에러가 떴어요.
network cymap-network declared as external, but could not be found
없는 네트워크를 찾는다는 거예요.
분명히 설정을 고쳤는데?
한참 헤맸어요.
그리고 깨달았죠.
제가 설정 파일을 고치고.
git commit을 안 했더라고요.
Jenkins는 GitHub에서 코드를 받아서 빌드하거든요.
근데 제 수정은 제 컴퓨터에만 있고.
GitHub엔 옛날 버전이 그대로였어요.
그러니 Jenkins는 계속 옛 설정으로 빌드한 거죠.
솔직히 이거 깨닫고 좀 허탈했어요.
거창한 보안 문제인 줄 알았는데.
그냥 커밋을 안 한 거였어요.
env 파일도 한 건 했어요
거의 다 왔다 싶었는데.
이번엔 환경변수 파일을 못 찾는대요.
비밀번호랑 API 키가 든 파일이요.
이건 보안상 GitHub에 안 올리거든요.
근데 Jenkins는 GitHub에서만 코드를 받으니까.
그 파일을 영원히 못 받는 거예요.
이건 좀 골치 아팠어요.
결국 그 파일을 Jenkins 작업 공간에 직접 넣어주는 방식으로 풀었어요.
비밀 파일은 GitHub 말고 서버에만 둔 거죠.
생각해보면 이게 맞아요.
비밀번호를 GitHub에 올리는 순간.
그것도 보안 사고거든요.
그래서 결국 성공했냐고요
네.
볼륨 권한이 마지막 관문이었어요.
그거 열고 나니까.
드디어.
Finished: SUCCESS
이 한 줄 보는데.
진짜 짜릿하더라고요.
빌드 → 배포 → 상태 확인까지.
전 과정이 자동으로 쭉 흘러갔어요.
이제 코드 push하면.
2분 안에 알아서 빌드되고 배포됩니다.
솔직한 회고
돌아보면.
이 삽질의 8할은 제 실수였어요.
커밋 안 하고.
설정 안 맞추고.
근데 나머지 2할.
socket-proxy랑 싸운 건.
후회 안 해요.
편하자고 docker.sock 통째로 줬다가.
6주 채굴당했잖아요.
그 기억이 있으니까.
403 좀 만나는 게.
차라리 안심이 됐어요.
"아, 막을 건 막고 있구나."
자동화는 편하지만.
편한 만큼 위험도 같이 자동화되거든요.
결국 핵심은 균형이에요
이번 편 내내 싸운 건 결국.
보안과 편의의 줄다리기였어요.
다 막으면 배포가 안 되고.
다 열면 또 뚫리고.
정답은 중간 어딘가예요.
"진짜 위험한 것(EXEC)만 막고, 나머지는 연다."
저는 거기에 섰어요.
이게 완벽한 답인지는.
다음 편에서 좀 더 솔직하게 얘기해볼게요.
다음 편 예고
자, 시리즈도 거의 끝이에요.
침해당하고.
새 서버 만들고.
서비스 옮기고.
자동배포까지 붙였어요.
근데 마지막으로.
제가 내린 선택들이 진짜 맞았는지.
남은 숙제는 뭔지.
5일간의 작업을 솔직하게 회고하면서 마무리할게요.
마지막 편에서 만나요.
'서버, 인프라' 카테고리의 다른 글
| 그래서 보안과 편의 사이, 어디에 서야 할까 (0) | 2026.06.21 |
|---|---|
| 7개 도메인을 무중단으로 HTTPS 이전한 이야기 (0) | 2026.06.19 |
| docker.sock을 Jenkins한테도 AI한테도 주지 마세요 (0) | 2026.06.19 |
| 내 서버가 6주째 몰래 채굴당하고 있었습니다 (0) | 2026.06.18 |
| Docker 안에서 Claude Code, Codex 쓰다가 git push 막힌 분들 보세요 (0) | 2026.06.03 |