LabHub
学习 学习路径 课程

KCSA — Kubernetes 安全助理

有创建 Pod 的权限,但特权 Pod 必须被拦下

在 LabHub 中继续学习

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

목표

ValidatingAdmissionPolicy(VAP)로 "특권 컨테이너 금지" 와 "hostPath 볼륨 금지" 정책을 CEL 로 직접 쓰고, 바인딩으로 한 네임스페이스에만 켠 뒤, API 서버가 돌려주는 거절 메시지와 정상 파드의 승인을 양쪽 다 관찰합니다.

왜 중요한가

쿠버네티스의 모든 쓰기 요청은 인증 → 인가(RBAC) → 어드미션 을 거쳐야 etcd 에 저장됩니다. RBAC 은 "이 사용자가 파드를 만들어도 되는가" 까지만 답하고, "그 파드의 내용이 안전한가" 는 묻지 않습니다. 파드를 만들 권한만 있으면 privileged: true 나 노드의 / 를 hostPath 로 마운트한 파드로 노드를 통째로 장악할 수 있습니다. 그 구멍을 막는 층이 어드미션 제어입니다.

Pod Security Admission(PSA)은 네임스페이스 라벨 하나로 정해진 기준(privileged/baseline/restricted)을 켭니다. 편하지만 조직만의 규칙(허용 레지스트리, 특정 라벨 필수 등)은 표현할 수 없습니다. VAP 는 API 서버 안에서 CEL 식을 직접 평가하므로 웹훅 서버 없이도 원하는 규칙을 쓸 수 있고, 정책(무엇을 검사하나)바인딩(어디에, Deny/Warn/Audit 중 무엇으로) 이 분리돼 있어 같은 정책을 네임스페이스마다 다르게 켤 수 있습니다.

거절 메시지에는 어떤 정책과 어떤 바인딩이 막았는지가 이름으로 찍힙니다. 사고 대응 때 "왜 배포가 안 되나" 를 가장 빨리 푸는 단서가 바로 이 한 줄입니다. 또 어드미션은 생성 요청 시점에만 일어나므로, 정책을 켜기 전에 이미 만들어진 오브젝트는 그대로 남는다는 점도 기억해 두세요.

단계

  1. 네임스페이스 kcsa-adm 을 만들고 자동 라벨을 확인합니다.
  2. 특권 컨테이너를 거절하는 정책 deny-privileged 를 CEL 로 씁니다.
  3. 바인딩 deny-privileged-binding 으로 kcsa-adm 에만 Deny 로 켭니다.
  4. 특권 파드를 만들어 보고 거절 메시지를 /root/kcsa-adm/denied-privileged.txt 에 저장합니다.
  5. 평범한 파드 good 이 승인되는 것을 확인합니다.
  6. hostPath 볼륨을 거절하는 정책 deny-hostpath 와 바인딩을 만듭니다.
  7. hostPath 파드를 만들어 보고 거절 메시지를 /root/kcsa-adm/denied-hostpath.txt 에 저장합니다.
  8. 무엇이 막히고 무엇이 통과했는지 /root/kcsa-adm/report.txt 에 남깁니다.

참고

정책을 걸 격리 구역 만들기

네임스페이스 kcsa-adm 를 만듭니다. 이 네임스페이스에는 API 서버가 자동으로 kubernetes.io/metadata.name=kcsa-adm 라벨을 붙이며, 뒤 단계의 바인딩이 이 라벨로 대상을 고릅니다.

쿠버네티스는 모든 네임스페이스에 자기 이름을 값으로 하는 kubernetes.io/metadata.name 라벨을 자동으로 붙입니다. 만든 뒤 kubectl get ns <이름> --show-labels 로 확인해 보세요. create 를 --dry-run=client 로 만들어 apply 하면 여러 번 돌려도 안전합니다.

특권 컨테이너를 막는 규칙을 CEL 로 쓰다

ValidatingAdmissionPolicy deny-privileged 를 만듭니다. 파드 CREATE 요청에서 securityContext.privilegedtrue 인 컨테이너가 하나라도 있으면 거절하는 CEL 식을 쓰고, 거절 메시지는 privileged containers are not allowed 로 둡니다. failurePolicyFail.

정책은 matchConstraints.resourceRules 로 어떤 요청(코어 그룹 v1 pods, CREATE)을 볼지 정하고, validations[].expression 이 참이어야 통과합니다. object.spec.containers.all(c, ...) 처럼 모든 컨테이너가 조건을 만족하는지 묻되, securityContext 나 privileged 필드가 아예 없는 경우도 통과로 쳐야 하니 has() 로 먼저 확인하세요. 정책만으로는 아무것도 막히지 않습니다.

규칙을 kcsa-adm 에 묶어 실제로 켜기

ValidatingAdmissionPolicyBinding deny-privileged-binding 을 만듭니다. policyNamedeny-privileged, validationActions["Deny"], 대상은 matchResources.namespaceSelectormatchLabelskubernetes.io/metadata.name: kcsa-adm 인 네임스페이스만 고릅니다.

VAP 는 정책(무엇을 검사하나)과 바인딩(어디에, 어떤 행동으로)이 나뉘어 있습니다. 바인딩이 없으면 정책은 평가조차 되지 않습니다. validationActions 에는 Deny 외에 Warn·Audit 도 있는데, Deny 여야 요청이 실제로 거절됩니다. 네임스페이스를 고를 땐 1단계에서 확인한 자동 라벨을 쓰세요.

특권 파드가 문 앞에서 돌려보내지다

네임스페이스 kcsa-admsecurityContext.privileged: true 인 컨테이너를 가진 파드(이미지 nginx:1.27-alpine)를 만들어 보고, API 서버가 돌려준 거절 메시지 전체/root/kcsa-adm/denied-privileged.txt 에 저장합니다.

거절은 파드가 생성되기 전에 일어나므로 kubectl 이 0 이 아닌 종료 코드와 함께 오류를 표준 에러로 출력합니다. 표준 에러까지 파일로 받아야 합니다(2>&1). 메시지에는 어떤 정책과 어떤 바인딩이 거절했는지가 이름으로 들어 있습니다. 바인딩 직후엔 반영에 1~2초 걸릴 수 있으니, 파드가 만들어져 버렸다면 지우고 다시 시도하세요.

평범한 파드는 막힘 없이 들어간다

네임스페이스 kcsa-adm 에 특권 설정이 없는 평범한 파드 good(이미지 nginx:1.27-alpine)을 만듭니다. 정책이 켜져 있어도 이 파드는 승인돼야 합니다.

좋은 정책은 나쁜 요청만 막고 정상 요청은 그대로 통과시킵니다. CEL 식에서 securityContext 가 없는 컨테이너를 통과로 처리했는지가 여기서 드러납니다. 만들어졌다면 kubectl get pod good -n kcsa-adm 로 보이고, 막혔다면 4단계와 같은 모양의 오류가 납니다.

노드 디스크로 통하는 hostPath 도 막기

ValidatingAdmissionPolicy deny-hostpath 와 바인딩 deny-hostpath-binding 을 만듭니다. 정책은 파드 CREATE 에서 hostPath 볼륨이 하나라도 있으면 거절하고(메시지 hostPath volumes are not allowed, failurePolicy: Fail), 바인딩은 validationActions ["Deny"]kubernetes.io/metadata.name: kcsa-adm 네임스페이스에만 적용합니다.

구조는 2·3단계와 같고 검사 대상만 volumes 로 바뀝니다. volumes 필드가 아예 없는 파드도 있으니 has(object.spec.volumes) 로 먼저 확인하고, 각 볼륨에 hostPath 필드가 있는지를 has() 로 물으세요. 정책과 바인딩을 --- 로 이어 한 번에 apply 해도 됩니다.

노드의 /etc 를 마운트하려던 파드가 거절되다

네임스페이스 kcsa-adm 에 노드의 /etchostPath 볼륨으로 마운트하는 파드(이미지 nginx:1.27-alpine)를 만들어 보고, 거절 메시지 전체를 /root/kcsa-adm/denied-hostpath.txt 에 저장합니다.

파드 spec 의 volumes 에 hostPath 볼륨을 두고 컨테이너 volumeMounts 로 붙이는 모양입니다. 4단계처럼 표준 에러를 파일로 받으세요. 이번 메시지에 찍히는 정책·바인딩 이름이 4단계와 다르다는 점을 확인하세요.

무엇이 막히고 무엇이 통과했는지 장부로 남기기

관찰 결과를 /root/kcsa-adm/report.txt 에 네 줄로 적습니다 — privileged=denied, hostpath=denied, plain=allowed, enforcement=validatingadmissionpolicy. 채점기는 각 줄을 실제 클러스터에 파드를 만들어 보며 대조합니다.

값은 denied 또는 allowed 둘 중 하나이고, 마지막 줄은 이 거절을 누가 집행했는지(PSA 라벨이 아니라 어떤 어드미션 메커니즘인지)를 소문자 한 낱말로 적습니다. 추측이 아니라 4·5·7단계에서 직접 본 결과를 옮기세요.