Kubernetes RBAC

들어가며: kubectl이 되는데, 왜 Pod에서는 API 호출이 거부될까?

Kubernetes 클러스터에서 kubectl get pods는 잘 되는데, Pod 안에서 실행한 애플리케이션이 Kubernetes API를 호출하면 403 Forbidden을 받는 상황. 원인은 RBAC(Role-Based Access Control)이다. kubectl은 kubeconfig에 지정된 사용자 인증서로 인증되지만, Pod는 자동 마운트된 ServiceAccount 토큰으로 인증되며, 해당 ServiceAccount에 권한이 없으면 모든 API 호출이 거부된다.

이 글에서는 Kubernetes 공식 문서(Using RBAC Authorization, Configure Service Accounts for Pods)를 근거로, RBAC의 네 가지 핵심 리소스 — ServiceAccount, Role, ClusterRole, Binding — 의 관계와 설계 원칙을 실무 시나리오와 함께 다룬다.

1. RBAC 4대 리소스와 관계

1-1. 전체 구조

리소스 범위 역할
ServiceAccount Namespace Pod의 신원(identity). Pod가 API 서버에 인증할 때 사용하는 주체
Role Namespace 특정 네임스페이스 내의 리소스에 대한 권한(verbs) 정의
ClusterRole Cluster-wide 클러스터 전체 또는 비-네임스페이스 리소스(Node, PV 등)에 대한 권한 정의
RoleBinding Namespace Role/ClusterRole을 특정 네임스페이스의 주체(ServiceAccount/User/Group)에 연결
ClusterRoleBinding Cluster-wide ClusterRole을 클러스터 전체 범위로 주체에 연결

핵심 관계: 주체(Who) → Binding(연결) → Role(What). Role은 “무엇을 할 수 있는가”를 정의하고, Binding은 “누가 그 Role을 갖는가”를 연결한다.

1-2. Role vs. ClusterRole 선택 기준

사용 사례 선택 이유
특정 네임스페이스의 Pod/Service 조회 Role + RoleBinding 네임스페이스 범위로 충분
여러 네임스페이스에서 동일 권한 재사용 ClusterRole + 각 NS에 RoleBinding ClusterRole을 정의하고 RoleBinding으로 NS별 연결 가능
Node, PersistentVolume 등 비-NS 리소스 ClusterRole + ClusterRoleBinding 네임스페이스 범위가 아닌 리소스
모든 네임스페이스의 Pod 조회 (모니터링) ClusterRole + ClusterRoleBinding 전체 클러스터 범위 필요

2. ServiceAccount: Pod의 신원 관리

2-1. 기본 동작

Kubernetes 공식 문서에 따르면, 모든 네임스페이스에는 default ServiceAccount가 자동 생성된다. Pod에 serviceAccountName을 지정하지 않으면 default SA가 사용된다.

Kubernetes 1.24+부터 ServiceAccount에 대한 시크릿 자동 생성이 중단되었다. 대신 TokenRequest API를 통해 시간 제한(bound) 토큰이 자동 마운트된다(공식 문서 Bound Service Account Tokens).

2-2. 최소 권한 원칙: default SA 사용 금지

# ❌ 안티패턴: default SA에 권한을 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: default-pod-reader
  namespace: production
subjects:
  - kind: ServiceAccount
    name: default          # 모든 Pod가 이 권한을 갖게 됨!
    namespace: production
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
# ✅ 권장: 전용 ServiceAccount 생성
apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitoring-agent
  namespace: production
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: monitoring-agent
  namespace: production
spec:
  template:
    spec:
      serviceAccountName: monitoring-agent  # 전용 SA 지정
      automountServiceAccountToken: true

2-3. 토큰 자동 마운트 비활성화

API 서버에 접근할 필요가 없는 Pod는 토큰 마운트를 비활성화하는 것이 보안 모범 사례다:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: web-app
  namespace: production
automountServiceAccountToken: false  # SA 레벨에서 비활성화

또는 Pod spec에서 automountServiceAccountToken: false를 지정할 수 있다. Pod 레벨 설정이 SA 레벨보다 우선한다.

3. Role과 ClusterRole: 권한 정의의 정밀도

3-1. 기본 Role 작성

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: production
rules:
  - apiGroups: [""]           # core API group
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods/log"]   # 서브리소스
    verbs: ["get"]

3-2. verbs 전체 목록

Verb HTTP 메서드 설명
get GET (단건) 특정 리소스 조회
list GET (목록) 리소스 목록 조회
watch GET (watch) 리소스 변경 감시
create POST 리소스 생성
update PUT 리소스 전체 업데이트
patch PATCH 리소스 부분 업데이트
delete DELETE 리소스 삭제
deletecollection DELETE (목록) 리소스 일괄 삭제

주의: *(와일드카드)는 모든 verb를 의미한다. 프로덕션에서 verbs: ["*"]는 사용하지 않는다.

3-3. resourceNames로 특정 리소스만 허용

rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["app-config", "feature-flags"]  # 이 2개만 접근 가능
    verbs: ["get", "update"]

resourceNames를 지정하면 해당 이름의 리소스에만 접근을 허용한다. listcreate verb에는 resourceNames가 적용되지 않는다는 점에 주의한다(공식 문서 명시).

3-4. Aggregated ClusterRoles

Kubernetes 공식 문서에 따르면, aggregationRule을 사용하면 레이블 셀렉터로 다른 ClusterRole의 규칙을 자동 병합할 수 있다. 빌트인 admin, edit, view ClusterRole이 이 방식으로 동작한다:

# CRD 권한을 기본 admin 역할에 자동 추가
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: my-crd-admin
  labels:
    rbac.authorization.k8s.io/aggregate-to-admin: "true"  # admin에 병합
rules:
  - apiGroups: ["myapp.example.com"]
    resources: ["widgets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

4. Binding: 주체와 권한의 연결

4-1. RoleBinding 예시

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: monitoring-agent-pod-reader
  namespace: production
subjects:
  - kind: ServiceAccount
    name: monitoring-agent
    namespace: production
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

4-2. ClusterRole + RoleBinding 패턴 (재사용)

ClusterRole을 정의하고, 각 네임스페이스에 RoleBinding으로 연결하면 권한 정의는 한 번, 적용은 네임스페이스별로 할 수 있다:

# 1. ClusterRole 정의 (한 번)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
# 2. 각 NS에 RoleBinding (필요한 곳마다)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-pod-reader
  namespace: development
subjects:
  - kind: ServiceAccount
    name: ci-agent
    namespace: development
roleRef:
  kind: ClusterRole    # Role이 아닌 ClusterRole 참조
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

이 패턴에서 ci-agentdevelopment 네임스페이스의 Pod만 조회할 수 있다. ClusterRole이지만 RoleBinding의 네임스페이스 범위로 제한된다.

5. 디버깅: 권한 확인 방법

5-1. kubectl auth can-i

# 현재 사용자의 권한 확인
kubectl auth can-i create deployments --namespace production

# 특정 ServiceAccount의 권한 확인
kubectl auth can-i list pods 
  --namespace production 
  --as system:serviceaccount:production:monitoring-agent

# 모든 권한 나열
kubectl auth can-i --list 
  --as system:serviceaccount:production:monitoring-agent

5-2. 403 에러 디버깅 체크리스트

  1. Pod의 serviceAccountName이 올바른지 확인: kubectl get pod -o jsonpath='{.spec.serviceAccountName}'
  2. 해당 SA에 바인딩된 Role/ClusterRole 확인: kubectl get rolebinding,clusterrolebinding -A -o json | grep -A5 "monitoring-agent"
  3. Role의 apiGroups, resources, verbs가 실제 API 호출과 일치하는지 확인
  4. 서브리소스 접근 시 resourcespods/log, pods/exec 등 서브리소스를 명시했는지 확인
  5. automountServiceAccountTokenfalse로 설정되어 토큰이 마운트되지 않는 것은 아닌지 확인

6. 실전 설계 패턴

6-1. 네임스페이스별 역할 분리

역할 ClusterRole Binding 권한
개발자 edit (빌트인) RoleBinding (dev NS) 리소스 CRUD, 시크릿 제외
운영자 admin (빌트인) RoleBinding (prod NS) Role/RoleBinding 포함 전체 NS 관리
모니터링 커스텀 pod-reader ClusterRoleBinding 전체 NS Pod/Service 읽기
CI/CD 커스텀 deployer RoleBinding (대상 NS) Deployment/Service/ConfigMap CRUD

6-2. CI/CD Deployer Role 예시

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: deployer
rules:
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]

delete를 의도적으로 제외하여 CI/CD 파이프라인이 실수로 리소스를 삭제하지 못하게 한다.

7. 흔한 실수와 방지법

실수 증상 방지법
default SA에 권한 부여 해당 NS의 모든 Pod가 과도한 권한 획득 워크로드별 전용 ServiceAccount 생성. default SA의 automountServiceAccountTokenfalse로 설정
cluster-admin ClusterRoleBinding 남용 단일 토큰 탈취 시 클러스터 전체 장악 최소 권한 원칙. cluster-admin은 긴급 운영용으로만 사용하고 일상 작업에는 사용 금지
apiGroups 누락/오류 403 Forbidden (core group은 "", apps는 "apps") kubectl api-resources로 정확한 apiGroup 확인
서브리소스 미등록 kubectl execkubectl logs 403 resourcespods/exec, pods/log 서브리소스 명시

정리

Kubernetes RBAC은 ServiceAccount(누구) → Binding(연결) → Role(무엇)의 삼각 구조다. 최소 권한 원칙을 지키려면 ①워크로드별 전용 SA 생성, ②필요한 verb만 명시, ③ClusterRole + RoleBinding 재사용 패턴을 활용한다. kubectl auth can-i --as로 권한을 사전 검증하고, 403 에러 시 apiGroups·resources·verbs·서브리소스를 하나씩 확인하면 대부분의 문제를 해결할 수 있다.

참고 자료

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux