LabHub
学习 学习路径 课程

KCSA — Kubernetes 安全助理

审计员来了,却拿不出“已启用”的证据

在 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 에 라벨 열을 추가하는 옵션을 쓰세요. 방금 강화한 네임스페이스와 아무도 손대지 않은 네임스페이스를 나란히 놓고 비교하면 결함이 보입니다.