LabHub
배우기 러닝패스 코스

KCSA — Kubernetes Security Associate

If You Can Create Roles, Can You Become Admin?

LabHub 에서 이어서 보기

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

목표

낮은 권한 서비스어카운트로 RBAC 권한 상승을 시도해, apiserver 의 권한 상승 방지(escalate/bind)가 실제로 그 시도를 막는 것을 관찰합니다. 그리고 정당한 관리자만이 권한을 넘길 수 있으며, 임퍼소네이션 역시 별도로 보호되는 권한임을 확인해 리포트로 남깁니다.

왜 중요한가

RBAC 에서 가장 위험한 실수는 "롤을 만들 수 있는 권한" 을 준 것이 사실상 "무엇이든 될 수 있는 권한" 을 준 것과 같아지는 경우입니다. 만약 롤 생성 권한만 있으면 자기에게 시크릿 권한을 담은 롤을 만들어 붙일 수 있다면, 그 서비스어카운트는 곧 클러스터 관리자입니다.

쿠버네티스는 이를 apiserver 수준에서 막습니다 — 자기가 현재 갖지 않은 권한을 담은 롤을 만들거나 바인딩할 수 없습니다(권한 상승 방지). 그래서 롤 생성 권한을 줘도 그것이 곧바로 특권 확대로 이어지지 않습니다. 이 경계가 코드가 아니라 apiserver 의 승인 단계에 있다는 것을, 그리고 impersonate 같은 동사가 왜 따로 위험 동사로 취급되는지를 직접 눈으로 보는 것이 이 실습의 핵심입니다.

단계

  1. 네임스페이스 kcsa-priv 와 서비스어카운트 lowpriv 를 만듭니다.
  2. lowpriv 에게 롤·롤바인딩 생성 권한만 주는 Role rolemaker 를 바인딩합니다.
  3. 관리자로 표적 ClusterRole secret-admin(시크릿 get/list)을 만듭니다.
  4. lowpriv 인 척 시크릿 권한 롤을 만들려다 거절되는 것을 확인해 저장합니다.
  5. lowpriv 인 척 secret-admin 을 자신에게 바인딩하려다 거절되는 것을 확인해 저장합니다.
  6. 관리자가 정식으로 secret-admin 을 lowpriv 에 바인딩해, 이제 시크릿을 읽을 수 있게 합니다.
  7. 임퍼소네이션 권한 없는 서비스어카운트 auditor2 가 남인 척 못한다는 것을 확인합니다.
  8. 무엇이 막히고 무엇이 통했는지 report.txt 에 기록합니다.

참고

낮은 권한 주체를 준비하다

네임스페이스 kcsa-priv 와 그 안에 서비스어카운트 lowpriv 를 만듭니다. 아직 아무 권한도 주지 않습니다.

서비스어카운트는 네임스페이스 안의 주체(subject)입니다. create 를 --dry-run=client 로 만들어 apply 하면 여러 번 돌려도 안전합니다. 이 단계에서는 lowpriv 에게 어떤 롤도 바인딩하지 않습니다.

롤을 만들 권한만 쥐여 주다

Role rolemaker 를 네임스페이스 kcsa-priv 에 만듭니다 — apiGroups rbac.authorization.k8s.io, resources roles,rolebindings, verbs create,get,list. 그리고 이 Role 을 서비스어카운트 lowpriv 에 RoleBinding(rolemaker-binding)으로 묶습니다. lowpriv 는 롤·롤바인딩을 만들 수는 있지만 시크릿 권한은 없어야 합니다.

RBAC 은 롤(권한 묶음)과 롤바인딩(주체에 연결)으로 나뉩니다. kubectl create role·create rolebinding 에 --verb·--resource·--serviceaccount 를 씁니다. lowpriv 는 이 단계 이후 롤 생성 권한은 얻지만 시크릿에 대한 권한은 여전히 없다는 점을 기억하세요 — 다음 단계들의 핵심입니다.

탐나는 표적 — 시크릿 읽기 권한

관리자 권한으로 ClusterRole secret-admin 을 만듭니다 — 코어 그룹 secrets 에 대한 get,list 권한. 이후 단계에서 lowpriv 가 이 권한을 손에 넣으려 시도하는 표적입니다.

ClusterRole 은 네임스페이스에 매이지 않는 권한 묶음입니다. kubectl create clusterrole 에 --verb·--resource 를 씁니다. 이 단계는 기본 kubeconfig(cluster-admin)로 그냥 만들면 됩니다.

가지지 않은 권한을 스스로에게 주려다 막히다

이제 lowpriv 인 척(--as=system:serviceaccount:kcsa-priv:lowpriv) 네임스페이스 kcsa-priv 에 시크릿 get 권한을 담은 Role sneaky 를 만들려고 시도합니다. lowpriv 는 시크릿 권한이 없으므로 apiserver 가 이 생성을 거절해야 합니다. 거절 메시지를 그대로 /root/kcsa-priv/escalate-denied.txt 에 저장합니다.

RBAC 에는 권한 상승 방지 규칙이 있습니다 — 자기가 갖지 않은 권한을 담은 롤을 만들 수 없습니다. 롤을 만들 권한(2단계)이 있어도 이 규칙은 별개로 걸립니다. 명령이 실패하는 것이 정상이니, 표준 에러까지 함께 파일로 받으세요(2>&1). 메시지에 'forbidden' 과 'not currently held' 가 보일 것입니다.

표적 권한을 스스로에게 묶으려다 막히다

lowpriv 인 척(--as=system:serviceaccount:kcsa-priv:lowpriv) ClusterRole secret-admin 을 자신(lowpriv)에게 묶는 RoleBinding grab 을 네임스페이스 kcsa-priv 에 만들려고 시도합니다. 이 역시 lowpriv 가 갖지 않은 권한을 넘기는 것이라 거절돼야 합니다. 거절 메시지를 /root/kcsa-priv/bind-denied.txt 에 저장합니다.

lowpriv 는 롤바인딩을 만들 수 있습니다(2단계). 하지만 자기가 가지지 않은 권한을 담은 롤을 바인딩하려면 그 권한을 이미 가지고 있거나 대상 롤에 대한 bind 권한이 있어야 합니다. 둘 다 없으니 apiserver 가 막습니다. 실패가 정상이니 2>&1 로 에러까지 받으세요.

관리자가 정식으로 권한을 넘기다

이번엔 관리자 권한(기본 kubeconfig)으로 ClusterRole secret-admin 을 서비스어카운트 lowpriv 에 묶는 RoleBinding granted 를 네임스페이스 kcsa-priv 에 만듭니다. 이제 lowpriv 는 kcsa-priv 에서 시크릿을 get 할 수 있어야 합니다.

경계는 lowpriv 의 '롤바인딩 생성 능력' 이 아니라 apiserver 의 권한 상승 검사였습니다. 이미 그 권한을 가진 관리자가 바인딩을 만들면 통과합니다. --clusterrole 과 --serviceaccount 로 바인딩을 만들고, auth can-i get secrets --as=<lowpriv> 가 yes 로 바뀌는지 확인하세요.

남인 척하는 권한도 따로 지켜진다

네임스페이스 kcsa-priv 에 서비스어카운트 auditor2 를 만듭니다(임퍼소네이션 권한은 주지 않습니다). auditor2 가 다른 사용자인 척(impersonate users)할 수 있는지 확인해, 권한 없는 주체는 임퍼소네이션을 하지 못한다는 것을 봅니다.

임퍼소네이션(--as)은 그 자체가 별도의 RBAC 권한(users/groups/serviceaccounts 리소스에 대한 impersonate 동사)입니다. 권한을 주지 않은 서비스어카운트는 auth can-i impersonate users --as=<그 SA> 가 no 여야 합니다. 관리자(기본 kubeconfig)는 yes 라는 점과 대비해 보세요.

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

관찰 결과를 /root/kcsa-priv/report.txt 에 정확히 네 줄로 적습니다 — self-escalate-role=blocked, self-bind-clusterrole=blocked, admin-grant=allowed, impersonate-without-verb=blocked. 값은 실제 클러스터 동작과 일치해야 합니다.

채점기는 이 네 줄을 클러스터에서 다시 확인합니다 — lowpriv 로 새 자기-상승 시도가 실패하는지, 관리자 바인딩 이후 lowpriv 가 시크릿을 get 할 수 있는지, auditor2 가 임퍼소네이션을 못하는지. 추측으로 적지 말고 앞 단계에서 본 결과를 그대로 옮기세요.