K8s Security - 01. rbac
"누가 무엇을 할 수 있는가" — RBAC가 결정하는 권한의 단위
한 클러스터에서 서비스 계정 하나가 모든 namespace의 Secret을 읽을 수 있었다. 개발자가 편하려고 ClusterAdmin을 줬기 때문이다. 그 서비스 계정이 탈취됐을 때, 공격자는 클러스터 전체의 비밀을 한 번에 들냈다. 이 사고의 원인은 RBAC의 오용이었다 — "편의를 위해 최대 권한"이 곧 "침해 시 최대 피해"가 된다.
이 글이 푸는 것은: Kubernetes RBAC(Role-Based Access Control)이 "누가 무엇을 할 수 있는가"를 어떻게 결정하고, 최소 권한 원칙을 어떻게 실천하는가다. 03-k8s-security 영역의 출발점이자, 보안의 첫 번째 축이다.
권한 제어의 세 축 — 누가, 무엇을, 어디서
"이 요청을 허용할까?"를 결정하려면 apiserver가 세 가지를 알아야 한다:
- 누가(Subject): 요청한 사람이나 서비스 계정.
- 무엇을(Verb/Resource): get/list/create/delete... 어떤 리소스에.
- 어디서(Scope): 한 namespace 안인가, 클러스터 전체인가.
이 세 축의 조합을 Kubernetes는 네 개의 객체로 표현한다 — Role, ClusterRole, RoleBinding, ClusterRoleBinding. 왜 네 개나 필요한지, 하나로는 안 되는지부터 보자. (Kubernetes docs - Using RBAC Authorization)
권한 규칙을 별도 객체로 뺀다 — Role
권한을 제어하려면 "이 작업은 허용"이라는 규칙을 어딘가에 적어둬야 한다. Kubernetes는 이 규칙을 Role이라는 객체로 분리한다. Role 안에는 "어떤 리소스에 어떤 동사를 허용한다"는 규칙(rule)이 들어간다.
# Kubernetes 1.36
apiVersion: rbac.authorization.k8s.io/v1
kind: Role # namespace 내 권한 규칙
metadata: {name: pod-reader, namespace: app}
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
이 Role은 "app namespace에서 pods와 pods/log에 get/list/watch를 허용"이라는 규칙 하나를 담는다. 규칙만 있고, 이 규칙을 누구에게 줄지는 아직 없다.
권한과 대상의 분리 — Binding이 왜 별개인가
Role에 규칙만 두고 대상(Subject)을 같이 넣으면 안 되나? "이 Role은 my-sa에게 적용"을 Role 안에 적으면 될 것 같다. 왜 RoleBinding이라는 별도 객체를 만들었을까?
답은 재사용성이다. 하나의 Role(예: "Pod 읽기")을 여러 Subject에게 주고 싶을 때, Role을 복제할 게 아니라 "누구에게 연결하나"만 바꾸면 된다. Role은 규칙의 정의, RoleBinding은 규칙과 대상의 연결 — 이 분리가 "같은 권한을 여러 계정에, 다른 권한을 같은 계정에" 유연하게 조합하게 만든다.
# Kubernetes 1.36
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: {name: read-pods, namespace: app}
subjects:
- kind: ServiceAccount
name: my-sa
namespace: app
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
이제 my-sa는 app namespace의 Pod를 읽을 수 있다. 다른 namespace의 Pod는 못 읽는다 — Role이 namespace 종속이라 app에만 적용되기 때문.
flowchart LR
S["Subject<br/>(user/SA/group)"] --> BIND["RoleBinding<br/>(연결)"]
ROLE["Role<br/>(권한 규칙)"] --> BIND
BIND -. 권한 부여 .-> S
namespace 범위와 클러스터 범위 — Role vs ClusterRole
Role은 한 namespace 안에서만 효력이 있다. 그런데 클러스터 전체 자원(Node, PersistentVolume, namespace 자체)은 namespace에 속하지 않는다 — 이런 자원을 다루는 권한은 Role로 표현할 수 없다. 그래서 ClusterRole이 별도로 있다.
| Role | ClusterRole | |
|---|---|---|
| 적용 범위 | 한 namespace | 클러스터 전체(또는 namespace 자원 템플릿) |
| 클러스터 범위 자원(Node, PV 등) | 못 다룸 | 다룸 |
| RoleBinding과 | 한 namespace에 한정 | 특정 namespace에 적용 가능(템플릿) |
| ClusterRoleBinding과 | X | 클러스터 전체 적용 |
Binding도 같은 논리로 둘로 나뉜다: RoleBinding은 한 namespace에, ClusterRoleBinding은 클러스터 전체에 연결.
ClusterRole을 RoleBinding으로 — 템플릿 패턴
여기에 재미있는 패턴이 있다. ClusterRole을 정의*해 두고 RoleBinding으로 *특정 namespace에만 연결할 수 있다. 왜 이렇게 하가? "같은 권한 규칙을 namespace마다 복제"하는 노력을 줄이기 위해서다. 예를 들어 view라는 ClusterRole을 만들어두고, RoleBinding으로 namespace마다 연결하면 — "namespace마다 같은 Role YAML을 복사" 없이 일관된 읽기 권한을 줄 수 있다. ClusterRole이 namespace 자원의 템플릿 역할을 하는 것이다.
동사(verbs) — 권한의 해상도
권한은 "이 리소스에 뭘 할 수 있다"에서 "뭘"에 해당하는 부분이다. Kubernetes는 이 "뭘"을 세밀한 동사로 나눈다:
get: 단일 객체 조회.list: 목록 조회.watch: 감시(스트림).create/update/patch/delete: 쓰기.*: 와일드카드(모든 동사) — 위험, 최소 권한 원칙 위반.
이 해상도가 왜 중요한가. "Pod를 볼 수 있다"에도 get(하나), list(목록), watch(실시간)가 있는데, 이 셋의 의미가 다르다. list만 주면 실시간 감시는 안 되고, watch만 주면 단일 조회가技术上 안 될 수 있다. 권한 범위를 정확히 좁히려면 이 동사 단위로 설계해야 한다.
watch를 주면 list도 사실상 허용되는 경우가 많다 — watch 응답에 객체가 포함되기 때문. 권한 범위를 좁힐 때 이 미묱함을 고려해야 한다. "list는 막았는데 watch를 줬다"가 사실상 list를 여는 함정.
Subject의 세 종류 — 왜 User는 객체가 없나
Subject(권한을 받는 대상)는 세 종류다:
- User: 외부 인증(OIDC, X.509)으로 식별되는 사람. 특이하게, Kubernetes에는 User 객체가 없다 — 이름만 존재한다. 인증 시스템(OIDC provider 등)이 "이 사람은 alice다"라고 알려주면, RBAC은 그 이름만 본다. 그래서 User를
kubectl create user로 만들 수 없다. - Group: User들의 집합(OIDC 그룹 등). 역시 객체 없이 이름만.
- ServiceAccount: Pod/자동화용 계정. 이것은 객체로 존재한다 —
kubectl create serviceaccount로 만들 수 있다(02-authentication에서 자세히).
왜 User는 객체가 없는데 ServiceAccount는 있나? User는 클러스터 바깥의 인증 시스템이 관리하고, ServiceAccount는 클러스터 안의 자원이라서. 이 차이를 모르면 "User를 만들려는데 안 된다"는 혼란이 생긴다.
최소 권한 원칙 — 왜 ClusterAdmin을 주면 안 되나
도입의 사고로 돌아가자. ClusterAdmin은 모든 것을 할 수 있는 슈퍼 권한이다. "편의"로 주면:
- 그 계정이 탈취되면 클러스터 전체 장악.
- 감사 추적이 무의미(다 할 수 있으니 "누가 뭘 했나" 추적 의미 약화).
- 피해 반경이 최대.
최소 권한 원칙은 이 사고의 교훈을 원칙으로 만든 것이다: 필요한 만큼만 준다. 앱이 Pod를 읽기만 하면 Pod get/list만. namespace 한정이면 RoleBinding만. 이것이 "침해 시 피해 최소화"의 기본이다.
권한 평가 알고리즘 — deny 없음, 합집합만
한 Subject에 여러 Role/ClusterRole이 연결돼 있을 수 있다. "이 요청 허용?"의 평가는:
- 요청의 (verb, resource, namespace)를 추출.
- 이 Subject에 연결된 모든 Role/ClusterRole의 규칙을 모은다(union).
- 규칙 중 하나라도 (verb, resource)를 허용하면 → 허용.
- 아무것도 매칭 안 되면 → 거부.
핵심: deny 규칙이 없다. RBAC은 허용만 정의한다. 권한을 안 주면 자동으로 거부. 여러 Role이 있으면 그 허용의 합이 권한이 된다 — "두 Role이 충돌한다"가 아니라 "둘 다 허용하면 더 넓게". 이 모델이 "이 User만 거부"를 RBAC으로 직접 못 하는 이유이고, 그래서 ValidatingAdmissionPolicy(04장) 같은 별도 수단이 필요하다.
권한 점검의 함정 — 직접 vs 간접
kubectl auth can-i가 즉시 답을 주지만, 간접 권한을 놓칠 수 있다:
- Pod를
exec할 권한이 없어도 — Pod를create(pods/exec 서브리소스) 권한이 있으면 exec 가능. - Secret을
get할 권한이 없어도 — Secret을 포함한 Pod를get하면 환경 변수로 노출.
이런 간접 경로를 RBAC만으로 막기 어렵다. 그래서 어드미션 정책(04장)이나 Pod Security Admission(05장)이 보완한다. "RBAC으로 충분하다"는 가정이 위험한 이유.
권한 검사 경계 — subresource와 impersonation
kubectl auth can-i가 도구지만, 미묘한 경계가 있다:
- subresource:
pods/exec,pods/log,pods/portforward는 별도 권한이다.pods의get이pods/exec를 포함하지 않는다. - impersonation:
--as로 다른 신원 테스트 시, impersonate 권한 자체가 필요하다. 없으면 "다른 신원으로 검사" 자체가 거부된다.
이 경계를 모르면 "exec 권한을 줬는데 안 된다"(실제론 pods/exec을 안 줌) 같은 혼란이 생긴다. can-i 검사 시 subresource까지 명시적으로 확인하는 습관이 필요하다.
기본 ClusterRole — 이미 정의된 권한
Kubernetes는 자주 쓰는 권한을 기본 ClusterRole로 제공한다:
| ClusterRole | 권한 | 바인딩 대상 |
|---|---|---|
cluster-admin |
모든 것(슈퍼유저) | 최소, 운영자 극소수 |
admin |
namespace 내 대부분(리소스별 RBAC, Role/RoleBinding 포함) | namespace 관리자 |
edit |
namespace 내 읽기/쓰기(RBAC 객체 제외) | 개발자 |
view |
namespace 내 읽기 전용 | 모니터링, QA |
이 기본 Role을 적절히 Group에 연결하면 대부분의 권한 설계가 해결된다. 단 cluster-admin은 정말 극소수에게만 — 최소 권한 원칙.
escalation prevention — 자기 권한을 초과해 권한을 주지 못하게
보안상 중요한 규칙: 자기가 없는 권한을 다른 이에게 줄 수 없다. Role/RoleBinding을 만들 때, 그 Role의 권한이 작성자 자신의 권한을 초과하면 apiserver가 거부한다(기본 동작). 이것이 escalation prevention이다.
왜 필요한가? 이 보호가 없으면 edit 권한 사용자가 자기에게 cluster-admin RoleBinding을 만들어 권한을 상승시킬 수 있다. escalation prevention이 이 권한 상승 경로를 막는다. (Kubernetes docs - Privilege escalation)
이것이 "RBAC 자체가 RBAC으로 보호된다"는 재귀적 안전장치다. 사용자가 권한을 늘리는 행위를 통제. 감사(audit, 10장)로 이런 시도를 추적.
aggregation — ClusterRole 합성
aggregationRule로 여러 ClusterRole을 합쳐 새 ClusterRole을 만들 수 있다. 예: "monitoring" ClusterRole = "pods-reader" + "services-reader" + "...". selector로 합성 대상을 지정한다. (Kubernetes docs - Aggregated ClusterRoles)
왜 유용한가? 대규모에서 권한이 복잡해지면 "이 팀은 이것저것 필요"를 하나의 거대한 Role로 적기보다, 작은 Role들을 합성하는 쪽이 관리하기 쉽다. 구성 ClusterRole이 바뀌면 합성된 ClusterRole도 자동 갱신 — "monitoring 권한"의 정의가 한곳에서 바뀌면 모든 곳에 반영.
can-i로 권한 검증 — 그리고 직접 확인하기
# Kubernetes 1.36 — 특정 Subject가 무엇을 할 수 있는가
kubectl auth can-i list pods --as=system:serviceaccount:app:my-sa -n app
kubectl auth can-i '*' '*' --as=... # 모든 것?
확인할 것: yes/no로 권한 즉시 확인. 권한 설계 검증의 핵심 도구.
실제로 Role/Binding을 만들고 검증하는 전체 사이클:
# Kubernetes 1.36 — Role/RoleBinding 만들고 권한 검증
kubectl create role pod-reader --verb=get,list --resource=pods -n default
kubectl create serviceaccount my-sa -n default
kubectl create rolebinding my-sa-read --role=pod-reader --serviceaccount=default:my-sa -n default
kubectl auth can-i list pods --as=system:serviceaccount:default:my-sa -n default # yes
kubectl auth can-i list secrets --as=system:serviceaccount:default:my-sa -n default # no
확인할 것: Pod list는 yes, Secret list는 no — 최소 권한이 작동함. "편의로 다 주는" 대신 "필요한 것만"이 RBAC 설계의 본질.
RBAC와 audit의 연결 — 권한 변경 추적
RBAC 변경(Role/RoleBinding 생성/수정)이 audit log(10장)에 기록된다. "누가 언제 권한을 바꿨나"를 사후 추적 가능. 침해 조사에서 핵심 — "공격자가 cluster-admin RoleBinding을 만들었나?"를 audit에서 찾는다. 그래서 RBAC 객체에 대한 RequestResponse 수준 감사가 권장된다(10장).
bootstrap token의 RBAC — 일회용 권한
kubeadm(14장)의 bootstrap token이 노드 합류에 쓰인다. 이 토큰이 어떤 권한을 갖나? 제한적이다:
- 인증서 서명 요청(CSR) 권한 — 노드 전용 인증서 받기.
- 몇 가지 기본 노드 등록 권한.
- 영구적 권한 없음 — 합류 후엔 노드가 자기 인증서로.
bootstrap token의 RBAC이 이 최소 일회용 권한을 표현한다. 토큰이 탈취돼도 합류 절차 외엔 못 한다(그리고 24시간 만료). 이것이 "일회용 신원의 권한 설계"의 예 — 도입의 최소 권한 원칙이 부트스트랩에도 적용된 것.
흔히 묻는 것, 흔히 틀리는 것
| 오해 | 정정 |
|---|---|
| "ClusterAdmin은 관리자니까 주자" | 침해 시 최대 피해. 최소 권한 원칙 위반 |
| "Role은 항상 namespace 종속이다" | Role은 그렇지만 ClusterRole은 클러스터 전체(또는 템플릿) |
| "RoleBinding은 ClusterRole을 못 가리킨다" | 가리킬 수 있음(namespace에 한정 적용 패턴) |
| "watch는 list와 무관하다" | watch 응답에 객체가 포함돼 사실상 list 허용되는 경우 多 |
| "User 객체를 만들어야 RBAC이 된다" | User는 객체 없이 이름만. 인증(OIDC/X.509)이 식별 |
| "권한은 deny 규칙으로 좁힌다" | RBAC은 허용만(deny 없음). 허용을 안 주면 거부 |
| "Role과 Binding을 하나로 합치면 간단하다" | 분리가 재사용성의 핵심. 같은 Role을 여러 Subject에 |
요약 — 이 글의 결론
- RBAC이 "누가 무엇을 어디서"를 결정 — Subject(누가) + Role(무엇) + Binding(scope)의 조합. 세 축이 네 객체로 표현된다.
- Role과 Binding이 분리된 이유는 재사용성 — Role은 권한 규칙의 정의, Binding은 규칙과 대상의 연결. 같은 권한을 여러 계정에, 다른 권한을 같은 계정에 유연하게.
- namespace 범위(Role)와 클러스터 범위(ClusterRole)가 나뉜 이유는 자원의 성격 — 클러스터 범위 자원(Node, PV)은 namespace에 속하지 않으므로 ClusterRole이 필요.
- ClusterRole을 RoleBinding으로 특정 namespace에 적용하는 템플릿 패턴이 반복 정의를 줄임.
- 최소 권한 원칙이 보안의 기본 — ClusterAdmin "편의" 부여 = 침해 시 최대 피해. 필요한 만큼만.
- RBAC은 허용(deny 없음). 권한을 안 주면 거부. deny 규칙이 필요하면 별도(ValidatingAdmissionPolicy, 04장).
- 간접 권한 함정 — RBAC만으로 Pod exec/Secret 노출 등 간접 경로를 다 못 막음. 어드미션 정책/PSA가 보완.
kubectl auth can-i가 권한 설계 검증의 핵심 도구. 단 subresource/간접 경로는 별도 확인.
생각해 볼 문제
- 한 SA가 app namespace의 Pod만 읽어야 한다. Role로 할까 ClusterRole+RoleBinding으로 할까? 이유는?
- ClusterAdmin을 줬더니 침해 피해가 컸다. "편의" 없이 최소 권한을 설계하는 원칙을 3가지 써라.
- RBAC에 deny 규칙이 없다면 "이 User만 거부"를 어떻게 구현하나? (어드미션 정책 예고)
watch권한을 주면list도 사실상 가능한 이유를 watch 응답 구조로 설명하라.- Role과 Binding이 분리되지 않고 하나의 객체였다면, 어떤 관리 문제가 생기나?
- SA가 탈취됐다. 피해를 줄이려면 이 SA의 RBAC을 어떻게 다시 설계해야 하나?
edit권한 사용자가 자기에게cluster-admin을 주려 했는데 거부됐다. 어떤 메커니즘이 막았나?
참고
- Kubernetes 공식 문서 - Using RBAC Authorization - 접근 2026-07-13
- Kubernetes 공식 문서 - Role/ClusterRole/Binding - 접근 2026-07-13
- Kubernetes 공식 문서 - kubectl auth can-i - 접근 2026-07-13
- Kubernetes 공식 문서 - Privilege escalation prevention - 접근 2026-07-13
- Kubernetes 공식 문서 - Aggregated ClusterRoles - 접근 2026-07-13
- Kubernetes 1.36 API - RBAC - 접근 2026-07-13
'Tech Artifacts > K8s Security' 카테고리의 다른 글
| K8s Security - 02. authentication (0) | 2026.07.16 |
|---|