/보안/kubectl Error from server (Forbidden) 403 원인 5가지와 RBAC 해결법
보안쿠버네티스 RBACkubectl Forbidden

kubectl Error from server (Forbidden) 403 원인 5가지와 RBAC 해결법

kubectl "Error from server (Forbidden)"는 401 인증이 아닌 RBAC 인가 실패입니다. 에러 메시지 해부표, 원인 5분기, auth can-i 진단 명령, 최소권한 Role/RoleBinding YAML 3종과 EKS aws-auth 확인법까지 정리했습니다.

kubectl Error from server (Forbidden) 403 원인 5가지와 RBAC 해결법

401이 아니라 403입니다 — 인증은 통과했는데 왜 막혔나

배포 5분 전, CI 파이프라인 로그에 이 한 줄이 찍히면 심장이 내려앉습니다.

CODE
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev:ci-deployer" cannot list resource "pods" in API group "" in the namespace "prod"

이 순간 가장 많이 벌어지는 사고가 "일단 cluster-admin 붙이고 배포부터 하자"입니다. 그리고 그 바인딩은 대부분 회수되지 않습니다. 이 글의 목적은 그 습관을 끊는 것입니다. 에러 메시지 한 줄만 읽고 30초 안에 "누가 / 무엇을 / 어디서" 막혔는지 판정하고, 다섯 가지 원인 중 하나로 좁혀서 최소권한으로 복구하는 절차를 만들어 둡니다.

먼저 범위를 명확히: 401과 403은 다른 문제입니다

구분401 Unauthorized403 Forbidden
질문"너 누구냐?" (인증)"너 그거 해도 되냐?" (인가)
대표 메시지error: You must be logged in to the server (Unauthorized)Error from server (Forbidden): ... cannot list resource ...
원인 축토큰 만료/미첨부, 인증서 만료, kubeconfig exec 플러그인 실패RBAC Role/Binding 미비, 스코프·apiGroup 불일치
해결 위치kubeconfig, 토큰 발급, IAM 인증 경로Role / ClusterRole / RoleBinding / ClusterRoleBinding

메시지가 Unauthorized 계열이라면 이 글이 아니라 kubectl Unauthorized 원인별 3분 진단·복구 런북 (EKS 재발급) 또는 kubectl Unauthorized(You must be logged in) 401 해결 6가지를 먼저 보세요. 인증이 안 되면 인가는 판정 자체가 시작되지 않습니다.

또한 API 서버 연결이 아예 안 되어 The connection to the server localhost:8080 was refused 가 뜨는 경우는 kubectl localhost:8080 refused 에러 30초 진단·복구 런북 쪽입니다.

시리즈 위치

쿠버네티스 API 요청은 인증(Authentication) → 인가(Authorization, RBAC) → 어드미션(Admission) 순으로 흐릅니다.

CODE
kubectl / SDK
   │
   ├─ ① 인증  : 이 요청의 주체는 누구인가        → 실패 시 401
   ├─ ② 인가  : 그 주체가 이 동작을 해도 되는가   → 실패 시 403 (이 글)
   └─ ③ 어드미션: 이 오브젝트 스펙을 허용할 것인가 → PSA 위반 시 create 거부

시리즈 2편에서 다룬 Pod Security Admission은 ③번, "무엇을 실행시킬 것인가"였습니다. 3편인 이 글은 ②번, "누가 무엇을 할 수 있는가"입니다. 같은 Forbidden 단어를 쓰더라도 PSA 거부 메시지에는 violates PodSecurity "restricted:latest" 같은 문구가 붙으므로 구분됩니다.

에러 원문 해부: 메시지 한 줄에 답이 다 들어 있다

적용 범위: Kubernetes v1.24 이상(RBAC v1 API 기준), kubectl 1.24+, EKS·GKE·AKS 및 kubeadm 자체 구축 클러스터 공통. 예시 출력은 일반적인 Linux 환경 기준입니다.

원문 3종

① ServiceAccount 주체 (CI/파드 내부에서 가장 흔함)

CODE
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev:ci-deployer" cannot list resource "pods" in API group "" in the namespace "prod"

② 익명 주체 (토큰 자체가 전달되지 않음)

CODE
Error from server (Forbidden): nodes is forbidden: User "system:anonymous" cannot get path "/api/v1/nodes"

③ EKS IAM 주체 (K8s 주체로 변환되지 못한 상태)

CODE
Error from server (Forbidden): deployments.apps is forbidden: User "arn:aws:iam::123456789012:role/eks-dev-role" cannot create resource "deployments" in API group "apps" at the cluster scope

필드별 해부표

메시지의 각 조각은 RBAC YAML의 특정 필드와 1:1로 대응합니다.

메시지 조각의미대응하는 YAML 위치
User "..."요청 주체(User / Group / ServiceAccount)Binding의 subjects[].kind + name (+ SA면 namespace)
cannot list거부된 verbRole/ClusterRole rules[].verbs
resource "pods"대상 리소스(복수형, 소문자)rules[].resources
pods/log 형태하위 리소스(subresource)rules[].resourcespods/log로 별도 명시
in API group ""API 그룹, ""는 corerules[].apiGroups (core는 [""])
in the namespace "prod"네임스페이스 스코프 요청Role + RoleBinding (해당 ns에)
at the cluster scope클러스터 스코프 요청ClusterRole + ClusterRoleBinding

핵심 규칙 한 줄: 메시지 끝이 in the namespace X면 그 네임스페이스에 RoleBinding을 만들면 되고, at the cluster scope면 ClusterRoleBinding이 필요합니다. 이 한 줄만 지켜도 헛수고의 절반이 사라집니다.

30초 판정표 — 주체 접두사로 갈래 잡기

주체 형태정체즉시 확인할 것
system:serviceaccount:<ns>:<sa>파드 내부 또는 CI 토큰Binding subjectsnamespace 필드가 SA의 실제 ns와 같은지
system:anonymous토큰이 아예 전달되지 않음kubeconfig의 user 블록 누락/빈 토큰. 사실상 인증 문제 → 401 런북 병행
arn:aws:iam::...:role/...EKS에서 K8s 주체로 매핑 실패aws-auth ConfigMap 또는 EKS Access Entry 매핑
kubernetes-admin, [email protected]일반 User 주체kubectl config current-context 가 의도한 컨텍스트인지
system:node:<hostname>kubeletNode authorizer / NodeRestriction 영역, 사람이 손댈 곳 아님

system:anonymous가 보이는데 익명 접근을 열어둔 적이 없다면 그건 권한 부여 문제가 아니라 자격증명 전달 문제입니다. Role을 아무리 만들어도 해결되지 않습니다.

원인 5분기: 당신의 Forbidden은 이 다섯 중 하나다

(a) 스코프 불일치 — 가장 흔한 1번 원인

Role/ClusterRole과 RoleBinding/ClusterRoleBinding의 조합은 네 가지이고, 각각 커버 범위가 다릅니다.

조합커버 범위대표 용도
Role + RoleBinding그 네임스페이스 안의 네임스페이스 리소스만팀 단위 개발자 권한
ClusterRole + ClusterRoleBinding전 네임스페이스 + 클러스터 스코프 리소스모니터링 에이전트, 클러스터 운영자
ClusterRole + RoleBindingClusterRole의 규칙을 해당 ns로만 한정해 적용공통 역할 1개 정의 후 팀별 ns에 재사용 (권장 패턴)
Role + ClusterRoleBinding불가능 (RoleBinding만 Role을 참조 가능)

여기서 놓치기 쉬운 두 가지:

  • nodes, persistentvolumes, namespaces, storageclasses, clusterroles 같은 클러스터 스코프 리소스는 RoleBinding으로 아무리 묶어도 접근할 수 없습니다.
  • "pods를 전체 네임스페이스에서 list 하고 싶다"도 ClusterRoleBinding이 필요합니다. pods 자체는 네임스페이스 리소스지만, kubectl get pods -A는 클러스터 스코프 요청으로 평가됩니다.

(b) apiGroups 오지정

에러의 in API group "" 문자열과 Role YAML의 apiGroups가 정확히 일치해야 합니다.

리소스apiGroup 값비고
pods, services, configmaps, secrets, nodes, namespaces, persistentvolumeclaims, serviceaccounts, events""core 그룹, 빈 문자열
deployments, replicasets, daemonsets, statefulsets"apps"
jobs, cronjobs"batch"
ingresses, networkpolicies"networking.k8s.io"
horizontalpodautoscalers"autoscaling"
roles, rolebindings, clusterroles, clusterrolebindings"rbac.authorization.k8s.io"
poddisruptionbudgets"policy"
customresourcedefinitions"apiextensions.k8s.io"

리소스와 그룹 매핑이 헷갈리면 클러스터에 직접 물어보는 게 가장 정확합니다.

Bash
kubectl api-resources -o wide | head -20
CODE
NAME          SHORTNAMES   APIVERSION   NAMESPACED   KIND         VERBS
configmaps    cm           v1           true         ConfigMap    create,delete,get,list,patch,update,watch
pods          po           v1           true         Pod          create,delete,get,list,patch,update,watch
deployments   deploy       apps/v1      true         Deployment   create,delete,get,list,patch,update,watch

APIVERSIONv1이면 core(""), apps/v1이면 apps입니다. NAMESPACED 컬럼이 false면 클러스터 스코프 리소스입니다.

(c) subjects 오타 — 에러 없이 조용히 실패

RBAC의 가장 악질적인 함정은 오타가 나도 리소스 생성은 성공한다는 점입니다. 매칭 실패는 런타임에 403으로만 드러납니다.

YAML
# 잘못된 예 — 이 YAML은 apply가 성공하지만 절대 매칭되지 않습니다
subjects:
  - kind: ServiceAccount
    name: system:serviceaccount:dev:ci-deployer   # ← 풀네임을 넣으면 안 됨
YAML
# 올바른 예
subjects:
  - kind: ServiceAccount
    name: ci-deployer      # SA 이름만
    namespace: dev         # ServiceAccount subject는 namespace 필수

정리하면:

  • kind: ServiceAccountname은 SA 이름만, namespace 필수. 누락 시 조용히 매칭 실패.
  • kind: User / kind: Groupnamespace 필드를 쓰면 안 됨. 이때는 system:serviceaccount:dev:ci-deployer 같은 풀네임을 name에 넣는 표기가 유효합니다(SA를 User 형태로 지칭하는 방식).
  • apiGroup 값: User/Group은 rbac.authorization.k8s.io, ServiceAccount는 ""(생략 가능).

(d) 주체 자체가 다름

권한은 제대로 만들었는데 내가 그 주체가 아닌 경우입니다.

Bash
kubectl config current-context
kubectl config view --minify -o jsonpath='{.contexts[0].context.user}{"\n"}'

EKS라면 IAM 주체가 K8s 주체로 변환되어야 합니다. 매핑이 없으면 에러에 IAM ARN이 그대로 노출됩니다. 확인할 지점:

  • aws-auth ConfigMap의 mapRoles / mapUsers 항목 누락
  • IAM Role ARN에 /aws-reserved/sso.amazonaws.com/... 같은 경로가 섞여 매칭 실패 (SSO 역할은 경로를 제거한 ARN으로 적어야 매칭되는 것으로 알려져 있으므로, 사용 중인 EKS 버전의 공식 문서 확인 필요)
  • EKS Access Entry(신규 방식)와 aws-auth를 병행하면서 어느 쪽이 적용되는지 혼선. 최근에는 Access Entry / Access Policy 쪽으로 무게중심이 옮겨가는 흐름이므로, 진단 시 두 경로를 모두 확인해야 합니다.

(e) 하위 리소스 누락

pods 권한은 pods/log를 포함하지 않습니다. 이건 버그가 아니라 설계입니다.

상위 리소스별도 명시가 필요한 하위 리소스대표 실패 명령
pods ("")pods/logkubectl logs
pods ("")pods/execkubectl exec
pods ("")pods/portforwardkubectl port-forward
pods ("")pods/attachkubectl attach
pods ("")pods/ephemeralcontainerskubectl debug
deployments (apps)deployments/scalekubectl scale, HPA 연동
statefulsets (apps)statefulsets/scalekubectl scale sts
deployments (apps)deployments/status컨트롤러의 status 갱신
nodes ("")nodes/metrics, nodes/proxy메트릭 수집기
serviceaccounts ("")serviceaccounts/token토큰 발급 API

로그 수집기가 갑자기 로그를 못 읽거나 HPA가 deployments/scale 없이 스케일을 못 하는 사례는 실무에서 가장 자주 보고되는 유형입니다.

복붙 진단 → 최소권한 복구 → 임퍼소네이션 검증

1단계: 현재 권한 전량 덤프

--as 플래그로 다른 주체인 척(impersonation) 조회할 수 있습니다. 단, 이 명령을 실행하는 내 계정impersonate 권한이 있어야 합니다.

Bash
# 특정 ServiceAccount가 prod 네임스페이스에서 가진 권한 전량
kubectl auth can-i --list \
  --as=system:serviceaccount:dev:ci-deployer \
  -n prod

정상 출력 예시:

CODE
Resources                                       Non-Resource URLs   Resource Names   Verbs
selfsubjectaccessreviews.authorization.k8s.io   []                  []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []               [create]
                                                [/api/*]            []               [get]
                                                [/healthz]          []               [get]

위처럼 selfsubject*/healthz만 나온다면 이 SA에는 사실상 아무 권한도 없습니다(모든 인증 주체에게 붙는 system:basic-user, system:discovery 기본 권한만 보이는 상태). 원인 (c) subjects 오타 또는 바인딩 자체 부재를 의심하세요.

2단계: 개별 verb 단위 확인

Bash
kubectl auth can-i list pods --as=system:serviceaccount:dev:ci-deployer -n prod
# → no

kubectl auth can-i create deployments.apps --as=system:serviceaccount:dev:ci-deployer -n prod
# → yes

# 하위 리소스도 직접 확인 가능
kubectl auth can-i get pods/log --as=system:serviceaccount:dev:ci-deployer -n prod
# → no

# 그룹 기준 확인
kubectl auth can-i list secrets [email protected] --as-group=platform-team -n prod

yes인데 실제로는 실패한다면, RBAC은 통과했고 어드미션 단계(PSA, ValidatingWebhook) 또는 다른 인가 모듈에서 막힌 것입니다.

3단계: 이 주체에 묶인 바인딩 역추적

Bash
SUBJECT="ci-deployer"
SUBJECT_NS="dev"

kubectl get clusterrolebinding,rolebinding -A -o json \
| jq -r --arg n "$SUBJECT" --arg ns "$SUBJECT_NS" '
  .items[]
  | select(
      (.subjects // [])
      | any(
          (.name == $n and (.kind == "ServiceAccount") and (.namespace == $ns))
          or (.name == ("system:serviceaccount:" + $ns + ":" + $n))
        )
    )
  | "\(.kind)\t\(.metadata.namespace // "-")/\(.metadata.name)\t-> \(.roleRef.kind)/\(.roleRef.name)"
' | column -t

정상 출력 예시:

CODE
RoleBinding         prod/ci-deployer-deploy   -> Role/ci-deployer
ClusterRoleBinding  -/metrics-reader          -> ClusterRole/view

아무것도 출력되지 않으면 바인딩이 없거나 subjects 표기가 틀린 것입니다 → 원인 (c).

4단계: 룰 실체 확인

Bash
# ClusterRole의 rules를 사람이 읽기 좋게
kubectl describe clusterrole view | head -30

# 네임스페이스 Role 원문
kubectl get role ci-deployer -n prod -o yaml

describe 출력에서 Resources 컬럼에 pods 만 있고 pods/log가 없다면 (e) 하위 리소스 누락 확정입니다.

5단계: EKS 매핑 확인

Bash
kubectl -n kube-system get configmap aws-auth -o yaml
YAML
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: arn:aws:iam::123456789012:role/eks-node-role
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

여기에 eks-dev-role이 없으면 그 IAM 역할은 K8s 주체로 변환되지 못합니다. Access Entry 방식도 병행 확인하세요.

Bash
aws eks list-access-entries --cluster-name my-cluster
aws eks list-associated-access-policies --cluster-name my-cluster \
  --principal-arn arn:aws:iam::123456789012:role/eks-dev-role

6단계: 감사 로그에서 거부 기록 찾기

API 서버 감사 로그가 활성화되어 있다면 "decision":"forbid" 로 필터링하면 거부된 요청의 주체·verb·리소스가 그대로 나옵니다.

Bash
# 감사 로그 파일이 있는 컨트롤 플레인 노드에서
jq -c 'select(.annotations["authorization.k8s.io/decision"]=="forbid")
       | {user: .user.username, verb, uri: .requestURI,
          reason: .annotations["authorization.k8s.io/reason"]}' \
  /var/log/kubernetes/audit.log | tail -20

EKS에서는 CloudWatch Logs의 audit 로그 그룹에서 같은 필드를 조회하면 됩니다.

최소권한 복구 YAML 3종

① 읽기 전용 (위험도: 낮음)

개발자가 자기 팀 네임스페이스 상태를 조회하는 용도입니다.

YAML
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ns-readonly
  namespace: prod
rules:
  - apiGroups: [""]                       # core 그룹
    resources: ["pods", "services", "configmaps", "events",
                "persistentvolumeclaims"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
    verbs: ["get", "list", "watch"]
  # secrets는 의도적으로 제외 — get 하나로 DB 비밀번호·토큰이 전부 노출됩니다.
  # pods/log도 제외 — 로그에 개인정보·토큰이 찍히는 경우가 흔합니다.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ns-readonly-dev-team
  namespace: prod
subjects:
  - kind: Group
    name: dev-team                        # User/Group은 namespace 필드 없음
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: ns-readonly
  apiGroup: rbac.authorization.k8s.io

② 디버깅용 (위험도: 높음 — 시간 제한 필수)

YAML
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ns-debugger
  namespace: prod
  annotations:
    security.internal/expires-at: "2026-08-05T00:00:00Z"   # 회수 기한 명시
    security.internal/ticket: "OPS-1234"
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["pods/exec", "pods/portforward"]
    verbs: ["create"]                     # exec/portforward는 create 동사입니다
  # delete는 제외 — 디버깅 중 파드 삭제로 장애를 키우는 사고 방지
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ns-debugger-oncall
  namespace: prod
subjects:
  - kind: User
    name: [email protected]
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: ns-debugger
  apiGroup: rbac.authorization.k8s.io

왜 위험한가: pods/exec은 사실상 컨테이너 내부 셸입니다. 그 파드에 마운트된 Secret 파일과 ServiceAccount 토큰을 그대로 읽을 수 있으므로, 파드의 SA 권한을 그대로 물려받는 것과 같습니다. 반드시 만료 티켓과 세트로 부여하고 감사 로그를 남기세요.

③ CI 배포용 (위험도: 중간)

YAML
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ci-deployer
  namespace: prod
rules:
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: ["apps"]
    resources: ["deployments/scale"]      # kubectl scale / HPA 연동
    verbs: ["get", "update", "patch"]
  - apiGroups: [""]
    resources: ["services", "configmaps"]
    verbs: ["get", "list", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list"]                # 롤아웃 실패 원인 확인용
  # create secrets 제외 — SA 토큰 Secret을 만들어 다른 SA 권한을 탈취하는
  # 권한 상승 경로가 열립니다. 시크릿은 External Secrets 등 별도 경로로.
  # delete deployments 제외 — 롤백은 update/patch로 충분합니다.
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-deployer
  namespace: dev
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer-prod
  namespace: prod                          # 권한이 적용될 ns
subjects:
  - kind: ServiceAccount
    name: ci-deployer
    namespace: dev                         # SA가 실제 존재하는 ns (필수)
roleRef:
  kind: Role
  name: ci-deployer
  apiGroup: rbac.authorization.k8s.io

RoleBinding의 metadata.namespace(권한이 걸리는 곳)와 subject의 namespace(SA가 사는 곳)가 다를 수 있다는 점이 헷갈리는 지점입니다. 위 예시는 dev ns의 SA에게 prod ns 권한을 주는 정상적인 구성입니다.

검증 루프

Bash
kubectl apply -f rbac.yaml

kubectl auth can-i --list \
  --as=system:serviceaccount:dev:ci-deployer -n prod

kubectl auth can-i update deployments.apps/scale \
  --as=system:serviceaccount:dev:ci-deployer -n prod

분기 판정:

결과되돌아갈 분기
여전히 기본 권한만 보임(c) subjects 오타 — kind/name/namespace 재확인
일부 리소스만 no(b) apiGroups 또는 (e) 하위 리소스 누락
-A 조회만 실패(a) 스코프 — ClusterRoleBinding 필요
can-i는 yes인데 실제 실패어드미션 단계(PSA 등) 또는 주체 불일치 (d)

파드 내부에서 실제 토큰으로 검증하고 싶다면:

Bash
kubectl exec -it <pod> -n dev -- sh -c '
  TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token);
  curl -s -o /dev/null -w "%{http_code}\n" \
    --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
    -H "Authorization: Bearer $TOKEN" \
    https://kubernetes.default.svc/api/v1/namespaces/prod/pods
'

200이면 정상, 403이면 RBAC 미비, 401이면 토큰 문제(바운드 토큰 만료·audience 불일치)입니다. 최근 클러스터는 바운드 ServiceAccount 토큰이 기본이라 토큰에 수명과 audience가 있으므로, 오래된 CI 스크립트가 캐시한 토큰을 쓰면 403이 아니라 401이 뜨는 경우도 있습니다.

하지 말아야 할 것

금기 1 — 임시 cluster-admin 부여

Bash
# 절대 하지 마세요
kubectl create clusterrolebinding temp-fix \
  --clusterrole=cluster-admin \
  --serviceaccount=dev:ci-deployer

전형적인 사고 경로는 이렇습니다. 급해서 부여 → 배포 성공 → 회수 티켓 미등록 → 수개월 방치 → CI 러너 컨테이너 침해로 SA 토큰 유출 → 전 네임스페이스 Secret 열람 → 클러스터 전체 장악. 부여했다면 만료 시각을 애노테이션과 티켓에 동시에 남기고, 다음 명령으로 정기 감사하세요.

Bash
kubectl get clusterrolebinding -o json \
| jq -r '.items[]
    | select(.roleRef.name=="cluster-admin")
    | "\(.metadata.name)\t\((.subjects // []) | map(.kind + ":" + .name) | join(","))"' \
| column -t

금기 2 — 와일드카드

verbs: ["*"], resources: ["*"], apiGroups: ["*"]는 ISMS-P·SOC 2 등 각종 인증 심사에서 최소권한 원칙 위반으로 자주 지적되는 지점입니다(구체적 판정 기준은 해당 심사 기준 문서 확인 필요). 특히 다음 verb는 별도로 관리하세요.

verb위험
escalate자신이 가진 권한을 넘어서는 Role을 생성 가능
bind임의의 ClusterRole을 자기 자신에게 바인딩 가능
impersonate다른 사용자·그룹으로 위장해 요청 가능
create on serviceaccounts/token다른 SA 토큰 발급 → 권한 탈취
create on pods원하는 SA를 붙인 파드를 띄워 권한 상승

대응 체크리스트

  1. 에러 원문에서 주체 / verb / resource / apiGroup / 스코프 5개 필드를 분리한다
  2. 주체 접두사로 30초 판정 (SA / anonymous / IAM ARN / User)
  3. 원인 5분기 중 하나로 좁힌다 (스코프 · apiGroup · subjects · 주체 · 하위 리소스)
  4. 최소권한 Role/Binding만 추가한다 (와일드카드·cluster-admin 금지)
  5. kubectl auth can-i --list --as=... 로 임퍼소네이션 검증
  6. 부여 이력과 만료 기한을 Git에 남긴다 (RBAC as Code)

다음 편 예고

4편에서는 Secrets와 ServiceAccount 토큰 관리를 다룹니다. automountServiceAccountToken: false로 불필요한 토큰 마운트 차단, 바운드 토큰의 수명·audience 설정, External Secrets Operator를 통한 외부 시크릿 연동까지 이어집니다. 이 글의 "왜 create secrets를 CI Role에서 뺐는가"가 4편의 출발점입니다.

공식 참고 문서: Kubernetes 공식 문서의 "Using RBAC Authorization", "Authorization Overview" 문서, AWS EKS 사용 설명서의 클러스터 액세스 관리(Access Entry) 항목을 함께 확인하세요.

자주 묻는 질문 (FAQ)

Q. kubectl auth can-i --list --as=... 를 실행했더니 제 계정이 cannot impersonate 라고 나옵니다. A. 임퍼소네이션 자체가 별도 권한입니다. users, groups, serviceaccounts 리소스에 대한 impersonate verb가 필요합니다. 다만 이 권한은 사실상 다른 사용자로 위장할 수 있는 강력한 권한이므로, 상시 부여보다는 감사 대상 관리자 계정에 한정하거나 대상 SA의 토큰으로 직접 --token 옵션을 써서 확인하는 방식을 권합니다.

Q. RoleBinding을 만들었는데도 kubectl get pods -A가 계속 Forbidden입니다. A. -A(전체 네임스페이스) 조회는 클러스터 스코프 요청으로 평가되므로 RoleBinding으로는 커버되지 않습니다. ClusterRole + ClusterRoleBinding이 필요합니다. 특정 네임스페이스만 봐도 된다면 -n <ns>를 명시해서 호출하도록 스크립트를 수정하는 편이 권한 측면에서 훨씬 안전합니다.

Q. EKS에서 Access Entry를 설정했는데도 여전히 IAM ARN이 그대로 에러에 찍힙니다. A. aws-auth ConfigMap과 Access Entry를 병행 중이라면 클러스터의 인증 모드 설정(API, API_AND_CONFIG_MAP, CONFIG_MAP)에 따라 어느 쪽이 실제로 적용되는지가 달라집니다. aws eks describe-clusteraccessConfig.authenticationMode를 먼저 확인하고, ARN 문자열이 SSO 경로(/aws-reserved/...)를 포함하는지도 함께 대조하세요.

✦ ✦ ✦
편집 검토 · Editorial Review

AI 도구는 자료 조사와 초안 작성의 보조 수단으로 사용될 수 있습니다. Nodelog는 공개 전 내용과 출처를 검토하고, 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서를 함께 확인하도록 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 ·

댓글

첫 번째 댓글을 남겨보세요.