Certified Kubernetes Security Specialist (CKS) #4 RBAC 최소 권한 깊이 (Cluster Hardening)

CKA #9 RBAC에서 Role과 ClusterRole, RoleBinding과 ClusterRoleBinding의 조합 규칙, subjects와 rules의 구조, auth can-i로 권한을 확인하는 법을 운영자 관점에서 익혔습니다. 그 글의 목표는 누가 무엇을 할 수 있는가를 설계하고 동작하게 만드는 것이었습니다. CKS의 Cluster Hardening도메인은 그 위에 한 가지 질문을 더 얹습니다. 이 권한이 정말로 필요한 만큼만인가입니다.

RBAC을 만들 줄 아는 것과 RBAC을 안전하게 좁히는 것은 다른 능력입니다. 운영 중에는 “일단 되게 하려고” 넓은 권한을 주는 일이 흔하고, 그렇게 쌓인 과도한 권한이 공격자에게는 그대로 권한 상승 통로가 됩니다. 이번 글은 **최소 권한 원칙(least privilege)**을 RBAC에 적용해, 넓게 열린 권한을 찾아내고 꼭 필요한 범위로 좁히는 작업을 다루겠습니다. 시험에서도 “과하게 넓은 Role을 최소 권한으로 좁혀라"가 단골로 나옵니다.

최소 권한 원칙이란 #

최소 권한 원칙은 모든 주체에게 작업을 수행하는 데 꼭 필요한 권한만 주고, 그 이상은 주지 않는다는 보안 설계 원칙입니다. RBAC에 적용하면 세 가지로 풀립니다.

  • resources를 좁힌다. *(모든 리소스)가 아니라 실제로 다루는 리소스만 명시합니다.
  • verbs를 좁힌다. *(모든 동작)가 아니라 실제로 수행하는 동작만 명시합니다. 읽기만 하면 get,list,watch만 줍니다.
  • scope를 좁힌다. 클러스터 전체가 아니라 특정 네임스페이스로, 가능하면 특정 리소스 이름(resourceNames)으로 한정합니다.

공격자가 클러스터 안의 한 ServiceAccount를 탈취했다고 생각하면 원칙의 가치가 분명해집니다. 그 토큰이 secrets를 읽을 수 있으면 다른 자격증명까지 줄줄이 넘어가고, Pod를 만들 수 있으면 권한 높은 ServiceAccount를 마운트해 권한을 상승시킬 수 있습니다. 권한이 좁을수록 탈취 한 건이 만들 수 있는 피해 범위(blast radius)가 작아집니다.

과도한 권한 찾기 #

좁히려면 먼저 어디가 넓은지를 봐야 합니다. CKS에서 자주 쓰는 진단 명령부터 익히겠습니다.

특정 주체가 무엇을 할 수 있는지 나열 #

auth can-i --list는 특정 주체가 가진 권한을 한눈에 펼쳐 줍니다. --as로 사용자나 ServiceAccount를 가장해 확인합니다.

# 현재 사용자의 전체 권한 나열
kubectl auth can-i --list

# 특정 ServiceAccount의 권한 나열
kubectl auth can-i --list \
  --as=system:serviceaccount:dev:app-sa -n dev

출력에서 Resources 열에 *.*, Verbs 열에 [*]가 보이면 그 주체는 사실상 무엇이든 할 수 있다는 뜻입니다. 이것이 가장 먼저 좁혀야 할 신호입니다.

개별 동작이 되는지는 auth can-i로 한 줄로 확인합니다. kubectl auth can-i get secrets --as=system:serviceaccount:dev:app-sa -n dev에서 yes가 나오면 과도한 권한일 가능성이 있습니다.

wildcard가 위험한 이유 #

Role이나 ClusterRole의 rules에 *가 들어가는 순간 최소 권한은 깨집니다.

# 위험: 모든 리소스에 모든 동작 허용
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

이 rule은 cluster-admin과 사실상 같습니다. 새 리소스 종류가 추가돼도 자동으로 권한이 따라붙기 때문에, 의도하지 않은 권한까지 계속 넓어집니다. apiGroups,resources,verbs 어느 칸이든 *가 보이면 좁힐 대상으로 표시해 두겠습니다.

위험한 권한 목록 #

같은 verb라도 어떤 리소스에 붙느냐에 따라 위험도가 다릅니다. CKS에서 특히 경계해야 할 조합을 정리하겠습니다.

권한위험
get/list secretsSecret 안의 토큰,비밀번호,인증서를 그대로 읽음. 다른 시스템 자격증명 유출로 번짐
create pods/exec도는 컨테이너에 셸로 들어감. 노드,네트워크로 측면 이동의 발판
create pods권한 높은 ServiceAccount를 마운트한 Pod를 띄워 권한 상승
escalate (verb on roles)자신보다 넓은 권한을 가진 Role을 만들어 스스로 권한 상승
bind (verb on roles)임의의 Role을 다른 주체에 바인딩. 권한 부여 통제 우회
impersonate (users/groups/serviceaccounts)다른 주체를 가장해 그 사람 권한으로 작업
* on *사실상 cluster-admin

escalatebind는 특히 주의해야 합니다. 쿠버네티스는 보통 “자신이 가진 권한 이상은 남에게 못 준다"는 권한 상승 방지 장치를 두는데, 이 두 verb가 그 장치를 우회하는 통로입니다. secrets에 대한 get, pods/exec에 대한 create는 시험에서 “이 권한을 빼라"는 식으로 자주 등장합니다.

Role을 좁히기 #

가장 흔한 시험 유형이 넓은 Role을 최소 권한으로 좁히는 작업입니다. 좁히기 전후를 비교해 보겠습니다.

좁히기 전: 과도한 Role #

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: app-role
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

이 Role은 dev 네임스페이스 안에서 무엇이든 할 수 있습니다. 앱이 실제로는 ConfigMap 하나만 읽으면 되는데도 secrets,pods,deployments까지 전부 손댈 수 있습니다.

좁히기 후: 최소 권한 Role #

앱이 app-config라는 ConfigMap만 읽으면 된다고 하면, 다음처럼 resources,verbs,resourceNames를 모두 좁힙니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: app-role
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["app-config"]
    verbs: ["get"]

좁힌 지점은 세 가지입니다. resourcesconfigmaps로, verbsget 하나로, 그리고 resourceNamesapp-config라는 특정 객체로만 한정했습니다. 이제 이 Role을 가진 주체는 다른 ConfigMap도, secrets도, pods도 건드릴 수 없습니다.

resourceNames의 제약 #

resourceNames는 이름이 정해진 객체에 get,update,delete,patch 같은 동작을 줄 때 강력하게 좁혀 줍니다. 다만 listcreate, deletecollection에는 적용되지 않습니다. list는 이름을 모른 채 전체를 나열하는 동작이고, create는 아직 이름이 없는 객체를 만드는 동작이라 이름으로 거를 수 없기 때문입니다. 이 제약은 시험에서 헷갈리기 쉬우니 외워 두겠습니다.

RoleBinding으로 묶기 #

좁힌 Role을 특정 ServiceAccount에 연결합니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: app-role-binding
subjects:
  - kind: ServiceAccount
    name: app-sa
    namespace: dev
roleRef:
  kind: Role
  name: app-role
  apiGroup: rbac.authorization.k8s.io

Role과 RoleBinding이 모두 dev 네임스페이스에 있으므로, 이 권한은 dev 안으로 한정됩니다.

ClusterRoleBinding 남용 줄이기 #

권한을 넓히는 가장 흔한 실수가 ClusterRoleBinding입니다. ClusterRole 자체는 클러스터 범위 정의일 뿐이지만, 그것을 ClusterRoleBinding으로 묶으면 모든 네임스페이스에서 그 권한이 발동합니다.

# 위험: edit ClusterRole을 클러스터 전체에 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: app-edit-everywhere
subjects:
  - kind: ServiceAccount
    name: app-sa
    namespace: dev
roleRef:
  kind: ClusterRole
  name: edit
  apiGroup: rbac.authorization.k8s.io

dev의 ServiceAccount가 모든 네임스페이스에서 리소스를 수정할 수 있게 됩니다. 앱이 dev 안에서만 동작하면 이 권한은 과합니다.

RoleBinding으로 네임스페이스 한정 #

핵심 패턴은 ClusterRole은 그대로 두되, RoleBinding으로 묶어 특정 네임스페이스로 한정하는 것입니다. ClusterRole을 RoleBinding으로 참조하면 그 권한은 RoleBinding이 있는 네임스페이스 안에서만 발동합니다.

# 안전: edit ClusterRole을 dev 안으로만 한정
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: app-edit-in-dev
subjects:
  - kind: ServiceAccount
    name: app-sa
    namespace: dev
roleRef:
  kind: ClusterRole
  name: edit
  apiGroup: rbac.authorization.k8s.io

같은 edit ClusterRole을 쓰지만 이제 권한은 dev 네임스페이스 안으로 묶였습니다. “ClusterRoleBinding을 RoleBinding으로 바꿔 권한을 좁혀라"가 시험 단골 변형입니다. 클러스터 전역이 정말로 필요한 경우(예: 노드나 PersistentVolume처럼 네임스페이스가 없는 리소스)에만 ClusterRoleBinding을 남기겠습니다.

default ServiceAccount 권한 제거 #

네임스페이스마다 자동으로 만들어지는 default ServiceAccount는 Pod가 명시적으로 다른 SA를 지정하지 않으면 자동으로 마운트됩니다. 이 default SA에 어떤 권한도 바인딩하지 않는 것이 기본입니다. 더 나아가, 토큰 자동 마운트 자체를 끄면 공격자가 Pod를 탈취해도 API 토큰을 손에 넣지 못합니다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: dev
automountServiceAccountToken: false

Pod 단위로도 spec.automountServiceAccountToken: false로 끌 수 있습니다. 토큰이 꼭 필요한 워크로드에만 전용 ServiceAccount를 두고 명시적으로 켜는 방향이 안전합니다. 토큰 관리의 세부는 #5에서 이어 다루겠습니다.

aggregated ClusterRole 주의 #

쿠버네티스는 aggregationRule로 여러 ClusterRole을 라벨로 묶어 하나로 합치는 기능을 제공합니다. 기본 view,edit,admin ClusterRole이 이 방식으로 만들어집니다.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.example.com/aggregate-to-monitoring: "true"
rules: []

주의할 점은, 이 라벨을 가진 ClusterRole을 새로 만들면 그 규칙이 자동으로 aggregated ClusterRole에 합쳐진다는 것입니다. 누군가 해당 라벨을 붙인 ClusterRole로 secrets get을 추가하면, monitoring을 쓰던 모든 주체가 조용히 secrets를 읽게 됩니다. aggregated ClusterRole을 좁힐 때는 본체뿐 아니라 그 라벨로 합쳐지는 모든 ClusterRole을 함께 점검해야 합니다.

좁힌 권한 검증 #

좁힌 뒤에는 반드시 검증합니다. CKS 채점도 결국 “필요한 건 되고, 불필요한 건 안 되는가"를 봅니다. --as로 대상 주체를 가장해 확인하겠습니다.

# 되어야 하는 것: app-config 읽기는 yes
kubectl auth can-i get configmaps/app-config \
  --as=system:serviceaccount:dev:app-sa -n dev

# 안 되어야 하는 것: secrets 읽기는 no
kubectl auth can-i get secrets \
  --as=system:serviceaccount:dev:app-sa -n dev

# 안 되어야 하는 것: 다른 네임스페이스는 no
kubectl auth can-i get configmaps \
  --as=system:serviceaccount:dev:app-sa -n kube-system

# 전체 권한을 펼쳐 wildcard가 사라졌는지 확인
kubectl auth can-i --list \
  --as=system:serviceaccount:dev:app-sa -n dev

기대값은 필요한 동작에만 yes, 나머지에는 no가 나오는 것입니다. --list 출력에서 *.*[*]가 보이지 않으면 좁히기가 제대로 된 것입니다.

시험 포인트 #

  • 최소 권한은 resources,verbs,scope를 모두 좁히는 것입니다. resources를 특정 종류로, verbs를 실제 동작만으로, scope를 네임스페이스나 resourceNames로 한정하겠습니다.
  • wildcard는 가장 먼저 좁힐 신호입니다. rules의 apiGroups,resources,verbs 어디든 *가 보이면 제거 대상입니다.
  • 위험한 권한을 식별해 둡니다. secretsget,list, pods/execcreate, podscreate, 그리고 escalate,bind,impersonate가 핵심입니다.
  • ClusterRoleBinding을 RoleBinding으로 바꿔 네임스페이스로 한정하는 패턴이 단골입니다. ClusterRole은 그대로 두고 바인딩만 RoleBinding으로 바꾸겠습니다.
  • resourceNames는 list,create,deletecollection에 적용되지 않습니다. 이름 기반으로 좁힐 수 없는 동작이 있다는 점을 기억하겠습니다.
  • default ServiceAccount에는 권한을 주지 않고, automountServiceAccountToken: false로 토큰 마운트를 끄겠습니다.
  • **검증은 auth can-i --as**로 합니다. 필요한 건 yes, 불필요한 건 no가 나오는지 양쪽을 모두 확인하겠습니다.

정리 #

이번 글에서 잡은 것:

  • 최소 권한 원칙. resources,verbs,scope를 모두 좁혀 탈취 한 건의 피해 범위를 줄입니다.
  • 과도한 권한 찾기. auth can-i --list로 wildcard 주체를 찾고, auth can-i로 개별 동작을 확인합니다.
  • 좁히기와 한정. resourceNames로 Role을 좁히고, ClusterRoleBinding을 RoleBinding으로 바꿔 네임스페이스로 묶습니다.
  • 위험한 권한과 default SA. secrets get,pods/exec,escalate,bind,impersonate를 경계하고, default SA는 권한을 주지 않으며 토큰 마운트를 끕니다.

다음: ServiceAccount 토큰 관리 #

권한을 좁혔으니, 이제 그 권한이 실려 나가는 토큰 자체를 관리할 차례입니다.

#5 ServiceAccount 토큰 관리, API 액세스 제한, 클러스터 업그레이드에서는 ServiceAccount 토큰이 어떻게 발급되고 마운트되는지, 수명이 짧은 projected token과 자동 마운트를 끄는 법, apiserver의 익명 접근과 API 액세스를 제한하는 설정, 그리고 보안 패치를 위한 클러스터 업그레이드 흐름까지 정리하겠습니다.

X