環境変数とファイルマウント、何が違うのか
한국어 원문으로 표시합니다.
이 실습은 진짜 VM 에서 돕니다
이 상자는 파드가 아니라 KubeVirt 가 띄운 가상머신입니다. 리눅스 커널이
따로 돌고 systemd 가 실제로 서비스를 관리하며, docker 는 흉내가 아니라
진짜 도커 엔진입니다. docker run 으로 띄운 컨테이너는 실제로 프로세스가
되고 docker exec 도 docker logs 도 그대로 동작합니다.
예전에는 이 실습이 파드 안에서 돌았습니다. 커널 권한을 전부 내려놓은 상자라 컨테이너를 띄우는 단계가 막혀 있었고, 그래서 이미지 아카이브를 직접 풀어 보는 우회로 배웠습니다. 이제 우회가 필요 없습니다.
알아 둘 것이 둘 있습니다.
- 처음 뜨는 데 1분 남짓 걸립니다. VM 이 부팅하고 도커를 설치하기 때문입니다. 파드 실습(보통 40초)보다 느립니다.
- 브라우저 미리보기가 없습니다. VM 으로 들어오는 연결은 채점 포트
하나만 열려 있습니다. 웹 서버를 띄웠다면 VM 안에서
curl로 확인하세요.
목표
같은 시크릿을 환경변수와 파일 마운트 두 방식으로 전달해 보고, docker inspect
노출 여부와 재시작 없는 로테이션 가능 여부를 직접 재어 비교표로 정리합니다.
마지막에는 간단한 시크릿 탐지 스크립트를 작성합니다.
왜 중요한가
환경변수는 저장 위치가 아니라 전달 방식입니다. /proc/<pid>/environ 은 프로세스가
사는 동안 계속 읽히고, 컨테이너 환경변수는 데몬에 접근할 수 있는 누구에게나
docker inspect 로 그대로 보입니다. 그래서 ".env 를 로드하고 지웠으니 괜찮다"는
안심은 근거가 없습니다. 더 중요한 것은 로테이션입니다. 환경변수는 프로세스 시작
시점에 고정되므로 값을 바꾸려면 반드시 재시작이 필요하고, 파일 마운트는 같은
inode 를 덮어쓰면 재시작 없이 새 값이 보입니다. 이 차이가 사고 대응 시간을
가릅니다 — 자격증명이 공개됐다고 가정할 때 폐기하고 교체해 서비스를 정상화하는
데 몇 분이 걸리는지가 곧 그 팀의 시크릿 관리 수준입니다.
단계
/root/sec3디렉터리를 만들고/root/sec3/app.env에API_KEY=labhub-env-key-v1한 줄을 적은 뒤, 파일 권한을600으로 바꿉니다.alpine:3.20으로sec3-env컨테이너를--env-file /root/sec3/app.env를 써서 백그라운드 실행합니다. 컨테이너의API_KEY가 파일의 값과 같아야 합니다.docker inspect sec3-env로 읽어낸API_KEY값을 그대로/root/sec3/leak-proof.txt에 저장합니다./root/sec3/secret.txt에labhub-file-key-v1을 적고, 그 파일을sec3-file컨테이너의/run/secrets/api_key경로에 읽기 전용으로 바인드 마운트해 백그라운드 실행합니다. 마운트 원본은 정확히/root/sec3/secret.txt여야 합니다.sec3-file컨테이너의 환경변수에API_KEY도, 시크릿 값 자체도 들어 있지 않아야 합니다.- 컨테이너를 재시작하지 않은 채
/root/sec3/secret.txt의 내용을labhub-rotated-v2로 덮어씁니다.sec3-file이 새 값을 보게 되고,sec3-env의 환경변수 값은 옛 값 그대로여야 합니다. 그리고/root/sec3/rotate.md에 어느 방식이 재시작을 필요로 하는지 결론을 적습니다(재시작또는restart라는 말이 들어가야 합니다). - 실행 권한이 있는
/root/sec3/find-secrets.sh를 작성합니다. 첫 번째 인자로 받은 디렉터리를 뒤져서AKIA로 시작하는 액세스 키나PRIVATE KEY블록이 있으면 0 이 아닌 종료코드로 끝나면서 어느 파일에서 찾았는지 경로를 출력하고, 아무것도 없으면 종료코드 0 으로 끝나야 합니다. /root/sec3/secrets.md에 환경변수 방식과 파일 마운트 방식을 비교하는 내용을 적고, 다음 네 줄을 정확히 이 형식으로 포함시킵니다.env_visible_in_inspect=yesmount_visible_in_inspect=noenv_needs_restart=yesmount_needs_restart=no
참고
- 파일 권한 변경은
chmod 600 <파일>, 확인은stat -c %a <파일>입니다. - 파일 하나를 읽기 전용으로 붙이려면
-v /호스트/경로:/컨테이너/경로:ro형식을 씁니다. - 컨테이너 환경변수 확인은
docker inspect <이름> | jq -r '.[0].Config.Env[]'입니다. - 6단계에서 파일을 덮어쓸 때는
printf '%s' labhub-rotated-v2 > /root/sec3/secret.txt처럼 제자리 덮어쓰기를 쓰세요. - 흔한 실수 1: 6단계에서
sed -i나 파일을 지웠다 다시 만드는 방식을 쓰면 inode 가 바뀌어 컨테이너가 옛 값을 계속 봅니다. - 흔한 실수 2: 4단계에서
sec3-file에-e API_KEY=...를 함께 주면 5단계가 실패합니다. 파일 마운트로 바꿨으면 환경변수는 빼세요. - 흔한 실수 3: 6단계에서
sec3-env를 다시 만들거나 app.env 를 함께 고치면 대조군이 사라져 실패합니다.sec3-env는 그대로 두세요.
시크릿 파일과 권한 600
/root/sec3 디렉터리를 만들고 /root/sec3/app.env 에 API_KEY=labhub-env-key-v1 한 줄을 적은 뒤, 파일 권한을 600 으로 바꿉니다.
값 자체보다 파일 권한이 채점 대상입니다. 소유자만 읽고 쓸 수 있어야 하며 그룹과 기타 사용자에게는 아무 권한도 없어야 합니다. 파일을 만든 뒤 권한을 바꾸는 순서로 하세요.
env-file 로 주입하기
alpine:3.20 으로 sec3-env 컨테이너를 --env-file /root/sec3/app.env 를 써서 백그라운드 실행합니다. 컨테이너의 API_KEY 가 파일의 값과 같아야 합니다.
값을 명령줄에 직접 쓰면 셸 히스토리와 프로세스 목록에 남습니다. 파일에서 읽어 주입하는 옵션을 쓰세요. 채점이 컨테이너의 환경변수 값과 파일 값을 대조하므로 컨테이너는 떠 있어야 합니다.
환경변수는 inspect 에 그대로 보인다
docker inspect sec3-env 로 읽어낸 API_KEY 값을 그대로 /root/sec3/leak-proof.txt 에 저장합니다.
컨테이너를 만든 사람이 아니어도 데몬에 접근할 수 있으면 이 값을 읽습니다. 추측해서 적지 말고 실제로 docker inspect 로 뽑아낸 값을 그대로 파일에 남기세요.
파일로 마운트하기
/root/sec3/secret.txt 에 labhub-file-key-v1 을 적고, 그 파일을 sec3-file 컨테이너의 /run/secrets/api_key 경로에 읽기 전용으로 바인드 마운트해 백그라운드 실행합니다. 마운트 원본은 정확히 /root/sec3/secret.txt 여야 합니다.
호스트 파일 하나를 컨테이너의 특정 경로에 그대로 붙이는 방식입니다. 마운트 원본 경로와 대상 경로가 채점 대상이므로 정확히 맞춰야 하고, 시크릿이므로 읽기 전용으로 붙여야 합니다.
환경변수에는 남기지 않기
sec3-file 컨테이너의 환경변수에 API_KEY 도, 시크릿 값 자체도 들어 있지 않아야 합니다.
파일 마운트로 바꿨다면 같은 값을 환경변수로도 넘길 이유가 없습니다. 둘 다 주면 파일 마운트의 이점이 사라집니다 — inspect 에 다시 보이기 때문입니다. 4단계 컨테이너에 환경변수가 섞이지 않았는지 확인하세요.
재시작 없이 로테이션하기
컨테이너를 재시작하지 않은 채 /root/sec3/secret.txt 의 내용을 labhub-rotated-v2 로 덮어씁니다. sec3-file 이 새 값을 보게 되고, sec3-env 의 환경변수 값은 옛 값 그대로여야 합니다. 그리고 /root/sec3/rotate.md 에 어느 방식이 재시작을 필요로 하는지 결론을 적습니다(재시작 또는 restart 라는 말이 들어가야 합니다).
바인드 마운트는 inode 를 따라갑니다. 파일을 제자리에서 덮어쓰면(리다이렉션) 컨테이너가 새 값을 즉시 보지만, 파일을 지우고 새로 만드는 방식(일부 편집기나 sed -i)은 inode 가 바뀌어 마운트가 옛 파일을 계속 가리킵니다. 그리고 환경변수 쪽은 절대 바뀌면 안 됩니다 — 그것이 이 단계의 대조군입니다.
시크릿 탐지 스크립트
실행 권한이 있는 /root/sec3/find-secrets.sh 를 작성합니다. 첫 번째 인자로 받은 디렉터리를 뒤져서 AKIA 로 시작하는 액세스 키나 PRIVATE KEY 블록이 있으면 0 이 아닌 종료코드로 끝나면서 어느 파일에서 찾았는지 경로를 출력하고, 아무것도 없으면 종료코드 0 으로 끝나야 합니다.
디렉터리를 재귀적으로 훑어 두 가지 형태를 찾습니다 — AKIA 로 시작하는 액세스 키와 PRIVATE KEY 블록 헤더. 깨끗하면 0, 찾으면 0 이 아닌 값으로 끝나야 하고 어느 파일인지 경로를 출력해야 합니다. 이런 규칙이 형태가 뚜렷한 값에만 잘 듣는다는 한계도 같이 생각해 보세요.
전달 방식 비교표
/root/sec3/secrets.md 에 환경변수 방식과 파일 마운트 방식을 비교하는 내용을 적고, 다음 네 줄을 정확히 이 형식으로 포함시킵니다.
env_visible_in_inspect=yesmount_visible_in_inspect=noenv_needs_restart=yesmount_needs_restart=no
네 줄의 값은 앞 단계에서 직접 확인한 결과와 일치해야 합니다. 3단계에서 무엇이 보였는지, 5단계에서 무엇이 안 보였는지, 6단계에서 어느 쪽이 재시작을 요구했는지 되짚어 적으세요. 그리고 본문에는 환경변수 방식과 마운트 방식이 모두 언급돼야 합니다.