CKS — Kubernetes Security Specialist
Pod Security Admission and securityContext
한국어 원문으로 표시합니다.
목표
네임스페이스 라벨만으로 워크로드의 보안 하한선을 정하는 방법을 익히고, restricted 프로파일을 실제로 통과하는 파드 스펙을 손으로 완성합니다.
왜 중요한가
PodSecurityPolicy 는 1.25 에서 제거됐습니다. 사용하려면 ServiceAccount 에 "이 PSP 를 use 할 권한"을 줘야 했는데 그 구조 자체가 권한 상승 경로였고, 여러 PSP 중 무엇이 적용될지 예측하기 어려웠기 때문입니다. 대체재인 PSA 는 네임스페이스 라벨 몇 줄로 끝나고, enforce·audit·warn 세 모드를 따로 켤 수 있어 점진적 도입이 가능합니다. 기존 클러스터에 곧바로 enforce 를 걸면 워크로드가 대량으로 막히므로 warn 과 audit 를 먼저 켜서 관찰하는 것이 표준 절차입니다.
restricted 프로파일이 요구하는 네 가지(runAsNonRoot, allowPrivilegeEscalation false, capabilities drop ALL, seccompProfile)는 CKS 에서 가장 자주 나오는 조합입니다. 외우는 것보다 직접 하나씩 빼 보면서 어떤 오류가 나는지 확인하는 편이 오래 갑니다.
이 환경의 API 서버에는 PodSecurity 어드미션이 켜져 있으므로, 위반 파드는 실제로 거부됩니다.
단계
- 네임스페이스
cks-psa를 만들고 라벨pod-security.kubernetes.io/enforce=baseline을 붙인다. - 네임스페이스
cks-psa-strict를 만들고 enforce·audit·warn 세 라벨을 모두restricted로, 그리고enforce-version·audit-version·warn-version세 라벨을 모두latest로 붙인다. cks-psa-strict에 파드hardened(컨테이너 이름app, 이미지nginx:1.27-alpine)를 만든다. restricted 를 통과해야 하므로 파드 레벨에runAsNonRoot: true와seccompProfile.type: RuntimeDefault, 컨테이너 레벨에allowPrivilegeEscalation: false와capabilities.drop: [ALL]이 필요하다.cks-psa-strict에privileged: true컨테이너를 가진 파드bad-pod를 apply 해 본다. 그 출력(표준 오류 포함)을/root/cks-securitycontext/denied.txt에 저장한다. 파드는 생성되면 안 된다.cks-psa에 파드nonroot-app(컨테이너 이름app)을 만든다. 파드 레벨 securityContext 에runAsNonRoot: true,runAsUser: 10001,runAsGroup: 10001,fsGroup: 20001을 넣는다.cks-psa-strict에 파드dropped(컨테이너 이름app)를 만든다. 파드 레벨seccompProfile.type은RuntimeDefault,runAsNonRoot는true이고, 컨테이너 레벨에allowPrivilegeEscalation: false,capabilities.drop: [ALL],readOnlyRootFilesystem: true를 넣는다.- RuntimeClass
cks-sandbox를 만든다.handler는runsc,overhead.podFixed는 메모리160Mi와 CPU250m이다. cks-psa-strict에 Deploymentpayments를 만든다. replicas 2, 셀렉터와 파드 라벨은app=payments, 파드 스펙의runtimeClassName은cks-sandbox이고, 파드 템플릿은 restricted 를 통과하도록 3 단계와 같은 네 조건을 모두 갖춘다.
참고
kubectl label ns cks-psa-strict pod-security.kubernetes.io/enforce=restricted pod-security.kubernetes.io/enforce-version=latest ...- 4 단계 실행 예:
kubectl apply -f bad-pod.yaml 2>&1 | tee /root/cks-securitycontext/denied.txt kubectl explain pod.spec.securityContext와kubectl explain pod.spec.containers.securityContext는 서로 다른 필드 목록을 보여 줍니다.- 흔한 실수 1:
capabilities를 파드 레벨에 넣습니다. 컨테이너 레벨 전용입니다. - 흔한 실수 2:
-version라벨을 빼먹으면 클러스터 업그레이드 때 프로파일 정의가 바뀌어 멀쩡하던 파드가 거부될 수 있습니다.
baseline 프로파일 적용
네임스페이스 cks-psa 를 만들고 라벨 pod-security.kubernetes.io/enforce=baseline 을 붙인다.
PSA 는 네임스페이스 라벨로 동작합니다. 라벨 키는 pod-security.kubernetes.io/ 로 시작합니다.
세 모드와 버전 라벨 전부 걸기
네임스페이스 cks-psa-strict 를 만들고 enforce·audit·warn 세 라벨을 모두 restricted 로,
그리고 enforce-version·audit-version·warn-version 세 라벨을 모두 latest 로 붙인다.
enforce·audit·warn 각각에 대응하는 -version 라벨이 따로 있습니다. 버전을 못 박으면 클러스터 업그레이드가 곧 정책 강화로 이어지지 않습니다.
restricted 를 통과하는 파드
cks-psa-strict 에 파드 hardened(컨테이너 이름 app, 이미지 nginx:1.27-alpine)를
만든다. restricted 를 통과해야 하므로 파드 레벨에 runAsNonRoot: true 와
seccompProfile.type: RuntimeDefault, 컨테이너 레벨에 allowPrivilegeEscalation: false
와 capabilities.drop: [ALL] 이 필요하다.
restricted 는 네 가지를 필수로 요구합니다. 하나라도 빠지면 거부되고, 오류 메시지가 어떤 필드가 걸렸는지 알려 줍니다.
한 가지 더 알아 두세요. runAsNonRoot: true 는 UID 를 정해 주지 않습니다. "root 로 돌지 마라" 고 말할 뿐이라, 이미지가 UID 를 지정하지 않으면 진짜 노드에서는 kubelet 이 파드를 띄우지 못하고 CreateContainerConfigError 를 냅니다. 실제 클러스터에서는 runAsUser 를 함께 박거나 비-root 로 만들어진 이미지를 쓰세요. 이 실습 환경은 파드를 실제로 실행하지 않아 그 차이가 드러나지 않습니다.
위반 파드가 거부되는지 확인
cks-psa-strict 에 privileged: true 컨테이너를 가진 파드 bad-pod 를 apply 해 본다.
그 출력(표준 오류 포함)을 /root/cks-securitycontext/denied.txt 에 저장한다.
파드는 생성되면 안 된다.
apply 의 표준 출력과 표준 오류를 함께 파일로 남겨야 합니다. 2>&1 | tee 를 쓰세요. 파드가 만들어졌다면 정책이 걸리지 않은 것입니다.
실행 사용자와 그룹 지정
cks-psa 에 파드 nonroot-app(컨테이너 이름 app)을 만든다. 파드 레벨 securityContext 에
runAsNonRoot: true, runAsUser: 10001, runAsGroup: 10001, fsGroup: 20001 을 넣는다.
runAsUser/runAsGroup/fsGroup 은 파드 레벨 securityContext 에, runAsNonRoot 는 양쪽 다 가능합니다. fsGroup 은 볼륨 파일의 그룹 소유권을 바꿉니다.
seccomp 와 capability 조합
cks-psa-strict 에 파드 dropped(컨테이너 이름 app)를 만든다. 파드 레벨
seccompProfile.type 은 RuntimeDefault, runAsNonRoot 는 true 이고,
컨테이너 레벨에 allowPrivilegeEscalation: false, capabilities.drop: [ALL],
readOnlyRootFilesystem: true 를 넣는다.
restricted 네임스페이스이므로 이 파드도 네 가지 필수 조건을 모두 만족해야 생성됩니다.
RuntimeClass 오브젝트
RuntimeClass cks-sandbox 를 만든다. handler 는 runsc, overhead.podFixed 는
메모리 160Mi 와 CPU 250m 이다.
RuntimeClass 는 클러스터 범위 리소스입니다. handler 값은 노드의 컨테이너 런타임 설정에 있는 런타임 이름과 문자 단위로 같아야 합니다.
종합: 샌드박스 런타임 배포
cks-psa-strict 에 Deployment payments 를 만든다. replicas 2, 셀렉터와 파드 라벨은
app=payments, 파드 스펙의 runtimeClassName 은 cks-sandbox 이고, 파드 템플릿은
restricted 를 통과하도록 3 단계와 같은 네 조건을 모두 갖춘다.
Deployment 의 파드 템플릿도 PSA 검사를 받습니다. 앞에서 만든 RuntimeClass 이름을 파드 스펙의 runtimeClassName 에 씁니다.