CloudNet@ Team Study/EKS Workshop 4th Cohort

EKS Identity and Access Management

"Everything about infra" 2026. 4. 9. 02:17
포스팅은 CloudNet@팀 서종호(Gasida)님이 진행하시는
AWS EKS Workshop Study 내용을 참고하여 작성합니다.

 

안녕하세요!

EKS 운영 시에 AWS API를 호출하거나,

AWS에서 kubernetes api server를 호출하는 일이 발생하는데요,

인증 / 인가에 대한 동작 방식에 대해서

학습하는 시간을 갖도록 하겠습니다.

 

 

 

K8S API 접근 통제 흐름

인증/인가를 살펴보기 이전에, API Server 앞단의 3단계 접근 통제 흐름의 다이어그램은 아래와 같습니다.

Client → Authentication → Authorization → Admission Control → etcd

kubectl, 서비스 어카운트, 외부 시스템 등 모든 API 요청은 이 파이프라인을 통과해야 합니다.

하나라도 거부된다면 요청은 차단되는 구조에요.

 

01. Authentication (인증)

인증 주체를 분류해보면 아래와 같습니다.

주체 설명 대표 방식
User 개발자, 운영자 X.509 cert, OIDC token
Service Account (Pod) Pod 내 프로세스 (non-user) JWT (Projected Volume)

K8S는 User 오브젝트를 직접 관리하지 않습니다. 외부 IdP나 인증서로 신원을 위임해요.

 

📌 인증 방식 종류

1) X.509 클라이언트 인증서

  • kubeconfig의 client-certificate / client-key
  • CN(Common Name) → username, O(Organization) → group 으로 매핑
  • EKS에서 Control Plane이 CA 관리, kubectl 기본 인증에 사용

kubeconfig를 들여다 보면 아래와 같이 되어있습니다.

users:
- name: admin
  user:
    client-certificate: /path/to/client.crt   # 신분증
    client-key: /path/to/client.key            # 개인키

다만, EKS에서는 kubeconfig 인증서 대신에 'aws eks get-token 결과(IAM 토큰)'가 들어갑니다.

즉, 인증서 방식을 직접 쓸 일은 거의 없어요.

 

2) Static Token / Bootstrap Token

  • --token-auth-file 또는 Bootstrap Token (kube-system 네임스페이스 Secret)
  • 노드 kubelet 초기 등록 시 TLS Bootstrapping에 사용
신규 노드 → [Bootstrap Token] → API Server → "일단 들어와, 그 다음 정식 인증서 발급해줄게"

노드가 클러스터에 처음 조인할 때만 쓰이는 일회성 토큰 방식 입니다.

kube-system 네임스페이스에 secret 으로 저장되며 노드 조인이 완료가 되었을 때

kubelet 클라이언트 인증서로 교체됩니다

 

3) ServiceAccount Token (Projected Volume)

  • Pod 내 /var/run/secrets/kubernetes.io/serviceaccount/token
  • 기존 Static JWT → Bound Service Account Token (만료 시간, audience 포함)
  • serviceAccountToken 타입 Projected Volume으로 마운트
Pod 기동 → /var/run/secrets/kubernetes.io/serviceaccount/token 자동 마운트
         → Pod 내 프로세스가 이걸 들고 API Server 호출
         
         # JWT 디코딩하면 exp, aud 필드 보임

Pod가 뜰 때 자동으로 주입되며 ServiceAccount를 미지정 시에 default SA로 지정됩니다.

예전엔 만료 없는 정적 JWT였는데, 지금은 Bound Token은 만료시간 + audience 포함합니다.

 

실제로 쓰이는 곳은 Prometheus가 K8S API 긁을 때, Operator가 CRD 감시할 때 등

코드가 API를 호출하는 모든 상황에 사용됩니다.

 

4) OIDC (OpenID Connect) : 권장

kubectl → OIDC IdP (Cognito, Okta, Keycloak) → id_token → kube-apiserver 검증

 

K8S Hardening Guide가 외부 인증 메커니즘으로 OIDC를 권장하는 이유:

  • 토큰 만료/revocation 지원
  • MFA 연동 가능
  • 중앙 IdP에서 사용자 생명주기 관리

흐름을 살펴볼까요? EKS에서는 IRSA 기반 인증 방식으로 사용됩니다.

사용자/Pod → OIDC IdP에서 id_token 발급
           → API Server에 id_token 제출
           → API Server가 IdP 공개키로 토큰 검증
           → 통과시 username/group 추출

 

User 인증 예시 (Okta/Cognito 연동):

개발자 → Okta 로그인 → id_token 발급 → kubectl에 토큰 세팅 → API Server 검증

 

EKS IRSA 예시 (Pod → AWS API 호출):

Pod → EKS OIDC Provider가 발급한 token
    → AWS STS AssumeRoleWithWebIdentity
    → IAM Role credentials 획득
    → S3, DynamoDB 등 AWS 서비스 호출

Pods 별로 IAM Role을 따로 관리하여 최소 권한 원칙으로 안전하게 운영이 가능합니다.

 

EKS 특이점

EKS는 IAM identity → K8S username/group 매핑을 통해 인증합니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: arn:aws:iam::123456789:role/NodeGroupRole
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
    - rolearn: arn:aws:iam::123456789:role/AdminRole
      username: admin
      groups:
        - system:masters
  mapUsers: |
    - userarn: arn:aws:iam::123456789:user/devuser
      username: devuser
      groups:
        - dev-group

먼저 배경부터 말씀드리면, K8S는 User 오브젝트를 직접 관리하지 않는다고 말씀드렸습니다.

그렇다면 EKS에서 'kubectl' 명령을 치는 IAM User를 K8S는 어떻게 알까요?

IAM Identity → ??? → K8S username/group → RBAC

??? 자리를 채우는게 aws-auth (configmap) 입니다.

 

다시 위 ConfigMap을 살펴보면,

"NodeGroupRole ARN이 K8S의 system:node username과 system:nodes group으로 매핑"되어 있습니다.

 

다만 ConfigMap으로 운영하게 되었을 때 아래와 같은 보안 문제점을 직면하였습니다.

  • ConfigMap이라 kubectl 권한 있는 사람이 직접 수정 가능 → 보안 취약
  • 실수로 잘못 수정하면 클러스터 전체 접근 불가 (락아웃)
  • 감사(Audit) 추적이 어려움

 

Access Entry 방식 출시.

그렇게 해서, 2024년 이후에 " Access Entry" 방식이 추가가 되었습니다.

Access Entry는 "이 IAM이 EKS 클러스터에서 어떤 K8S 신원으로 동작할 것인가" 를 정의하는 오브젝트 !

IAM ARN (누구인지)
  +
K8S username / groups (클러스터 안에서 뭐라고 부를지)
  +
Access Policy (어떤 권한 줄지) ← 선택사항
= Access Entry

구성 필드를 하나씩 살펴보도록 하겠습니다.

 

1) principal ARN (필수)

누가 이 Entry의 주인인가
arn:aws:iam::123456789:role/MyRole
arn:aws:iam::123456789:user/devuser

IAM Role이 올 수도 있고, IAM User가 올 수도 있습니다.

 

2) type (어떤 종류의 접근 주체인가)

STANDARD      → 일반 사람 or 애플리케이션 (기본값)
EC2_LINUX     → 노드 그룹 (자동으로 system:nodes group 부여)
FARGATE_LINUX → Fargate 프로필

노드 그룹 Role은 EC2_LINUX로 설정하면 K8S 노드 인증에 필요한 group을 자동으로 매핑해줍니다.

 

3) username (K8S 안에서 쓸 이름)

이 IAM이 K8S API를 호출할 때 어떤 username으로 보일지
예: "admin", "devuser", "system:node:{{EC2PrivateDNSName}}"

RBAC에서 Subject의 name 필드가 이것과 매칭됩니다.

 

4) kubernetes groups (RBAC group 매핑)

["system:masters"]         → cluster-admin 수준
["system:nodes"]           → 노드 권한
["dev-team", "read-only"]  → 커스텀 group

RoleBinding/ClusterRoleBinding에서 subjects.kind: Group으로 묶인 것과 연결돼요.

 

📌 권한 부여 2가지 경로.

 

1) K8S RBAC 직접 관리

# Access Entry에 group만 매핑
--kubernetes-groups '["dev-group"]'

# 별도로 ClusterRoleBinding 생성
kubectl create clusterrolebinding dev-binding \
  --clusterrole=view \
  --group=dev-group

K8S 레벨에서 세밀하게 제어하고 싶을 때 사용합니다.

 

2) AWS 관리형 Access Policy 연결

aws eks associate-access-policy \
  --cluster-name myeks \
  --principal-arn arn:aws:iam::123456789:role/AdminRole \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy \
  --access-scope type=cluster

kubectl 안 써도 되고, AWS가 ClusterRoleBinding을 대신 관리해줍니다.

여기서, '--access-scrop'은 두 가지 형태로 관리할 수 있는데 아래와 같습니다.

type=cluster          → 클러스터 전체
type=namespace, namespaces=["dev","staging"]  → 특정 NS만

 

그래서 결론적으로 Access Entry를 실제 생성하는 예시는

# 1. Access Entry 생성
aws eks create-access-entry \
  --cluster-name myeks \
  --principal-arn arn:aws:iam::123456789:role/DevRole \
  --type STANDARD \
  --username devuser \
  --kubernetes-groups '["dev-group"]'

# 2. (선택) Access Policy 연결
aws eks associate-access-policy \
  --cluster-name myeks \
  --principal-arn arn:aws:iam::123456789:role/DevRole \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy \
  --access-scope type=namespace \
  --namespaces '["default"]'

# 3. 확인
aws eks list-access-entries --cluster-name myeks
aws eks list-associated-access-policies \
  --cluster-name myeks \
  --principal-arn arn:aws:iam::123456789:role/DevRole

 

aws-auth vs Access Entry

근본적인 차이는 K8S 오브젝트로 관리하나느냐, AWS API로 관리하느냐의 차이입니다.

aws-auth     → K8S 오브젝트 (ConfigMap) 로 관리
Access Entry → AWS API 로 관리

관리 주체에 따라 모든 차이가 파생됩니다 !

 

그래서 즉, aws-auth의 보안 취약한 단점과 락아웃되는 단점들을 보완하여

관리 주체가 AWS API인 Access Entry로 Migration 하는 추세이며, 가독성이 좋게 표로 정리하겠습니다.

항목 aws-auth Access Entry
관리 위치 kube-system ConfigMap EKS API / 콘솔
수정 권한 kubectl 권한자 IAM 권한자
락아웃 위험 있음 ⚠️ 없음 ✅
CloudTrail 감사 안 됨 됨 ✅
노드 그룹 등록 수동 type 지정으로 자동 ✅
AWS 관리형 정책 없음 있음 ✅
마이그레이션 - API_AND_CONFIG_MAP 모드로 병행 가능

 


 

02. Authorization (인가)

운영자가 "kubectl" 명령을 쳤을 때 인증을 받고나면 인가 부분에서 EKS 표준인 RBAC에 아래와 같이 물어봅니다.

1. 인증 완료
   → username: "devuser"
   → groups: ["dev-group"]
   이 두 가지가 확정됨

2. API Server가 RBAC에 물어봄
   → "devuser가 default 네임스페이스에서
      pods를 get 할 수 있어?"

3. RBAC가 RoleBinding 뒤짐
   → dev-group이 pod-reader Role에 묶여있네
   → pod-reader는 pods get 허용하네
   → 허용

 

하지만 인가 부분의 종류도 3가지로 나뉘는데요,

모드 설명 실사용
RBAC Role 기반 표준
Node kubelet 전용 EKS 기본 활성화
Webhook 외부 서버로 인가 위임 조건부

EKS는 기본적으로 RBAC + Node 조합으로 동작합니다.

 

RBAC 핵심 오브젝트

Subject (who) + RoleBinding → Role (what)
오브젝트 범위 설명
Role Namespace 특정 NS의 리소스 권한
ClusterRole Cluster-wide 전체 리소스 또는 비NS 리소스
RoleBinding Namespace Role/ClusterRole을 NS 내 Subject에 바인딩
ClusterRoleBinding Cluster-wide ClusterRole을 전역 Subject에 바인딩

 

바로 yaml 파일로 예시를 보도록 하겠습니다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-sa
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: default
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: ServiceAccount
  name: my-sa
  namespace: default
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

이렇게 SA를 생성 후 Role과 RoleBinding을 배포했다고 가정을 한다면, 아래와 같이 동작합니다.

 

1) Role 생성

클러스터에 "pod-reader라는 권한 목록"이 등록됨
→ pods 리소스에 get, watch, list만 허용

2) ServiceAccount 생성

클러스터에 "my-sa라는 신분증"이 등록됨
→ 아직 아무 권한 없음, 그냥 존재만 함

3) RoleBinding 생성

subjects: my-sa       ← "누구한테 줄 거냐"
roleRef: pod-reader   ← "뭘 줄 거냐"

= my-sa에게 pod-reader 권한을 부여

4) Pod에 serviceAccountName: my-sa 지정

Pod가 뜰 때 API Server가 확인:
→ my-sa가 클러스터에 존재하나? ✅
→ my-sa에 RoleBinding 걸려있나? ✅
→ pod-reader가 get 허용하나? ✅
→ 허용

 

Node Authorization

  • kubelet이 자신의 노드에 스케줄된 Pod 관련 리소스만 접근 가능하도록 제한
  • system:node:<nodeName> username, system:nodes group 필수
  • Node Authorizer가 처리 : kubelet이 다른 노드의 secret을 못 읽게 격리

뭔가 마지막 Node Authrorizer 부분에서 감히 잡히셨을 것 같지만 한번 더 말씀드리자면,

kubelet이 하는 일은 아래와 같습니다.

  • 자기 노드에 스케줄된 Pod 정보 읽기
  • 해당 Pod의 Secret / ConfigMap 읽기
  • 노드 상태 업데이트

인가를 RBAC로만 허용하고 Node를 사용하지 않는다면?

노드 A의 kubelet이 노드 B의 Secret도 읽을 수 있음
→ 노드 하나 털리면 클러스터 전체 Secret이 노출

Node Authorizer가 하는 일:

system:node:ip-10-0-0-1.ap-northeast-2.compute.internal
    → 자기 노드에 스케줄된 Pod 관련 리소스만 접근 허용
    → 다른 노드의 Secret, ConfigMap 접근 차단

aws-auth / Access Entry에서 노드 그룹 Role을 등록할 때 이 형식을 맞춰주는 이유입니다.

그래서 결론적으론 kubelet의 요청에서 RBAC와 Node Authorizer를 동시에 체크 후, 둘 다 허용이 되어야

할당된 권한 (파드 조회 등..)을 사용하여 운영자가 어떠한 행위가 가능한 것입니다.

 

즉, EKS는 인가 동작 방식이 RBAC + Node이 기본입니다.

 

Webhook 모드

RBAC / Node로 커버 안 되는 복잡한 정책이 필요할 때 외부 서버로 인가를 위임하는 방식 입니다.

kube-apiserver → SubjectAccessReview (HTTP POST) → 외부 서버 → allowed/denied

 

요청 예시 구조를 yaml 파일로 볼까요?

{
  "apiVersion": "authorization.k8s.io/v1",
  "kind": "SubjectAccessReview",
  "spec": {
    "user": "devuser",
    "groups": ["dev-group"],
    "resource": "pods",
    "verb": "get",
    "namespace": "default"
  }
}

외부 서버가 이걸 받아서 allowed: true/false 반환합니다.

실제로 어디에 쓰일지 한번 볼까요?

 

📌 OPA (Open Policy Agent) / Gatekeeper:

"dev 팀은 월~금 09:00~18:00에만 배포 가능"
"production NS에는 image tag가 latest인 Pod 배포 금지"
"특정 레이블 없는 리소스는 생성 불가"

이런 시간, 레이블, 비즈니스 규칙 기반 정책은 RBAC verb/resource 체계로 표현 불가능합니다.

그래서 이 방식은 Webhook으로 외부 정책 엔진에 위임하는 방식입니다.

 

그래서 결론적으로 RBAC + Node vs Webhook 뭘 사용해야 할까요?

RBAC    → "누가 어떤 리소스에 어떤 동작을" 제어
Webhook → "어떤 조건일 때" 제어 (시간, 레이블, 비즈니스 규칙 등)

둘 관계는 베타적이지 않습니다.

귀사의 내부 정책이나, 비즈니스 요구사항에 맞춰서 협의하여 운영하시면 되겠습니다.

 


03. Admission Control

전체 흐름을 한줄 요약하자면, 인증/인가에 통과한 요청에 대해서

변조 확인, 정책 검사, 외부 검사를 통해 최종적으로 허용/거부를 해주는 녀석입니다.

위 다이어그램으로 단계별로 설명드리도록 하겠습니다.

 

1) User → API Server → Authentication + Authorization

사용자가 "Pod 만들어줘" 요청
→ 인증 (너 누구야)
→ 인가 (이 작업 해도 돼)

이 부분은 위에서 설명드렸던 인증 / 인가 관련된 부분입니다.

 

2) Mutating Webhook(s)

  • "For all Validating Policies" 부분이 등록된 Mutating Webhook이 여러개면 순서대로 하나씩 돌아요 (Loop)

그렇다면, Mutating이 하는 일을 예시로 들어본다면,

- resource limit 없는 Pod → 기본값 자동 주입
- 레이블 없는 Pod → 레이블 자동 추가
- Istio sidecar 자동 삽입

여기서 중요한 점은 거부도 가능합니다. Modify or reject 라고 적혀있는 것이

수정도 가능하고 거부도 가능하다는 의미입니다.

 

3) Validating Admission Policies (정책 위반 검사)

  • 여기서도 마찬가지로 Webhook이 여러개면 순서대로 하나씩 돌아요 (Loop)

Validating이 하는 일을 예시로 들어본다면,

- image tag가 latest면 거부
- resource limit 없으면 거부
- 특정 레이블 없으면 거부

즉, Mutating과 차이는 거부만 가능하다는 점이겠네요 (Validating은 오브젝트 수정X)

 

4) Validating Webhook(s)

  • 위 두 가지 Mutating과 Validating은 Loop 였지만 이 녀석은 par 입니다.
  • 즉, 루프 도는 녀석이 아닌 병렬적으로 전부 동시에 처리합니다.

그렇다면, 왜 얘만 병렬로 처리할까요?

Mutating은 순서가 중요합니다.
→ A가 수정한 결과를 B가 봐야 하니까 직렬

Validating Webhook은 순서가 상관이 없어요.
→ 그냥 "이 오브젝트 괜찮아?" 물어보는 거라
→ 동시에 다 물어보고 하나라도 reject 하면 거부
→ 병렬로 처리해서 빠르게

 

그래서 결론적으론 아래와 같이 동작합니다.

Mutating Webhooks        루프(직렬) - 수정 가능
      ↓
Validating Policies      루프(직렬) - 거부만
      ↓
Validating Webhooks      병렬 - 거부만
      ↓
전부 통과 → etcd 저장

 


04. 실제 인증 상세 흐름도

복잡한 구조여서 인증-인가-Admission Control 단계를 지나 다시 흐름을 복습하고자 해당 챕터를 마련했습니다.

 

1) 토큰 생성 (Client Side)

  • 사용자가 'kubectl' 명령을 치기 전에 먼저 "내가 누구인지 증명할 토큰"이 필요합니다.
  • 일반 K8S는 인증서나 static token을 쓰지만 EKS는 IAM 자격증명으로 Pre-signed URL을 만들어서 토큰으로 사용해요.

'aws eks get-token' 명령을 치면 AWS CLI가 로컬에 저장된 IAM 자격증명으로

STS GetCallerIdentity URL에 서명을 해서 반환해줍니다.

핵심은 이 URL 자체가 토큰이라는 거에요 !
실제 API 호출은 아직 안 했고, "나중에 이 URL로 호출하면 내가 누구인지 증명할 수 있어" 라는 서명된 약속을 만든 것뿐입니다.
추가적으로, X-Amz-Expires가 60초로 되어있는데 토큰 만료 시간입니다. (탈취당해도 60초만 유효함)

 

2) API Server에 요청

  • 토큰이 생겼으면 이제 kubectl이 실제 요청을 보냅니다.
  • Bearer Token으로 이 Pre-signed URL을 담아서 API Server에 전달하겠죠 ?

여기서 포인트는, API Server는 이 토큰을 직접 검증할 수 없습니다.

왜냐하면 이건 AWS STS 서명이라서 K8S가 AWS IAM을 모르기 때문입니다.

 

그래서 API Server는 Webhook Token Authentication 방식으로 aws-iam-authenticator server에

검증을 위임합니다. 이게 K8S의 확장 포인트에요.

 

3) Token Review

  • API Server가 토큰 검증을 외부에 위임하는 방식이 바로 TokenReview입니다.

TokenReview는 K8S 표준 오브젝트 입니다.

API Server가 aws-iam-authenticator server에 아래와 같은 요청을 보냅니다.

"이 Bearer Token, 유효한 거야? 
 유효하면 이게 어떤 사용자야?"

aws-iam-authenticator server는 이걸 받아서 다음 단계인 STS 검증으로 넘어가겠죠.

 

4) STS 검증 및 IAM ARN 반환

aws-iam-authenticator server가 토큰(Pre-signed URL)을 실제로 실행해서 진짜 신원을 확인하는 단계.

aws-iam-authenticator server가 Pre-signed URL을 가지고 STS GetCallerIdentity를 실제로 호출합니다.

STS가 서명을 검증하고 아래와 같은 응답을 돌려주겠죠?

{
  "UserId": "SAMPLEUSERID",
  "Account": "12345678",
  "Arn": "arn:aws:iam::123456:role/k8s-admin"
}

이제 "이 요청자의 IAM ARN이 뭔지" 확정됐습니다.

 

그 다음 aws-iam-authenticator server가 aws-auth ConfigMap을 뒤져서

이 ARN이 어떤 K8S username/group으로 매핑되는지 찾습니다.

arn:aws:iam::123456:role/k8s-admin
→ username: admin
→ groups: ["system:masters"]

이걸 API Server에 돌려주면 인증(AuthN) 단계가 완료됩니다.

 

전체 흐름 요약

지금까지 설명드린 흐름을 한 장의 다이어그램으로 보면 아래와 같습니다.

① 관리자가 kubectl 명령
② AWS-IAM-Authenticator Client가 Pre-signed URL 생성 (토큰 발급)
③ kubectl이 API Server에 Action + Token 전달
④ API Server가 Id Token 확인
⑤ AWS-IAM-Authenticator Server가 STS에 GetCallerIdentity 호출
⑥ STS가 성공 응답 (IAM ARN 반환)
⑦ aws-auth ConfigMap에서 K8S User 확인 (ARN → username/group 매핑)
⑧ K8S RBAC에서 RoleBinding 확인
⑨ 허용/차단 결과 반환

결국 EKS 인증의 핵심은 IAM이라는 외부 신원 체계를 K8S가 이해할 수 있는 username, group으로

번역해주는 것이고, 그 번역 사전 역할을 하는 것이 aws-auth ConfigMap 또는 Access Entry 입니다.

 


 

05. Managing Service Accounts

ServiceAccount 토큰이 실제로 어떻게 동작할까?에 대해 다뤄보는 챕터입니다.

 

토큰 발급 방식

Pod가 뜰 때 자동으로 아래 경로에 토큰이 마운트 됩니다.

/var/run/secrets/kubernetes.io/serviceaccount/
├── token      ← JWT 토큰
├── ca.crt     ← API Server 인증서 검증용
└── namespace  ← 현재 네임스페이스

예전 방식은 Static JWT 였어요 (토큰의 만료 기간이 없었고, Secret Object로 저장됨)

토큰의 만료 기간이 없다면 토큰이 영구적으로 유효하게 되고, 탈취당하면 답이 없어집니다.

 

그래서 지금은 Bound Service Account Token 방식으로 바뀌었습니다.

  • 만료시간 있음 (기본 1시간)
  • audience 포함 (어떤 서비스용 토큰인지)
  • Pod 삭제되면 자동 무효화
  • kubelet이 만료 전에 자동 갱신

Static JWT 방식 vs Bound Service Account Token 방식을 JWT로 디코딩해서 비교해보면

// Static JWT (구버전)
{
  "iss": "kubernetes/serviceaccount",
  "namespace": "default",
  "serviceaccount": {"name": "my-sa"}
  // exp 없음 ← 만료 없음
}

// Bound Token (현재)
{
  "iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/...",
  "aud": ["https://kubernetes.default.svc"],  // audience
  "exp": 60,   // 만료시간
  "kubernetes.io": {
    "pod": {"name": "my-app"},    // 바인딩된 Pod
    "serviceaccount": {"name": "my-sa"}
  }
}

위와 같이 보이는 것을 확인하실 수 있습니다.

 

그럼 이 Bound Token을 Pod에 어떻게 전달할까요?

 

토큰을 Pod에 전달하는 방법

📌 Projected Volume으로 마운트되는 이유

단순히 토큰만 있으면 안 되고 여러 정보를 하나의 볼륨으로 묶어서 마운트하는 게 Projected Volume 입니다.

원래는 볼륨 소스마다 따로 마운트 했어야 했습니다.

volumes:
- name: my-secret
  secret:
    secretName: my-secret
- name: my-configmap
  configMap:
    name: my-config
- name: my-token
  serviceAccountToken: ...

이게 너무 번거로워서 Project Volume으로 하나로 묶어서 마운트 해버렸습니다.

volumes:
- name: all-in-one
  projected:
    sources:
    - secret:
        name: my-secret
    - configMap:
        name: my-config
    - serviceAccountToken:
        path: token
        expirationSeconds: 3607
    - downwardAPI:
        items:
        - path: namespace
          fieldRef:
            fieldPath: metadata.namespace

볼륨을 하나로 두고, 아래에 여러 리소스들을 하나로 묶어서 마운트할 수 있습니다. (기능은 동일, 편의성 증가)

 

그래서 Project Volumes 보호 대상은 아래 5가지입니다.

타입 뭘 담을까?
secret DB 비번, API 키 등 민감한 값
configMap 일반 설정값
serviceAccountToken API Server 호출용 JWT 토큰
downwardAPI Pod 자신의 메타데이터 (namespace, name 등)
clusterTrustBundle 클러스터 CA 인증서 묶음 (신규)

왜 이 5가지 보호대상을 Volumes에 묶어서 파드에 전달할까요?

 

Pod 안의 프로세스가 K8S API를 호출하려면 이 세 가지가 반드시 세트로 필요합니다.

  • token → "내가 누구인지" 증명
  • ca.crt → "API Server가 진짜인지" 검증
  • namespace → "내가 어느 NS에 있는지" 파악

이 세 가지를 매번 따로 마운트하는 게 번거롭기 때문에 Projected Volume 하나로 묶어서 자동으로 넣어줘요.

 

즉, 정리를 해보면 Project Volumes이 지원하는 소스 타입이 5가지 보호 대상이고,

Pod가 K8S API를 호출할 때 필요로 하는 것들은 token, ca.crt, namespace 입니다.

Projected Volume 지원 소스 (5가지)
├── secret
├── configMap
├── serviceAccountToken  ┐
├── downwardAPI          ├── K8S API 호출에 필요한 세트 (3가지)
└── clusterTrustBundle   ┘ (ca.crt는 clusterTrustBundle 또는 configMap으로 전달)

 

뭔가 명확하게 짚고 넘어가기 위해서 전체 흐름을 다시 설명드리겠습니다.

여기서 중요한 포인트는, 사용자가 작업하는 것이 아니라 자동으로 위 행위들이 이루어 집니다.

추가로 kubelet이 토큰 만료시간이 다 되면 자동으로 새 토큰으로 교체합니다 !

 


 

06. Kubelet Authentication/Authorization

그렇다면 kubelet이 뭐하는 친구일까요? 각 노드에 떠있는 에이전트라고 생각하시면 됩니다.

  • API Server로부터 "이 Pod 실행해" 지시 받는 녀석.
  • 실행 결과를 API Server에 보고하는 착한 녀석.
  • 노드 상태 주기적으로 업데이트해요.

즉 kubelet도 API Server에 요청을 보내는 클라이언트 입니다. 그렇다면 kubelet도 인증이 필요하겠죠?

 

📌 kubelet → API Server 인증 (아웃바운드)

kubelet이 API Server에 요청할 때는 클라이언트 인증서 방식을 사용합니다.

/var/lib/kubelet/pki/
├── kubelet-client-current.pem   ← 현재 사용 중인 클라이언트 인증서
└── kubelet.crt                  ← kubelet 서버 인증서

인증서 안에 아래와 같은 내용이 담겨있어요.

CN: system:node:ip-10-0-0-1.ap-northeast-2.compute.internal
O:  system:nodes

이게 위에서 설명드렸던 Node Authorizer가 처리하는 system:node:<nodeName> 형식입니다.

 

📌 API Server → kubelet 인증 (인바운드)

반대로 API Server가 kubelet에 요청을 보낼 때도 있습니다.

kubectl logs   → API Server가 kubelet에 로그 요청
kubectl exec   → API Server가 kubelet에 exec 요청
kubectl port-forward

이때 kubelet은 두 가지 방식으로 인증 / 인가를 처리합니다.

# 인증 2가지
Anonymous     → 인증 없이 접근 허용 (기본값, 위험 ⚠️)
Webhook       → API Server에 "이 요청자 인증됐어?" 물어봄 (권장)

#인가 2가지
AlwaysAllow   → 전부 허용 (위험 ⚠️)
Webhook       → API Server RBAC에 위임 (권장)

하지만 EKS에서는 기본값으로 위에 권장사항이 설정되어 있습니다.

 


긴 글 읽어주셔서 감사합니다.

 

인증, 인가 방식이 너무 복잡하네요 ..!

다음에는 직접 실습을 해보는 시간을 갖도록 하겠습니다.