KCSA — Kubernetes Security Associate
Applying Pod Security Admission With Labels
한국어 원문으로 표시합니다.
목표
네임스페이스 라벨만으로 파드 보안 정책을 걸어 보고, restricted 위반 파드가 실제로 거부되는 것과 그 거부 메시지를 확인하고, 마지막으로 등급을 올려도 이미 돌고 있는 파드는 쫓겨나지 않는다는 PSA 의 핵심 성질을 직접 관찰합니다.
왜 중요한가
PSP 가 실패한 이유는 기능이 부족해서가 아니라 디버깅이 불가능했기 때문입니다. 여러 PSP 가 매칭될 때 어느 것이 적용됐는지 알기 어려웠고, mutating 이라 내가 쓴 스펙과 실행되는 스펙이 달랐습니다. 쓰이지 않는 보안 기능은 보안이 아닙니다.
PSA 는 그 교훈으로 만들어졌습니다. 라벨 하나면 정책이고, 거부 메시지에 위반 필드가 다 나옵니다. 표현력을 잃은 대신 운영 가능성을 얻은 교환이며, 이런 교환이 왜 옳았는지 이해하는 것이 KCSA 에서 자주 묻는 지점입니다.
7 단계가 이 실습의 하이라이트입니다. PSA 는 어드미션 시점에만 동작하므로
enforce 를 restricted 로 올려도 이미 도는 파드는 그대로 삽니다. 아무 일도 안 일어난 것처럼
보이다가 다음 롤아웃이나 노드 교체 때 파드가 안 뜨면서 터집니다.
정책 변경과 사고 사이에 시차가 있다는 것을 손으로 확인해 두면 마이그레이션을 설계할 때 달라집니다.
단계
- 네임스페이스
kcsa-psa-baseline을 만들고 라벨 네 개를 붙입니다 —pod-security.kubernetes.io/enforce=baseline,pod-security.kubernetes.io/enforce-version=v1.30,pod-security.kubernetes.io/audit=restricted,pod-security.kubernetes.io/warn=restricted. - 네임스페이스
kcsa-psa-restricted를 만들고 라벨 네 개를 붙입니다 —pod-security.kubernetes.io/enforce=restricted,pod-security.kubernetes.io/enforce-version=v1.30,pod-security.kubernetes.io/audit=restricted,pod-security.kubernetes.io/warn=restricted. kcsa-psa-restricted에 privileged 컨테이너를 가진 파드bad를 만들려고 시도합니다. 거부되어야 하며, 그 거부 메시지 전체를/root/kcsa-psa/reject.txt에 저장합니다. 파드bad는 끝까지 만들어지지 않은 상태여야 합니다.kcsa-psa-baseline에 파드app-baseline을 만듭니다 — 이미지nginx:1.27-alpine, securityContext 는 아무것도 지정하지 않습니다. Running 까지 갑니다.kcsa-psa-restricted에 파드app-restricted를 만듭니다 — 이미지nginx:1.27-alpine, 파드 레벨에securityContext.runAsNonRoot: true와securityContext.seccompProfile.type: RuntimeDefault, 컨테이너 레벨에allowPrivilegeEscalation: false,capabilities.drop: ["ALL"],runAsUser: 1000./root/kcsa-psa/exempt.yaml에 AdmissionConfiguration 을 작성합니다 —plugins[0].name은PodSecurity,configuration.kind는PodSecurityConfiguration,defaults.enforce는baseline,exemptions.namespaces의 첫 항목은kube-system. 그리고/root/kcsa-psa/exempt-note.txt에 PSA 면제의 세 축을 한 줄씩 정확히usernames,runtimeClasses,namespaces로 적습니다.kcsa-psa-baseline의enforce라벨만restricted로 바꿉니다. 그다음 (a) 기존 파드app-baseline의 현재 상태를 확인해/root/kcsa-psa/upgrade.txt에existing-pod=<상태>로 적고, (b) 4단계와 같은 스펙의 파드app-baseline2를 만들어 보고 거부되는 것을 확인한 뒤 같은 파일에new-pod=rejected줄을 추가합니다. 파드app-baseline2는 만들어지지 않은 상태여야 합니다.
참고
- 라벨은
kubectl label namespace <이름> <키>=<값>으로 붙이고, 이미 있는 값을 바꿀 때는--overwrite가 필요합니다. - 거부 메시지는 표준 에러로 나오므로
kubectl apply -f bad.yaml 2> /root/kcsa-psa/reject.txt처럼 저장해야 합니다. - 파드 상태는
kubectl get pod app-baseline -n kcsa-psa-baseline -o jsonpath='{.status.phase}'로 확인합니다. - 흔한 실수 1: 4단계 파드에 보안 설정을 미리 넣어 두는 것. 그러면 7단계에서 대비가 사라지고 채점도 통과하지 못합니다.
- 흔한 실수 2: 3단계나 7단계에서 거부된 파드를 어떻게든 만들려고 라벨을 잠깐 낮추는 것. 채점은 그 파드가 없는 것을 확인합니다.
baseline 네임스페이스 만들기
네임스페이스 kcsa-psa-baseline 을 만들고 라벨 네 개를 붙입니다 — pod-security.kubernetes.io/enforce=baseline, pod-security.kubernetes.io/enforce-version=v1.30, pod-security.kubernetes.io/audit=restricted, pod-security.kubernetes.io/warn=restricted.
PSA 는 네임스페이스 라벨로만 동작합니다. 모드는 셋(enforce/audit/warn)이고 여기에 버전 고정 라벨이 하나 더 붙습니다. enforce 는 실제로 차단하는 등급, audit/warn 은 '올리면 무엇이 깨지는지' 미리 보는 등급이라는 점을 생각하며 값을 정하세요.
restricted 네임스페이스 만들기
네임스페이스 kcsa-psa-restricted 를 만들고 라벨 네 개를 붙입니다 — pod-security.kubernetes.io/enforce=restricted, pod-security.kubernetes.io/enforce-version=v1.30, pod-security.kubernetes.io/audit=restricted, pod-security.kubernetes.io/warn=restricted.
앞 단계와 같은 라벨 네 개인데 값이 전부 가장 엄격한 등급입니다. 버전 라벨을 빠뜨리면 클러스터를 업그레이드할 때 정책 내용이 바뀔 수 있습니다.
restricted 위반 파드가 거부되는지 확인
kcsa-psa-restricted 에 privileged 컨테이너를 가진 파드 bad 를 만들려고 시도합니다. 거부되어야 하며, 그 거부 메시지 전체를 /root/kcsa-psa/reject.txt 에 저장합니다. 파드 bad 는 끝까지 만들어지지 않은 상태여야 합니다.
이 단계는 실패를 만드는 것이 목표입니다. 명령이 실패하면 메시지는 표준 에러로 나오니 리다이렉션에 주의하세요. 거부 메시지 안에 어떤 프로파일과 어떤 필드가 문제인지 다 적혀 있습니다.
baseline 만 통과하는 평범한 파드
kcsa-psa-baseline 에 파드 app-baseline 을 만듭니다 — 이미지 nginx:1.27-alpine, securityContext 는 아무것도 지정하지 않습니다. Running 까지 갑니다.
특별한 보안 설정을 하나도 넣지 않은 평범한 파드입니다. baseline 은 통과하지만 restricted 요건은 하나도 만족하지 않는 상태여야 합니다. 다음 단계와 비교하기 위한 대조군이니 굳이 강화하지 마세요.
restricted 를 통과하는 파드
kcsa-psa-restricted 에 파드 app-restricted 를 만듭니다 — 이미지 nginx:1.27-alpine, 파드 레벨에 securityContext.runAsNonRoot: true 와 securityContext.seccompProfile.type: RuntimeDefault, 컨테이너 레벨에 allowPrivilegeEscalation: false, capabilities.drop: ["ALL"], runAsUser: 1000.
네 가지 요건이 필요하고, 그중 둘은 파드 레벨 securityContext 에 둘은 컨테이너 레벨에 들어갑니다. 어느 것이 어디에 가는지 헷갈리면 거부 메시지가 알려 줍니다. runAsUser 도 명시해야 합니다.
면제 설정과 면제 축 정리
/root/kcsa-psa/exempt.yaml 에 AdmissionConfiguration 을 작성합니다 — plugins[0].name 은 PodSecurity, configuration.kind 는 PodSecurityConfiguration, defaults.enforce 는 baseline, exemptions.namespaces 의 첫 항목은 kube-system. 그리고 /root/kcsa-psa/exempt-note.txt 에 PSA 면제의 세 축을 한 줄씩 정확히 usernames, runtimeClasses, namespaces 로 적습니다.
면제는 네임스페이스 라벨로 표현할 수 없어 apiserver 의 어드미션 설정 파일에 씁니다. 이 실습에서는 그 파일을 작성만 합니다. 면제 축은 셋이고, 세 축이 각각 무엇을 기준으로 검사를 건너뛰는지 생각해 보세요.
라벨만 올렸을 때 기존 파드는 어떻게 되나
kcsa-psa-baseline 의 enforce 라벨만 restricted 로 바꿉니다. 그다음 (a) 기존 파드 app-baseline 의 현재 상태를 확인해 /root/kcsa-psa/upgrade.txt 에 existing-pod=<상태> 로 적고, (b) 4단계와 같은 스펙의 파드 app-baseline2 를 만들어 보고 거부되는 것을 확인한 뒤 같은 파일에 new-pod=rejected 줄을 추가합니다. 파드 app-baseline2 는 만들어지지 않은 상태여야 합니다.
PSA 는 어드미션 시점에만 판단합니다. 이 사실이 등급을 올렸을 때의 결과를 결정합니다. 기존 파드의 상태를 직접 확인해 그대로 적고, 같은 스펙의 새 파드를 만들어 보면 대비가 선명해집니다.