본문으로 건너뛰기

9.2 RBAC

인증을 통과한 요청은 인가(authorization) 단계에서 “이 신원에게 이 동작이 허용되는가"를 판정받는다. Kubernetes의 표준 인가 방식이 RBAC(Role-Based Access Control)이다. RBAC의 리소스는 네 개뿐이다. 권한 묶음을 정의하는 Role과 ClusterRole, 그 묶음을 주체에게 부여하는 RoleBinding과 ClusterRoleBinding이다. 기억할 전제가 하나 있다. RBAC에는 허용 규칙만 있고 거부(deny) 규칙이 없다. 아무 Binding에도 해당하지 않는 동작은 전부 거부되고, 권한은 부여한 만큼만 늘어난다.

네 리소스의 조합

Role은 특정 네임스페이스 안에서 유효한 권한 묶음이고, ClusterRole은 네임스페이스에 묶이지 않은 권한 묶음이다. 정의만으로는 아무 일도 일어나지 않으며, Binding이 User, Group, ServiceAccount 같은 주체(subject)와 연결해야 효력이 생긴다. 무엇을 무엇으로 부여하느냐에 따라 권한 범위가 달라진다.

정의부여권한이 미치는 범위
RoleRoleBindingRole이 있는 네임스페이스
ClusterRoleRoleBindingRoleBinding이 있는 네임스페이스
ClusterRoleClusterRoleBinding클러스터 전체
RoleClusterRoleBinding허용되지 않는 조합

권한 규칙의 문법은 세 필드다. 무엇에 대해(apiGroups + resources) 무슨 동작을(verbs) 허용하는지 적는다.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: pod-reader
rules:
- apiGroups: [""]                  # core API 그룹은 빈 문자열
  resources: ["pods", "pods/log"]  # 하위 리소스는 슬래시로
  verbs: ["get", "list", "watch"]

apiGroups의 빈 문자열은 Pod, Service처럼 core 그룹에 속한 리소스를 뜻하고, Deployment라면 apps를 적는다. verbs는 get, list, watch, create, update, patch, delete, deletecollection이 기본 목록이다. 읽기 권한이라 하면 get만 떠올리기 쉬운데, kubectl get pods는 목록 조회라 list가 필요하고 컨트롤러류는 변경 감지를 위해 watch까지 요구한다. ClusterRole은 여기에 더해 Node나 PersistentVolume처럼 네임스페이스에 속하지 않는 리소스, 그리고 /healthz 같은 nonResourceURLs도 다룬다.

부여는 Binding이 담당한다.

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: read-pods
subjects:
- kind: User
  name: jane
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

roleRef는 생성 후 변경이 거부되는 불변 필드다. 다른 Role을 가리키게 하려면 Binding을 삭제하고 다시 만든다.

ClusterRole을 RoleBinding으로 좁혀 쓰기

같은 권한 묶음이 네임스페이스마다 필요할 때, Role을 네임스페이스 수만큼 복제하면 정의가 흩어지고 수정할 곳도 늘어난다. 권한 정의는 ClusterRole 하나로 두고 네임스페이스마다 RoleBinding으로 부여하는 패턴이 이를 해결한다. 위 조합표의 둘째 줄이다. ClusterRole이라는 이름과 달리, RoleBinding으로 부여하면 권한은 그 RoleBinding이 있는 네임스페이스로 한정된다.

내장 ClusterRole인 view, edit, admin이 정확히 이 용도로 제공된다.

kubectl create rolebinding dev-view \
  --clusterrole=view --group=dev-team -n dev

내장 ClusterRole들은 aggregated ClusterRole이기도 하다. aggregationRule에 적힌 label selector와 일치하는 다른 ClusterRole들의 rules를 컨트롤러가 자동으로 합산해 넣는다. CRD를 배포하는 컴포넌트가 aggregation label을 붙인 ClusterRole을 함께 배포하면, 기존 view나 edit에 새 리소스의 권한이 흡수된다.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: aggregate-widgets-view
  labels:
    rbac.authorization.k8s.io/aggregate-to-view: "true"
rules:
- apiGroups: ["widgets.example.com"]
  resources: ["widgets"]
  verbs: ["get", "list", "watch"]

최소 권한으로 설계한다

RBAC 설계 원칙은 필요한 동작만, 필요한 리소스에, 필요한 범위로 허용하는 것이다. verbs, resources, apiGroups에 와일드카드 *를 넣으면 당장은 편하지만, 이후 클러스터에 추가되는 리소스와 동사까지 소급 허용되므로 권한의 상한을 예측하지 못하게 된다. cluster-admin을 개별 사용자에게 직접 부여하는 것도 같은 이유로 피한다.

특별히 주의할 지점이 몇 가지 있다. Secret에 대한 get, list 권한은 그 네임스페이스의 자격 증명 전체를 읽는 권한과 같다. Pod create 권한은 임의의 ServiceAccount를 지정한 Pod를 만들어 그 토큰을 획득하는 경로가 되므로, ServiceAccount의 권한과 분리해 생각하기 어렵다. escalate, bind, impersonate 같은 verb는 RBAC 자체를 조작하는 동사라 사실상 권한 상승 허가에 해당한다.

RBAC API에는 권한 상승 방지 장치가 내장돼 있다. 자신이 보유하지 않은 권한을 담은 Role을 만들거나(escalate 없이), 자신에게 없는 권한을 남에게 부여하는(bind 없이) 요청은 거부된다.

kubectl auth can-i로 검증한다

설계가 의도대로 동작하는지는 추측하지 않고 확인한다.

# 현재 신원 기준
kubectl auth can-i create deployments -n dev

# 다른 신원으로 가장(impersonation)해서 확인
kubectl auth can-i list secrets -n dev --as=jane
kubectl auth can-i get pods -n dev \
  --as=system:serviceaccount:dev:app-sa

# 특정 신원이 가진 권한 전체 나열
kubectl auth can-i --list -n dev --as=jane

--as는 impersonation 기능으로, 실행자에게 impersonate verb 권한이 있어야 동작한다. 새 Role과 Binding을 적용한 직후 대상 신원으로 can-i를 실행해 보는 습관이 권한 문제의 디버깅 시간을 크게 줄인다.

정리

권한 정의(Role, ClusterRole)와 부여(RoleBinding, ClusterRoleBinding)의 분리가 RBAC 구조의 전부이며, 넷의 조합에서 권한 범위가 결정된다. 정의는 ClusterRole로 모으고 부여는 RoleBinding으로 좁히면 관리량과 노출 범위를 함께 줄인다. 적용한 권한은 kubectl auth can-i--as 가장으로 검증한다.