LabHub

KCSA — 쿠버네티스 보안 어소시에이트 · 클라우드 네이티브 보안 개요 · 이론

공격 표면 지도 — 어디로 들어오는가

LabHub 에서 이어서 보기

한 줄 요약

쿠버네티스의 진입점은 생각보다 적습니다. **API 서버, kubelet, etcd, 컨테이너 레지스트리,
그리고 워크로드 자신.** 나머지는 대부분 이 다섯 중 하나로 가는 경로입니다.

왜 이게 필요했나

"클러스터를 보호하자"는 말이 막연한 이유는 지켜야 할 것의 목록이 없기 때문입니다.
공격자는 목록을 갖고 움직입니다 — 열린 포트를 훑고, 익명 접근을 시도하고, 이미 있는 자격증명으로
할 수 있는 일을 셉니다. 방어자도 같은 목록을 가져야 대칭이 맞습니다.

어떻게 동작하나

진입점 다섯

1) kube-apiserver (6443)
클러스터의 유일한 문이자 가장 값진 표적입니다. 여기서 볼 것은 셋 —
익명 접근이 열려 있는가(--anonymous-auth), 인가 모드가 무엇인가(AlwaysAllow 는 재앙),
그리고 누가 무슨 자격증명을 갖고 있는가.

2) kubelet (10250, 10255)
가장 자주 잊히는 표적입니다. kubelet 은 자기 API 를 갖고 있고 여기에는 파드 실행·exec·로그가 있습니다.
--anonymous-auth=true 이거나 인가 모드가 AlwaysAllow 면 **노드에 네트워크로 닿을 수 있는 누구나
그 노드의 모든 컨테이너에서 명령을 실행**할 수 있습니다. 읽기 전용 포트(10255)도 인증이 없어
파드 스펙과 환경변수가 그대로 노출됩니다. 그래서 readOnlyPort=0 이 하드닝 표준 항목입니다.

3) etcd (2379/2380)
클러스터 상태 전체가 여기 있습니다. **etcd 를 읽을 수 있으면 모든 Secret 을 읽을 수 있고,
쓸 수 있으면 apiserver 의 모든 인가 검사를 우회해 클러스터를 소유합니다.**
기본 설정에서 Secret 은 사실상 평문으로 저장되므로 etcd 백업 파일·스냅샷·디스크 이미지를 얻은
사람도 같은 것을 얻습니다.

4) 컨테이너 레지스트리와 이미지 공급망
공격자가 클러스터에 직접 붙을 필요 없이, 내가 알아서 그 코드를 받아 실행하게 만드는 경로입니다.
태그는 움직입니다 — myapp:1.4.2 가 어제와 오늘 다른 이미지를 가리킬 수 있습니다.
다이제스트로 고정하지 않으면 무엇을 배포했는지 특정할 수 없습니다.

5) 워크로드 자신
이미 클러스터 안에서 도는 파드는 이미 안쪽에 있습니다. 여기서 나가는 경로가 여럿입니다 —
마운트된 ServiceAccount 토큰으로 API 호출, hostPath 로 노드 파일시스템 접근,
privileged 컨테이너로 노드 장악, hostNetwork 로 노드의 네트워크 네임스페이스 진입.

자주 놓치는 표면

클라우드 네이티브 특유의 성질 둘

동적입니다. 파드는 계속 죽고 새로 뜨며 IP 가 바뀝니다. IP 기반 방화벽 규칙이 의미를 잃고,
그래서 라벨과 신원(SPIFFE 같은) 기반 정책으로 옮겨 갑니다.

증거가 휘발합니다. 침해된 파드는 이미 사라졌을 수 있습니다. 로그와 감사 기록을 클러스터 밖으로
내보내 두지 않으면 조사할 대상이 남지 않습니다.

현장에서 만나는 모습

저자의 블로그가 정리한 실패 패턴 중 KCSA 관점에서 특히 값진 것 하나 — 쿠버네티스 Secret 의 실체입니다.
etcdctl get /registry/secrets/default/app-db | hexdump -C 를 뜨면 키 경로가 평문 그대로 보이고,
kubectl get secret ... -o jsonpath='{.data.password}' | base64 -d 한 줄이면 비밀번호가 나옵니다.
base64 는 키가 없고 되돌리는 데 명령 하나면 되므로 암호화가 아닙니다.

여기에 RBAC 문제가 겹칩니다. 어떤 네임스페이스에 get secrets 권한이 있으면
그 네임스페이스의 모든 Secret 을 읽습니다. 저자의 표현으로는 "개발자에게 편의로 준 편집 권한이
사실상 프로덕션 자격증명 열람 권한인 경우가 매우 흔합니다."

또 하나, 환경변수에 대한 오해. .env 파일을 로드한 뒤 지워도 소용없습니다 —
값은 이미 커널이 프로세스마다 들고 있고 /proc/PID/environ 으로 읽힙니다.
같은 파드의 사이드카, 노드 접근자, kubectl debug --target=app 으로 붙은 디버그 컨테이너가
전부 같은 것을 봅니다. 환경변수는 저장 위치가 아니라 전달 방식이라는 게 요점입니다.

다음 퀴즈에서 확인할 것

이 모듈은 퀴즈로 마무리합니다. 다음 모듈에서 apiserver 의 인증 체인부터 kubelet 의 인가까지
컴포넌트별로 내려가 보고, 실습에서 위험 플래그 목록과 EncryptionConfiguration 을 직접 작성합니다.