CKS — Kubernetes Security Specialist
Narrowing RBAC and Blocking Tokens
한국어 원문으로 표시합니다.
목표
클러스터에서 과도한 권한을 가진 역할을 직접 찾아내고, 최소 권한 Role 로 대체한 뒤 그 결과를 API 서버에 물어서 확인합니다. ServiceAccount 토큰이 파드에 자동으로 들어가는 경로도 전부 닫습니다.
왜 중요한가
RBAC 은 "누가 무엇을 할 수 있는가"를 정하지만, 정작 그것을 사람이 읽어서 검증하기는 매우
어렵습니다. ClusterRole 하나에 resources: ["*"] 가 섞여 있으면 그 역할은 앞으로 추가될
CRD 까지 전부 포함하게 되고, 그 사실을 아무도 눈치채지 못합니다. 그래서 하드닝의 실제 작업은
"정책을 잘 쓰는 일"이 아니라 "지금 무엇이 허용돼 있는지 목록으로 뽑는 일"에서 시작합니다.
두 번째 축은 자격증명입니다. 파드는 기본적으로 토큰을 받습니다. 애플리케이션이 API 를 쓰지
않아도 받습니다. 파드 하나가 뚫리면 공격자는 즉시 클러스터 자격증명 하나를 얻는 셈입니다.
automountServiceAccountToken: false 는 한 줄이지만 침해 시 확산 범위를 크게 줄입니다.
단계
- 네임스페이스
cks-rbac을 만들고 그 안에 ServiceAccountreport-sa를 만든다. - 클러스터의 모든 ClusterRole 중
rules[].verbs에*가 들어간 것들의 이름만 골라 사전순으로 정렬해/root/cks-rbac-hardening/wildcard-roles.txt에 한 줄에 하나씩 저장한다. - ClusterRole
cks-pod-reader를 만든다. apiGroups 는 코어 그룹(빈 문자열) 하나, resources 는pods와pods/log, verbs 는get·list·watch세 개다. 어느 필드에도*가 있으면 안 된다. - 클러스터의 모든 ClusterRole 중
escalate,bind,impersonate중 하나라도 verbs 에 가진 것들의 이름만 정렬해/root/cks-rbac-hardening/danger-verb-roles.txt에 저장한다. cks-rbac네임스페이스에 RoleBindingreport-sa-pod-reader로report-sa에 ClusterRolecks-pod-reader를 바인딩한다. 그다음 아래 두 질문의 답(yes또는no)을 순서대로/root/cks-rbac-hardening/can-i.txt에 두 줄로 저장한다. 첫 줄은report-sa가cks-rbac에서 pods 를 list 할 수 있는지, 두 줄은report-sa가cks-rbac에서 secrets 를 get 할 수 있는지다.cks-rbac에 파드report(이미지nginx:1.27-alpine)를 만든다.serviceAccountName은report-sa, 파드 스펙의automountServiceAccountToken은false다.cks-rbac에 Secretreport-sa-token을 만든다. 타입은kubernetes.io/service-account-token, 어노테이션kubernetes.io/service-account.name값은report-sa다. 그리고 파드token-app(이미지nginx:1.27-alpine, serviceAccountNamereport-sa)을 만들되 토큰은 projected 볼륨으로 받는다. 볼륨 이름은api-token,serviceAccountToken소스의audience는api,expirationSeconds는3600,path는token이다.cks-rbac의defaultServiceAccount 에automountServiceAccountToken: false를 설정하고, default SA 가 이 네임스페이스에서 pods 를 list 할 수 없다는 것(no)을/root/cks-rbac-hardening/default-sa-can-i.txt에 한 줄로 저장한다.
참고
- 와일드카드 감사 예:
kubectl get clusterrole -o json | jq -r '.items[] | select(...) | .metadata.name' | sort kubectl auth can-i list pods -n cks-rbac --as=system:serviceaccount:cks-rbac:report-sakubectl create clusterrole cks-pod-reader --verb=get,list,watch --resource=pods,pods/log- 흔한 실수 1:
automountServiceAccountToken을 컨테이너 스펙 안에 넣습니다. 파드 스펙 바로 아래입니다. - 흔한 실수 2: RoleBinding 의 subjects 에서 ServiceAccount 의
namespace를 빠뜨리면 바인딩이 아무 효과가 없습니다.
네임스페이스와 전용 ServiceAccount
네임스페이스 cks-rbac 을 만들고 그 안에 ServiceAccount report-sa 를 만든다.
kubectl create serviceaccount 로 만듭니다. 네임스페이스를 먼저 만들지 않으면 SA 생성이 실패합니다.
와일드카드 ClusterRole 찾아내기
클러스터의 모든 ClusterRole 중 rules[].verbs 에 * 가 들어간 것들의 이름만 골라
사전순으로 정렬해 /root/cks-rbac-hardening/wildcard-roles.txt 에 한 줄에 하나씩 저장한다.
kubectl get clusterrole -o json 을 jq 로 훑어 rules 중 verbs 에 * 가 들어간 것을 고릅니다. 결과는 이름만, 한 줄에 하나씩 저장합니다.
좁힌 ClusterRole 작성
ClusterRole cks-pod-reader 를 만든다. apiGroups 는 코어 그룹(빈 문자열) 하나,
resources 는 pods 와 pods/log, verbs 는 get·list·watch 세 개다.
어느 필드에도 * 가 있으면 안 된다.
kubectl create clusterrole --resource= --verb= 로 초안을 만들 수 있습니다. 와일드카드는 apiGroups·resources·verbs 어디에도 쓰지 않습니다.
위험 동사를 가진 역할 골라내기
클러스터의 모든 ClusterRole 중 escalate, bind, impersonate 중 하나라도 verbs 에 가진
것들의 이름만 정렬해 /root/cks-rbac-hardening/danger-verb-roles.txt 에 저장한다.
escalate, bind, impersonate 세 동사 중 하나라도 가진 ClusterRole 이 대상입니다. 02 단계와 같은 방식으로 이름만 정렬해 저장합니다.
auth can-i 로 결과 확인
cks-rbac 네임스페이스에 RoleBinding report-sa-pod-reader 로 report-sa 에
ClusterRole cks-pod-reader 를 바인딩한다. 그다음 아래 두 질문의 답(yes 또는 no)을
순서대로 /root/cks-rbac-hardening/can-i.txt 에 두 줄로 저장한다.
첫 줄은 report-sa 가 cks-rbac 에서 pods 를 list 할 수 있는지,
두 줄은 report-sa 가 cks-rbac 에서 secrets 를 get 할 수 있는지다.
--as=system:serviceaccount:<네임스페이스>:<이름> 형식으로 주체를 가장합니다. 출력은 yes 또는 no 한 줄뿐이니 그대로 파일에 모으면 됩니다.
파드에서 토큰 자동 마운트 끄기
cks-rbac 에 파드 report(이미지 nginx:1.27-alpine)를 만든다. serviceAccountName 은
report-sa, 파드 스펙의 automountServiceAccountToken 은 false 다.
automountServiceAccountToken 은 파드 스펙 최상위(spec 바로 아래) 필드입니다. 컨테이너 안이 아닙니다.
수동 토큰 Secret 과 projected 토큰
cks-rbac 에 Secret report-sa-token 을 만든다. 타입은
kubernetes.io/service-account-token, 어노테이션 kubernetes.io/service-account.name 값은
report-sa 다. 그리고 파드 token-app(이미지 nginx:1.27-alpine, serviceAccountName
report-sa)을 만들되 토큰은 projected 볼륨으로 받는다. 볼륨 이름은 api-token,
serviceAccountToken 소스의 audience 는 api, expirationSeconds 는 3600,
path 는 token 이다.
수동 토큰 Secret 은 타입이 kubernetes.io/service-account-token 이고 kubernetes.io/service-account.name 어노테이션으로 주인을 지정합니다. 반대편은 파드의 projected 볼륨에 serviceAccountToken 소스를 두고 audience 와 만료 시간을 붙입니다.
default ServiceAccount 무력화
cks-rbac 의 default ServiceAccount 에 automountServiceAccountToken: false 를 설정하고,
default SA 가 이 네임스페이스에서 pods 를 list 할 수 없다는 것(no)을
/root/cks-rbac-hardening/default-sa-can-i.txt 에 한 줄로 저장한다.
kubectl patch serviceaccount default -n <네임스페이스> -p '...' 로 한 줄에 끝납니다. 그리고 default SA 가 정말 아무 권한이 없는지 can-i 로 확인하세요.