LabHub
배우기 러닝패스 코스

KCSA — Kubernetes Security Associate

The Auditor Arrives, and There's No Proof It's Switched On

LabHub 에서 이어서 보기

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

목표

여러 보안 통제 항목(익명 접근 차단, Pod Security 강제, 서비스어카운트 토큰 자동 마운트 해제, 자원 할당량)을 클러스터에서 직접 확인하고, 그 결과를 통제 ID 별 pass/fail 리포트와 결함 목록으로 남깁니다.

왜 중요한가

CIS Kubernetes Benchmark, NSA/CISA 강화 가이드, SOC 2·ISO 27001 같은 프레임워크는 결국 "이 통제가 켜져 있는가" 를 묻는 질문 목록입니다. 감사에서 문제가 되는 건 통제가 없는 경우보다 켜져 있다고 적었는데 실제로는 꺼져 있는 경우입니다. 그래서 리포트의 한 줄 한 줄은 설정 파일이나 기억이 아니라, 지금 클러스터가 돌려주는 답(auth can-i, 오브젝트 필드, 라벨)에서 나와야 합니다.

또 강화한 곳만 보고 끝내면 안 됩니다. 아무도 손대지 않은 default 네임스페이스처럼 통제 밖에 남은 자리를 찾아 결함으로 기록하는 것이 컴플라이언스 점검의 절반입니다.

단계

  1. 점검 대상 네임스페이스 kcsa-comp 를 만듭니다.
  2. system:anonymous 가 시크릿을 list 할 수 있는지 물어보고 anon.txt 에 기록합니다.
  3. kcsa-comppod-security.kubernetes.io/enforce=restricted 라벨을 붙입니다.
  4. restricted 요건을 모두 갖춘 파드 hardened 를 만듭니다.
  5. default 서비스어카운트의 토큰 자동 마운트를 끕니다.
  6. ResourceQuota quota 로 네임스페이스 자원 상한을 겁니다.
  7. 네 통제 항목의 실제 결과를 compliance.csv 리포트로 씁니다.
  8. enforce 라벨이 없는 네임스페이스를 찾아 gaps.txt 에 결함으로 기록합니다.

참고

감사 대상 네임스페이스를 세우다

컴플라이언스 점검 대상이 될 네임스페이스 kcsa-comp 를 만듭니다.

감사는 범위를 정하는 데서 시작합니다. 네임스페이스는 create 를 --dry-run=client 로 만들어 apply 하면 여러 번 돌려도 안전합니다.

로그인하지 않은 누군가가 시크릿을 볼 수 있는가

인증되지 않은 사용자(system:anonymous)가 네임스페이스 kcsa-comp 에서 시크릿을 list 할 수 있는지 클러스터에 직접 물어보고, 그 답을 /root/kcsa-comp/anon.txtanonymous-list-secrets=<yes|no> 한 줄로 적습니다.

먼저 kubectl auth can-i--as=system:anonymous 를 붙여 보세요. 답 대신 거절이 옵니다 — can-i 는 그 사용자 자신으로 SelfSubjectAccessReview 를 만드는데, 익명 사용자는 그것조차 못 하기 때문입니다. 그래서 관리자가 대신 묻는 SubjectAccessReview(authorization.k8s.io/v1)를 만들어 spec.user 와 groups(system:unauthenticated), resourceAttributes 를 채우고 status.allowed 를 읽습니다. false 면 no 입니다.

네임스페이스에 restricted 문지기를 세우다

네임스페이스 kcsa-comp 에 Pod Security Admission 라벨 pod-security.kubernetes.io/enforce=restricted 를 붙입니다(원하면 warn·audit 라벨도 함께).

Pod Security Standards 는 네임스페이스 라벨 하나로 켜집니다. 라벨 키는 pod-security.kubernetes.io/<모드> 이고 값은 privileged·baseline·restricted 중 하나입니다. kubectl label 에 --overwrite 를 붙이면 다시 돌려도 안전합니다.

restricted 기준을 통과하는 파드를 들이다

네임스페이스 kcsa-comp 에 파드 hardened(이미지 nginx:1.27-alpine)를 만듭니다. 파드 레벨에 runAsNonRoot: trueseccompProfile.type: RuntimeDefault, 컨테이너 레벨에 allowPrivilegeEscalation: false, capabilities.drop: [ALL], runAsNonRoot: true 를 선언해 restricted 프로파일의 요건을 모두 갖춥니다.

restricted 프로파일은 루트로 돌지 않을 것, 권한 상승을 막을 것, 모든 capability 를 버릴 것, seccomp 프로파일을 지정할 것을 요구합니다. securityContext 는 파드 레벨(spec.securityContext)과 컨테이너 레벨(containers[].securityContext) 두 곳에 있고, 필드마다 둘 수 있는 자리가 다릅니다. 요건 하나라도 빠지면 3단계의 라벨 때문에 생성이 거절될 수 있습니다.

쓰지도 않는 토큰이 모든 파드에 꽂힌다

네임스페이스 kcsa-compdefault 서비스어카운트에 automountServiceAccountToken: false 를 설정해, 따로 요청하지 않은 파드에는 API 토큰이 자동으로 마운트되지 않게 합니다.

네임스페이스가 생기면 컨트롤러가 default 서비스어카운트를 만들어 줍니다. 아직 없다면 잠시 기다렸다가 확인하세요. 이 필드는 서비스어카운트 오브젝트의 최상위 필드라 patch 로 바꿀 수 있습니다.

한 팀이 클러스터를 다 먹지 못하게

네임스페이스 kcsa-comp 에 ResourceQuota quota 를 만들어 pods=10, requests.cpu=2, requests.memory=2Gi 를 상한으로 겁니다.

ResourceQuota 는 네임스페이스 전체의 합계를 제한하는 오브젝트로, spec.hard 아래에 자원 이름과 상한을 적습니다. kubectl create quota 에 --hard 로 여러 항목을 쉼표로 이어 줄 수 있습니다.

감사관에게 낼 통제 항목 리포트

/root/kcsa-comp/compliance.csv 를 만듭니다. 첫 줄은 헤더 control,status, 그 아래 네 통제 항목 anonymous-access, psa-enforce, sa-token-automount, resource-quota 를 한 줄씩 적고 status 에는 실제로 확인한 결과(pass 또는 fail)를 적습니다. 네 항목 모두 통과 상태여야 합니다.

리포트의 값은 선언이 아니라 증거입니다. 채점기는 줄마다 해당 통제를 클러스터에서 다시 확인해, 적힌 status 와 실제 상태가 다르면 실패시킵니다. 2·3·5·6단계에서 본 결과를 각각 어느 통제 항목에 대응시킬지 생각해 보고, 적기 전에 한 번 더 확인하세요.

기본 네임스페이스는 누가 지키고 있나

PSA enforce 라벨이 없는 네임스페이스는 컴플라이언스 결함(gap)입니다. 클러스터의 default 네임스페이스에 enforce 라벨이 있는지 확인하고, 결함이 있는 네임스페이스 이름을 /root/kcsa-comp/gaps.txtpsa-missing-namespace=<네임스페이스> 한 줄로 적습니다. (라벨을 붙여 고치지는 마세요 — 이 단계는 발견을 기록하는 단계입니다.)

모든 네임스페이스의 라벨을 한꺼번에 보려면 kubectl get ns 에 라벨 열을 추가하는 옵션을 쓰세요. 방금 강화한 네임스페이스와 아무도 손대지 않은 네임스페이스를 나란히 놓고 비교하면 결함이 보입니다.