Tech Artifacts/K8s Security

K8s Security - 01. rbac

"누가 무엇을 할 수 있는가" — RBAC가 결정하는 권한의 단위

한 클러스터에서 서비스 계정 하나가 모든 namespace의 Secret을 읽을 수 있었다. 개발자가 편하려고 ClusterAdmin을 줬기 때문이다. 그 서비스 계정이 탈취됐을 때, 공격자는 클러스터 전체의 비밀을 한 번에 들냈다. 이 사고의 원인은 RBAC의 오용이었다 — "편의를 위해 최대 권한"이 곧 "침해 시 최대 피해"가 된다.

이 글이 푸는 것은: Kubernetes RBAC(Role-Based Access Control)이 "누가 무엇을 할 수 있는가"를 어떻게 결정하고, 최소 권한 원칙을 어떻게 실천하는가다. 03-k8s-security 영역의 출발점이자, 보안의 첫 번째 축이다.

권한 제어의 세 축 — 누가, 무엇을, 어디서

"이 요청을 허용할까?"를 결정하려면 apiserver가 세 가지를 알아야 한다:

  1. 누가(Subject): 요청한 사람이나 서비스 계정.
  2. 무엇을(Verb/Resource): get/list/create/delete... 어떤 리소스에.
  3. 어디서(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이 연결돼 있을 수 있다. "이 요청 허용?"의 평가는:

  1. 요청의 (verb, resource, namespace)를 추출.
  2. 이 Subject에 연결된 모든 Role/ClusterRole의 규칙을 모은다(union).
  3. 규칙 중 하나라도 (verb, resource)를 허용하면 → 허용.
  4. 아무것도 매칭 안 되면 → 거부.

핵심: 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는 별도 권한이다. podsgetpods/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/간접 경로는 별도 확인.

생각해 볼 문제

  1. 한 SA가 app namespace의 Pod만 읽어야 한다. Role로 할까 ClusterRole+RoleBinding으로 할까? 이유는?
  2. ClusterAdmin을 줬더니 침해 피해가 컸다. "편의" 없이 최소 권한을 설계하는 원칙을 3가지 써라.
  3. RBAC에 deny 규칙이 없다면 "이 User만 거부"를 어떻게 구현하나? (어드미션 정책 예고)
  4. watch 권한을 주면 list도 사실상 가능한 이유를 watch 응답 구조로 설명하라.
  5. Role과 Binding이 분리되지 않고 하나의 객체였다면, 어떤 관리 문제가 생기나?
  6. SA가 탈취됐다. 피해를 줄이려면 이 SA의 RBAC을 어떻게 다시 설계해야 하나?
  7. edit 권한 사용자가 자기에게 cluster-admin을 주려 했는데 거부됐다. 어떤 메커니즘이 막았나?

참고

'Tech Artifacts > K8s Security' 카테고리의 다른 글

K8s Security - 02. authentication  (0) 2026.07.16