infra
Platform

모듈 맵

[Kubernetes] RBAC(Role-based Access Control) 기반 다중 사용자 보안

0 / 29 완료

펼치기
0 / 29 완료0%

Kubernetes · 15 / 29

[Kubernetes] RBAC(Role-based Access Control) 기반 다중 사용자 보안

Role, ClusterRole, RoleBinding으로 최소 권한 원칙을 적용하고 403 Forbidden을 디버깅합니다

🚨INCIDENT ALERT
HIGH

CI/CD 파이프라인이 운영 배포 중 403 Forbidden을 내며 멈췄습니다. 반대로 권한을 넓게 주면 실수로 모든 Namespace를 수정할 수 있는 위험이 생깁니다. RBAC은 Kubernetes API 접근을 필요한 만큼만 허용하는 운영 보안의 기본입니다.

CI/CD 파이프라인에서 새벽 3시에 배포가 실패했습니다. 로그를 보니 Error from server (Forbidden): deployments.apps is forbidden: User "system:serviceaccount:ci:ci-sa" cannot list resource "deployments" in API group "apps" in the namespace "production". 파이프라인을 고치기 위해 급하게 cluster-admin ClusterRole을 바인딩했습니다. 배포는 성공했지만, 이제 CI 봇이 클러스터의 모든 것을 할 수 있게 되었습니다. 보안 감사에서 이 설정이 발견됐고, 즉각 수정 요청이 들어왔습니다.

Kubernetes RBAC(Role-Based Access Control)은 "누가(Subject) 어떤 리소스에(Resource) 무엇을(Verb) 할 수 있는가"를 제어합니다. 올바르게 설계된 RBAC는 사고가 발생했을 때 피해 범위를 제한하는 가장 효과적인 방어선입니다. CI 봇은 자신이 배포하는 네임스페이스의 Deployment만 수정할 수 있어야 하고, 로그 수집 에이전트는 pods를 읽을 수만 있어야 합니다. 이 원칙을 최소 권한(Principle of Least Privilege)이라고 합니다.

이번 챕터에서 배울 것
  • 1RBAC 핵심 4종: Role, ClusterRole, RoleBinding, ClusterRoleBinding
  • 2ServiceAccount: 파드에 부여하는 K8s ID
  • 3최소 권한 원칙으로 권한 설계
  • 4kubectl auth can-i로 권한 시뮬레이션
  • 5403 Forbidden 디버깅 워크플로
  • 6CI/CD ServiceAccount 권한 설계 실전
실습 환경 준비
현재 사용자 권한 확인
kubectl auth can-i '*' '*' --all-namespaces
실습용 네임스페이스 생성
kubectl create namespace rbac-demo && kubectl create namespace production
기존 ClusterRole 목록 확인
kubectl get clusterroles | grep -v system | head -10
기존 ClusterRoleBinding 확인
kubectl get clusterrolebindings | grep -v system | head -10
💡개념

RBAC 핵심 개념: 4가지 리소스

CI/CD 봇이 새벽에 배포 실패 알림을 보냅니다. 로그를 보니 403 Forbidden입니다. 급하게 cluster-admin을 부여하면 배포는 되지만 보안 감사에서 지적을 받게 됩니다. 반대로 너무 좁게 주면 다음 배포에서 또 같은 오류가 납니다. RBAC는 "누가 어떤 리소스에 무엇을 할 수 있는가"를 명시적으로 선언하는 시스템입니다. Role과 ClusterRole은 권한의 내용을 정의하고, RoleBinding과 ClusterRoleBinding은 그 권한을 특정 주체에게 연결합니다. 이 네 가지 리소스의 관계를 이해하면 최소 권한 원칙을 정확하게 설계할 수 있습니다.

RBAC는 권한 정의(Role/ClusterRole)와 권한 부여(RoleBinding/ClusterRoleBinding)로 나뉩니다.

RBAC 핵심 개념: 4가지 리소스

Subject (누가)           Binding (연결)            Role (무엇을)
─────────────────        ─────────────────        ──────────────────
User                     RoleBinding              Role (네임스페이스)
Group          ────────► ClusterRoleBinding ────► ClusterRole (클러스터 전체)
ServiceAccount

Verb (동작) 종류:

  • get, list, watch — 읽기
  • create, update, patch — 쓰기
  • delete, deletecollection — 삭제
  • * — 모든 동작

Resource 예시:

  • 네임스페이스 리소스: pods, deployments, services, configmaps, secrets
  • 클러스터 리소스: nodes, persistentvolumes, namespaces, clusterroles
YAML
# Role 예시: 특정 네임스페이스의 pods 읽기만 허용
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: rbac-demo    # 이 네임스페이스에서만 유효
rules:
- apiGroups: [""]          # "" = core API group (pods, services, configmaps 등)
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
YAML
# ClusterRole 예시: 모든 네임스페이스의 노드 정보 읽기
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
  resources: ["nodes", "pods"]
  verbs: ["get", "list"]
Kubernetes
# 빌트인 ClusterRole 확인 (재사용 권장)
kubectl describe clusterrole view       # 읽기 전용
kubectl describe clusterrole edit       # 읽기/쓰기 (secrets 제외)
kubectl describe clusterrole admin      # 네임스페이스 전체 관리
kubectl describe clusterrole cluster-admin  # 클러스터 전체 관리
💡개념

ServiceAccount와 RoleBinding: 파드에 권한 부여

파드 안에서 실행되는 배포 자동화 도구나 모니터링 에이전트는 Kubernetes API를 직접 호출해야 하는 경우가 있습니다. 그런데 이 파드에 사람 계정의 자격증명을 넣으면 보안 사고 시 피해 범위가 클러스터 전체로 확대됩니다. ServiceAccount는 파드 전용 K8s 내부 ID입니다. 필요한 권한만 Role로 정의하고 ServiceAccount에 바인딩하면, 그 파드는 설계된 동작만 수행할 수 있고 나머지는 모두 거부됩니다. 인프라를 변경해도 파드를 재배포할 필요 없이 바인딩만 수정하면 됩니다.

ServiceAccount는 파드(애플리케이션)에 할당되는 Kubernetes 내부 ID입니다. RoleBinding으로 ServiceAccount에 Role을 연결합니다.

ServiceAccount와 RoleBinding: 파드에 권한 부여

YAML
# 1. ServiceAccount 생성
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-sa
  namespace: ci
---
# 2. CI가 production 네임스페이스의 Deployment를 관리할 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deploy-manager
  namespace: production
rules:
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
# 3. ci 네임스페이스의 ci-sa에 production의 deploy-manager Role 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deploy-binding
  namespace: production    # 이 네임스페이스에서 권한 적용
subjects:
- kind: ServiceAccount
  name: ci-sa
  namespace: ci            # ServiceAccount가 있는 네임스페이스
roleRef:
  kind: Role
  name: deploy-manager
  apiGroup: rbac.authorization.k8s.io
위험 명령어Git이나 매니페스트와 다른 임시 상태가 생기고 잘못된 패치가 즉시 운영 트래픽에 영향을 줄 수 있습니다.

운영 리소스 직접 패치

안전한 실행 조건: 변경 내용을 코드에 반영할 계획이 있고 영향 범위를 검토했을 때만 실행하세요.

실행 전 반드시 확인

  • 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
  • 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
  • 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl patch deployment my-app -n production

위 항목을 모두 확인한 후 복사할 수 있습니다

Kubernetes
kubectl apply -f ci-rbac.yaml

# 파드에 ServiceAccount 지정
# (지정 안 하면 네임스페이스의 default ServiceAccount 사용)
kubectl patch deployment my-app -n production \
  --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/serviceAccountName","value":"ci-sa"}]'
💡개념

kubectl auth can-i: 권한 시뮬레이션

403 Forbidden이 발생했을 때, 또는 권한을 배포 전에 검증하고 싶을 때 사용합니다.

Kubernetes
# 현재 사용자 권한 확인
kubectl auth can-i list pods -n production
# yes

# 다른 주체로 가장해서 확인 (--as)
kubectl auth can-i list pods \
  --as=system:serviceaccount:ci:ci-sa \
  -n production
# no  ← 권한 없음

# 어떤 권한이 있는지 전체 확인
kubectl auth can-i --list \
  --as=system:serviceaccount:ci:ci-sa \
  -n production
# Resources             Non-Resource URLs   Resource Names   Verbs
# deployments.apps      []                  []               [get list watch update patch]
# pods                  []                  []               [get list watch]

# 특정 동작 확인
kubectl auth can-i create deployments \
  --as=system:serviceaccount:ci:ci-sa \
  -n production
# yes

kubectl auth can-i delete nodes \
  --as=system:serviceaccount:ci:ci-sa
# no  ← 클러스터 레벨 권한 없음

실습: 읽기 전용 사용자 설정

Kubernetes
# 읽기 전용 ServiceAccount 생성
kubectl create serviceaccount readonly-user -n rbac-demo

# 빌트인 view ClusterRole을 RoleBinding으로 연결 (네임스페이스 범위)
kubectl create rolebinding readonly-binding \
  -n rbac-demo \
  --clusterrole=view \
  --serviceaccount=rbac-demo:readonly-user

# 권한 검증
kubectl auth can-i list pods \
  --as=system:serviceaccount:rbac-demo:readonly-user \
  -n rbac-demo
# yes

kubectl auth can-i delete pods \
  --as=system:serviceaccount:rbac-demo:readonly-user \
  -n rbac-demo
# no  ← 삭제 권한 없음

kubectl auth can-i list pods \
  --as=system:serviceaccount:rbac-demo:readonly-user \
  -n production
# no  ← 다른 네임스페이스 접근 불가

Jenkins/GitHub Actions 파이프라인이 쿠버네티스 배포 단계에서 실패합니다. 로그에 Error from server (Forbidden): deployments.apps is forbidden이 출력됩니다.

Kubernetes
# 1단계: 에러 메시지에서 주체(Subject)와 동작 파악
# "User "system:serviceaccount:ci:jenkins-sa" cannot update resource
#  "deployments" in API group "apps" in the namespace "production""
# → 주체: ci 네임스페이스의 jenkins-sa ServiceAccount
# → 동작: production 네임스페이스의 deployments 업데이트

# 2단계: ServiceAccount 존재 여부 확인
kubectl get serviceaccount jenkins-sa -n ci
# Error from server (NotFound)  ← SA 자체가 없음!
# 또는
# NAME         SECRETS   AGE
# jenkins-sa   0         5m

# 2-1: SA가 없다면 생성
kubectl create serviceaccount jenkins-sa -n ci

# 3단계: 현재 바인딩된 권한 확인
kubectl get rolebinding,clusterrolebinding -A \
  | grep jenkins-sa
# (아무 출력 없음 — 바인딩이 없음)

# 4단계: 필요한 최소 권한 파악 후 Role 생성
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: jenkins-deployer
  namespace: production
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "update", "patch"]
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jenkins-deploy-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: jenkins-sa
  namespace: ci
roleRef:
  kind: Role
  name: jenkins-deployer
  apiGroup: rbac.authorization.k8s.io
EOF

# 5단계: 권한 검증
kubectl auth can-i update deployments \
  --as=system:serviceaccount:ci:jenkins-sa \
  -n production
# yes  ← 이제 가능

kubectl auth can-i delete deployments \
  --as=system:serviceaccount:ci:jenkins-sa \
  -n production
# no  ← 삭제는 불가 (최소 권한 원칙)

# 6단계: Kubeconfig에 ServiceAccount 토큰 설정 (Jenkins)
# K8s 1.24+ 에서는 토큰을 수동으로 생성해야 함
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: jenkins-sa-token
  namespace: ci
  annotations:
    kubernetes.io/service-account.name: jenkins-sa
type: kubernetes.io/service-account-token
EOF

TOKEN=$(kubectl get secret jenkins-sa-token -n ci \
  -o jsonpath='{.data.token}' | base64 -d)

# Jenkins에 이 TOKEN 값을 Kubernetes credential로 등록
echo "Token: ${TOKEN:0:30}..."
🔍실행 후 확인할 것
  • NAME조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
  • STATUS/READYRunning, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
  • RESTARTS/EVENTS재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.

예방 패턴: CI/CD 파이프라인 구성 시 ServiceAccount, Role, RoleBinding을 GitOps 방식으로 관리하고, 배포 전에 kubectl auth can-i --list로 권한을 검증하는 단계를 파이프라인에 포함하세요. 절대로 cluster-admin을 임시방편으로 사용하지 마세요.

💼
실무 맥락
현업 패턴

시나리오: 멀티팀 클러스터에서 팀별 네임스페이스 권한 설계

스타트업이 성장해 팀이 세 개(frontend, backend, data)로 분리됐습니다. 각 팀은 자신의 네임스페이스만 관리할 수 있어야 하고, 서로의 작업을 방해할 수 없어야 합니다.

로컬 터미널
# 1단계: 팀별 네임스페이스 생성
for team in frontend backend data; do
  kubectl create namespace $team
done

# 2단계: 팀별 ServiceAccount 생성
for team in frontend backend data; do
  kubectl create serviceaccount "${team}-admin" -n $team
done

# 3단계: 각 팀에 자신의 네임스페이스 admin 권한 부여
# 빌트인 admin ClusterRole을 각 네임스페이스 내에서만 적용
for team in frontend backend data; do
  kubectl create rolebinding "${team}-admin-binding" \
    -n $team \
    --clusterrole=admin \
    --serviceaccount="${team}:${team}-admin"
done

# 4단계: 팀 간 격리 검증
kubectl auth can-i create deployments \
  --as=system:serviceaccount:frontend:frontend-admin \
  -n frontend
# yes ← 자신의 네임스페이스

kubectl auth can-i create deployments \
  --as=system:serviceaccount:frontend:frontend-admin \
  -n backend
# no ← 다른 팀 네임스페이스 접근 불가

# 5단계: 공통 읽기 권한 (팀 간 파드 상태 공유 필요 시)
# 모든 팀이 다른 팀의 파드를 읽기 전용으로 볼 수 있도록
for team in frontend backend data; do
  for target in frontend backend data; do
    if [ "$team" != "$target" ]; then
      kubectl create rolebinding "${team}-view-${target}" \
        -n $target \
        --clusterrole=view \
        --serviceaccount="${team}:${team}-admin"
    fi
  done
done

# 6단계: ResourceQuota로 팀별 리소스 제한
for team in frontend backend data; do
  cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-quota
  namespace: $team
spec:
  hard:
    pods: "20"
    requests.cpu: "4"
    requests.memory: "8Gi"
    limits.cpu: "8"
    limits.memory: "16Gi"
EOF
done

실무 포인트: 직접 Role을 만드는 것보다 빌트인 ClusterRole(view, edit, admin)을 RoleBinding으로 네임스페이스 범위에서 사용하는 것이 관리가 간편합니다. 새 팀원 온보딩, 서비스 계정 추가 등 반복 작업은 스크립트화해두면 휴먼 에러를 줄일 수 있습니다.

핵심 요약

리소스범위용도
Role네임스페이스특정 네임스페이스 리소스 권한 정의
ClusterRole클러스터 전체모든 네임스페이스 또는 클러스터 리소스 권한 정의
RoleBinding네임스페이스Subject에 Role/ClusterRole 연결 (네임스페이스 내)
ClusterRoleBinding클러스터 전체Subject에 ClusterRole 연결 (전체 범위)
디버깅 명령어용도
kubectl auth can-i <verb> <resource> -n <ns>현재 사용자 권한 확인
kubectl auth can-i --list --as=<subject> -n <ns>특정 주체 전체 권한 나열
kubectl get rolebinding,clusterrolebinding -A | grep <name>바인딩 찾기
kubectl describe rolebinding <name> -n <ns>바인딩 상세 확인

지식 확인

퀴즈 — 3문제

Q1

Role과 ClusterRole의 핵심 차이점은?

Q2

kubectl auth can-i list pods --as=system:serviceaccount:ci:ci-sa -n production 명령어의 목적은?

Q3

ServiceAccount에 ClusterRole을 바인딩할 때 RoleBinding과 ClusterRoleBinding 중 어느 것을 사용해야 하며, 그 차이는?

0 / 3 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

kubectl로 nginx Pod를 생성하고 Deployment와 Service를 차례로 만들어 클러스터 외부에서 접근 가능한 상태까지 구성한다. K8s 3대 리소스의 역할과 관계를 직접 손으로 익힌다.

40📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

kubernetes고급 · 65
[Kubernetes] NetworkPolicy를 활용한 특정 파드 간 통신 차단 및 제한
Kubernetes 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점