LabHub
배우기 러닝패스 코스

CKS — Kubernetesセキュリティスペシャリスト

塞がれる時点が違えば、直す場所も違う

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

설정을 쓰는 연습과 그 설정이 지켜지는 것을 확인하는 연습은 다릅니다. 사고는 언제나 후자에서 납니다.

概念マップ: 위반한 파드도 그냥 Running・그 설정이 지켜지는 것을 확인하는 연습・어드미션・kubelet

왜 강제되는 곳에서 해야 하나

앞 모듈들에서 securityContext 와 Pod Security 를 여러 번 썼습니다. 그런데 그 실습들이 도는 곳에는 kubelet 도 컨테이너 런타임도 없어서, 위반한 파드도 그냥 Running 이 되었습니다.

설정을 쓰는 연습은 되었지만 그 설정이 지켜지는 것을 확인하는 연습은 되지 않았습니다. 그리고 실무에서 사고가 나는 자리는 후자입니다 — 설정을 다 써 놓고 적용되지 않은 상태가 정상처럼 보이기 때문입니다.

같은 "안 된다" 가 세 층에 흩어져 있다

무엇이 여기 있나 증상 언제 보이나
어드미션 Pod Security Admission, 정책 엔진 kubectl apply 가 거부됨 배포 전 (CI)
kubelet runAsNonRoot, 이미지 USER 검사 파드는 생성, CreateContainerConfigError 배포 직후
런타임 읽기 전용 루트, capability, seccomp 파드는 Running, 앱만 실패 그 코드 경로를 탈 때

가장 좋은 것은 어드미션입니다. 피드백이 즉시 오고, 잘못된 것이 아예 클러스터에 들어오지 않습니다.

가장 나쁜 것은 런타임입니다. 파드는 멀쩡히 돌고 있어서, 증상이 애플리케이션 오류로 보입니다. 평소에는 괜찮다가 특정 코드 경로에서만 실패하는 일도 흔합니다.

그래서 순서가 있습니다. 앞 층에서 막을 수 있는 것을 뒤 층에 맡기지 않습니다.

Pod Security Admission 은 warn 부터

enforce 를 바로 걸면 이미 도는 워크로드가 다음 재배포에서 전부 막힙니다. 그것 자체가 장애입니다.

pod-security.kubernetes.io/warn: restricted     # 먼저 이것만
pod-security.kubernetes.io/enforce: restricted  # 경고가 0 이 된 뒤에

끊고 나서 세는 것이 아니라 세고 나서 끊는 순서입니다.

컨테이너를 조이면 무엇이 깨지는가

보안 설정을 켰을 때 실제로 무엇이 실패하는지 알아 두면, 장애를 겪기 전에 이미지 쪽을 고칠 수 있다. 자주 부딪히는 것만 정리한다.

읽기 전용 루트 파일 시스템. 대부분의 앱은 어딘가에 임시 파일을 쓴다. 로그, 캐시, 소켓, 그리고 언어 런타임이 쓰는 임시 디렉터리다. 루트를 읽기 전용으로 만들면 이것들이 전부 막히는데, 증상은 대개 애플리케이션의 권한 오류로 나타나 보안 설정과 연결 짓기까지 시간이 걸린다. 해법은 쓰기가 필요한 경로마다 emptyDir 을 붙이는 것이고, 어디에 쓰는지는 추측하지 말고 조인 상태로 한 번 띄워 실패 경로를 모아 보는 편이 빠르다.

루트가 아닌 사용자로 실행. 1024 미만 포트를 열 수 없다는 점이 가장 먼저 걸린다. 컨테이너 안에서 80 포트를 듣던 이미지는 8080 으로 바꿔야 하고, 서비스의 targetPort 도 함께 고쳐야 한다. 그리고 이미지 안의 파일 소유자가 루트로 되어 있으면 새 사용자가 읽지 못할 수 있으므로, 이미지를 만들 때 소유권을 함께 넘겨야 한다.

capability 를 전부 떨어뜨리기. 대부분의 앱은 아무것도 필요 없지만, 낮은 포트를 열어야 하면 NET_BIND_SERVICE, ping 을 쓰면 NET_RAW 가 필요하다. 필요한 것만 다시 얹는 순서로 가야지, 무엇이 필요한지 모른 채 전부 남겨 두면 조인 의미가 없다.

seccomp 기본 프로필. 허용되지 않은 시스템 호출은 EPERM 으로 돌아오고, 라이브러리가 그것을 삼키면 증상이 엉뚱하게 나타난다. 이 실습 환경의 컨테이너 실습이 막혀 있는 이유도 같은 계열이다.

정리하면 순서는 이렇습니다. 조인 설정으로 먼저 띄워 무엇이 실패하는지 모으고, 실패한 만큼만 예외를 열고, 그 예외 목록을 이미지 쪽에서 없애 나갑니다. 반대로 "일단 다 풀고 나중에 조인다" 로 시작하면 그 나중은 오지 않습니다.

실무에서 진짜 중요한 것

앞 층에서 막을 수 있는 것을 뒤 층에 맡기지 않습니다. 어드미션에서 걸리면 CI 에서 즉시 알 수 있지만, 런타임에서 걸리면 파드가 멀쩡히 도는 채로 특정 코드 경로에서만 실패해 애플리케이션 오류처럼 보입니다. 같은 규칙이라도 어디서 강제하느냐가 대응 비용을 열 배 가릅니다.

enforce 는 경고가 0 이 된 뒤에 겁니다. 바로 걸면 이미 도는 워크로드가 다음 재배포에서 전부 막히고, 그것 자체가 장애입니다. warn 으로 먼저 세고 나서 끊는 순서를 지켜야 합니다.

runAsNonRoot 실패는 파드 생성 실패로 보이지 않습니다. 파드는 만들어지고 CreateContainerConfigError 만 남아서, 이미지의 USER 를 의심하기까지 시간이 걸립니다. 이미지를 만들 때 USER 를 명시해 두는 것이 가장 값싼 예방입니다.

다음 실습에서 이 셋을 진짜 클러스터 위에서 하나씩 확인합니다.