LabHub

CKS — 쿠버네티스 보안 전문가 · 시스템 하드닝 · 실습

seccomp·AppArmor·capability 로 조이기

LabHub 에서 이어서 보기

목표

파드 스펙만으로 컨테이너가 커널에 요청할 수 있는 일을 줄이는 방법을 손에 익히고,
클러스터에 남아 있는 privileged 컨테이너를 찾아내는 탐지 절차를 만듭니다.

왜 중요한가

컨테이너는 호스트 커널을 공유합니다. 탈출 취약점은 대부분 시스템 콜이나 capability 를 통해
커널을 찌릅니다. 그래서 방어는 침입을 막는 쪽이 아니라, 침입한 프로세스가 할 수 있는 일을
미리 줄여 두는 쪽으로 설계합니다. seccomp 는 호출 가능한 시스템 콜을, AppArmor 는 접근 가능한
파일과 기능을, capabilities 는 root 권한의 조각을 각각 제한합니다. 세 층이 겹치기 때문에
하나가 뚫려도 나머지가 남습니다. 반대로 privileged: true 는 이 세 층을 한 번에 무효화하므로,
운영 클러스터에서 privileged 컨테이너를 세는 일이 곧 위험도 측정입니다.

주의: 이 실습 환경에는 실제 컨테이너 런타임이 없습니다. seccomp·AppArmor 프로파일이 정말로
강제되는지는 확인할 수 없고, 노드에 프로파일 파일이 있는지도 검사하지 않습니다. 채점은 파드
스펙의 필드가 정확히 쓰였는지만 봅니다. CKS 시험에서 채점하는 것도 결국 이 스펙입니다.

단계

1. 네임스페이스 cks-sys 를 만들고 그 안에 파드 seccomp-default(이미지
nginx:1.27-alpine)를 만든다. 파드 레벨 spec.securityContext.seccompProfile.type
RuntimeDefault 다.
2. cks-sys 에 파드 seccomp-custom 을 만든다. 파드 레벨 seccompProfile 의 type
Localhost, localhostProfileprofiles/audit.json 이다.
3. cks-sys 에 파드 apparmor-app(컨테이너 이름 app, 이미지 nginx:1.27-alpine)을 만들고
AppArmor 프로파일 k8s-custom-profile 을 건다. 컨테이너
securityContext.appArmorProfiletypeLocalhost, localhostProfile
k8s-custom-profile 이다. (구버전 형식인
container.apparmor.security.beta.kubernetes.io/app: localhost/k8s-custom-profile
어노테이션으로 해도 통과한다.)
4. cks-sys 에 파드 capped(컨테이너 이름 app)를 만든다. 컨테이너 securityContext 에서
capabilities.dropALL 하나, capabilities.addNET_BIND_SERVICE 하나이고
allowPrivilegeEscalationfalse 다.
5. cks-sys 에 파드 no-host-ns 를 만든다. hostNetwork, hostPID, hostIPC,
shareProcessNamespace 를 모두 false 로 명시한다.

여기에 감사에서 반드시 알아야 할 사실이 하나 숨어 있다. PodSpec 에서 앞의 세 필드는
bool + omitempty 라, false 로 적어도 직렬화 단계에서 통째로 빠진다. 즉 저장된
오브젝트만 보면 "false 라고 적었다" 와 "아예 안 적었다" 를 구별할 수 없다. 그래서
실제 감사 기준은 "false 라고 적혀 있나" 가 아니라 "true 가 아닌가" 다. 반면
shareProcessNamespace*bool 이라 false 가 그대로 남는다 — 채점기도
이 차이를 그대로 반영한다. 적용한 뒤 kubectl get pod no-host-ns -n cks-sys -o yaml
로 직접 확인해 보라.
6. cks-sys 에 파드 legacy-agent(컨테이너 이름 agent)를 만들되 컨테이너
securityContext 의 privilegedtrue 로 둔다(레거시 워크로드 재현).
그다음 /root/cks-system-hardening/find-privileged.sh 에 탐지 스크립트를 쓰고,
그 실행 결과를 /root/cks-system-hardening/privileged.txt 에 저장한다. 결과는 cks-sys
네임스페이스에서 privileged 컨테이너(초기화 컨테이너 포함)를 가진 파드를
cks-sys/<파드이름> 형식으로 한 줄에 하나씩, 사전순 정렬한 것이다.
7. cks-sys 에 파드 hardened(컨테이너 이름 app)를 만든다. 컨테이너 securityContext 에
readOnlyRootFilesystem: true, runAsNonRoot: true, runAsUser: 1000,
allowPrivilegeEscalation: false, capabilities.drop: [ALL] 을 모두 넣고,
파드 레벨 seccompProfile.typeRuntimeDefault 로 둔다. 쓰기가 필요한 /tmp
이름이 tmp 인 emptyDir 볼륨으로 마운트한다.

참고

단계 7개

  1. 런타임 기본 seccomp 프로파일
  2. 커스텀 seccomp 프로파일 지정
  3. AppArmor 프로파일 붙이기
  4. capability 전부 버리고 하나만 되찾기
  5. 호스트 네임스페이스 차단
  6. privileged 컨테이너 탐지
  7. 읽기 전용 루트 파일시스템 종합