2026. 4. 12. 23:19ㆍ카테고리 없음
gasida 님이 진행하는 AEWS4 스터디를 기반으로 정리한 내용입니다.
학습할 내용은 크게 2가지로 나뉘는데
- K8S(EKS) Identity and Access Management
- K8S(EKS) Pod(SA) with IAM Role → AWS 리소스 사용
이번 글에서는 K8S(EKS) Identity and Access Management 를 살펴보겠습니다.
K8S(EKS) Identity and Access Management
이번 첫 번째 챕터에서는 Kubernetes 플랫폼, 조금 더 정확히는 Kubernetes API 서버를 사용할 때 인증과 인가를 어떻게 처리하는지 다룹니다. 이 실습 환경에서는 결국 EKS API 서버를 사용할 때 접근을 어떻게 제어하는지 확인하는 내용입니다.

K8S 기본 보안 개념과 참고 링크
이 내용은 이미 익숙하실 수도 있지만, 흐름을 맞추기 위해 한 번 짚고 가겠습니다. 여기 보이는 것이 Kubernetes API 서버입니다.
가운데 퍼즐 모양으로 표시된 부분을 보겠습니다.

대상은 사용자(Human)일 수도 있고, 애플리케이션 파드와 같은 서비스 어카운트일 수도 있습니다. 서비스 어카운트 토큰 등을 가지고 API를 사용할 때는 인증(Authentication), 인가(Authorization), 그리고 어드미션 컨트롤(Admission Control)이라는 세 가지 단계를 거치게 됩니다.
이 세 단계를 모두 무사히 통과하면, 비로소 실제 상태 정보가 etcd에 저장되며 요청된 작업이 반영됩니다.
또한 여기서 중요하게 짚고 넘어갈 점은 사람이 아닌 주체(애플리케이션 등)를 위한 Service Account 개념입니다. 서비스 어카운트 토큰이 발급되면 이를 통해 인증을 통과하게 되고, 이어서 해당 주체가 작업을 수행할 권한이 있는지 확인하는 인가(Authorization) 단계로 넘어갑니다.
이때 가장 보편적으로 사용하는 방식이 바로 RBAC(Role-Based Access Control)입니다. 쿠버네티스는 인증과 인가를 처리하기 위한 다양한 메커니즘을 제공하고 있는데,
이번 실습에서는 EKS API 접근 시 주로 활용되는 Webhook Authentication 방식을 중심으로 살펴볼 예정입니다.
참고 링크
Conecpts → Security
- K8S API 접근 통제(인증, 인가, Admission control, Auditing → ETCD)* Controlling Access to the Kubernetes API - Docs
- 서비스 어카운트(non-human account)* Service Accounts - Docs
- 역할 기반 접근 통제* Role Based Access Control Good Practices - Docs
- 인증 강화 권고 방안(OIDC 등 외부 인증 권장) Hardening Guide - Authentication Mechanisms - Docs
Reference → API Overview
인가를 확인하는 방법도 여러 가지가 있고, EKS는 여기 적은 세 가지를 모두 사용합니다.
Reference → API Access Control
- Authenticating* - Docs
- Authenticating with Bootstrap Tokens - Docs
- 인가를 확인하는 방법도 여러 가지가 있고, EKS는 여기 적은 세 가지를 모두 사용합니다.
Authorization* : AlwaysAllow, AlwaysDeny, ABAC, RBAC, Node, Webhook - Docs - Admission Control in Kubernetes* : adminssion controller(DefaultStorageClass..) ****- Docs
- Managing Service Accounts - Docs
- Projected Volumes : 보호 대상 - secert, downwardAPI, configMap, serviceAccountToken, clusterTrustBundle - Docs
- Certificates and Certificate Signing Requests - Docs
- Kubelet authentication/authorization - Docs
- TLS bootstrapping - Docs
그리고 Admission Control에는 Mutating과 Validating이 있습니다. Mutating은 요청을 수정할 수 있고, Validating은 요청을 검사할 수 있습니다.

계속해서 RBAC 관련 내용을 심도 있게 다룰 예정이므로, 실습의 효율을 높여줄 Krew 플러그인들도 미리 준비해 두겠습니다.
이번 과정에서는 특히 rbac-tool과 rolesum을 자주 활용하게 될 텐데요. 지금은 간단히 설치만 진행해 두고, 구체적인 사용법은 실습을 진행하면서 하나씩 확인해 보겠습니다.
참고로 ARM CPU 기반의 환경에서는 일부 플러그인이 정상적으로 동작하지 않을 수 있습니다. 본인의 환경이 이에 해당한다면 해당 플러그인은 제외하고 설치를 진행하시면 됩니다.
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
kubectl krew install access-matrix rbac-tool rolesum whoami rbac-view
❯ kubectl whoami
arn:aws:iam::003590317363:user/admin
이제부터는 EKS API를 사용하는 과정을 단계별로 살펴보겠습니다.

가운데 API 그림은 Kubernetes API 입니다.
1. K8S Action
사용자가 kubectl get nodes 명령을 입력합니다. 그러면 로컬의 kubectl이 EKS의 Kubernetes API 서버에 노드 정보를 조회하는 요청을 보냅니다.
2. Token 생성
이 요청을 보내기 전에 클라이언트는 AWS 자격 증명을 사용해 bearer token을 생성합니다. 이 토큰은 aws eks get-token 또는 aws-iam-authenticator 경로로 만들어지고, 수명은 15분입니다.
3. Action + Token 전달
kubectl은 API 요청과 함께 이 bearer token을 Kubernetes API 서버에 전달합니다.
4. Id Token 확인
Kubernetes API 서버는 토큰을 직접 검증하지 않고, webhook token authentication 방식으로 외부 인증 시스템에 검증을 요청합니다. 이때 API 서버는 bearer token 검증을 위해 원격 서비스에 질의합니다.
5. sts:GetCallerIdentity
EKS 토큰은 단순 문자열이 아니라, sts:GetCallerIdentity 호출을 위한 pre-signed URL을 담고 있습니다. 이 URL에는 AWS credential과 signature가 포함됩니다.
6. 성공
외부 인증 경로에서 이 요청이 유효하다고 확인되면, 사용자 계정·ARN·UserId 같은 정보가 Kubernetes API 서버로 돌아오고 인증이 완료됩니다.
7. K8S User 확인
그다음 인증된 IAM principal을 Kubernetes username/group과 연결합니다. 그림의 aws-auth ConfigMap은 이 매핑을 설명하는 기존 방식이고, 현재 EKS에서는 Access Entries API 방식이 권장됩니다.
8. K8S Role 확인
이후 Kubernetes는 인가를 수행합니다. 이 단계에서 사용자나 그룹에 연결된 RBAC 권한을 확인해 요청을 허용할지 판단합니다.
9. 허용/차단
RBAC 결과에 따라 요청을 허용하거나 차단합니다. 그리고 kubectl get nodes는 read 요청이므로 Admission Control 단계로 가지 않고, 여기서 바로 결과를 반환합니다.
실습
[1] 클라이언트(aws cli)에서 토큰 생성 : Client side (aws-iam-authenticator token) - Github
토큰을 어떻게 만드는지 확인해보겠습니다. 토큰 발급에 관여하는 AWS 서비스가 STS입니다. Security Token Service의 약자입니다.
AWS CLI 버전 1.16.156 이상에서는 별도 aws-iam-authenticator 설치 없이 aws eks get-token으로 사용 가능합니다.


이 서비스의 장점은 여러 AWS 서비스에서 공통으로 사용할 수 있는 임시 자격 증명 흐름을 제공한다는 점입니다.
여러 AWS 서비스를 사용할 때 서비스마다 별도로 자격 증명을 만들기보다, STS를 통해 임시 보안 자격 증명을 얻고 만료 시간까지 필요한 권한 범위에서 각 서비스를 사용할 수 있습니다.
즉, 클라이언트는 STS를 기준으로 임시 보안 자격 증명 흐름을 사용하고, 서버 쪽에서는 그 값을 검증합니다. 예전에는 aws-iam-authenticator 같은 별도 프로그램이 필요했지만, 지금은 AWS CLI에서 직접 토큰을 확인할 수 있습니다.
먼저 STS 서비스의 기본 동작부터 확인해보겠습니다. get-caller-identity는 현재 호출 주체가 누구인지 확인하는 요청입니다.
> aws sts get-caller-identity --query Arn
"arn:aws:iam::003590317363:user/admin"
user-id, account-id, ARN 정보를 확인할 수 있습니다.
그리고 위는 EKS를 호출할 때 사용하는 자격 증명 파일 ~/.kube/config 에도 들어 있습니다. 즉, 사용자 항목에서 aws eks get-token을 실행하도록 구성되어 있습니다.
> cat ~/.kube/config
...
- name: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args: - --region - ap-northeast-2 - eks - get-token - --cluster-name - myeks - --output - json
command: aws
current-context: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
아래 명령을 실행해, 도움말을 보면 kubectl이 EKS 클러스터에 접근할 때 인증용 토큰을 가져오는 역할이라고 설명합니다.
> aws eks get-token help
...
DESCRIPTION
Get a token for authentication with an Amazon EKS cluster. This can be
used as an alternative to the aws-iam-authenticator.
예전에는 별도 도구를 설치해야 했지만, 지금은 AWS CLI만으로도 이 흐름을 확인할 수 있습니다.
이제 토큰을 수동으로 한 번 발급해보겠습니다. 아래처럼 토큰이 출력되고, 토큰에는 만료 시간이 포함됩니다.
❯ export CLUSTER_NAME=myeks
aws eks get-token --cluster-name $CLUSTER_NAME | jq
{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {},
"status": {
"expirationTimestamp": "2026-04-10T10:32:56Z",
"token": "k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFRQlZQNzRVWjdNNUFFWFVBJTJGMjAyNjA0MTAlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDEwVDEwMTg1NlomWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2lnbmF0dXJlPWEzNjU3NDRiMDk5ZTUyNzE0YmQ1ZTgzZWZhZTIwNmZmYTA0NWZlZGI2YTM1NTZiNTY0NzljZTEwOWIyZjZjZjY"
}
}
여기 보이는 만료 시간까지만 이 토큰을 사용할 수 있습니다.
"2026-04-10T10:32:56Z",
그리고 이 과정을 --debug로 보면 내부 절차도 함께 확인할 수 있습니다.
❯ aws eks get-token --cluster-name $CLUSTER_NAME --debug | jq
2026-04-10 19:19:31,566 - MainThread - awscli.clidriver - DEBUG - CLI version: aws-cli/2.34.11 Python/3.13.12 Darwin/23.6.0 source/x86_64
2026-04-10 19:19:31,566 - MainThread - awscli.clidriver - DEBUG - Arguments entered to CLI: ['eks', 'get-token', '--cluster-name', 'myeks', '--debug']
2026-04-10 19:19:31,596 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <function add_s3 at 0x10c1b22a0>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <function add_ddb at 0x10bebac00>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <bound method BasicCommand.add_command of <class 'awscli.customizations.configure.configure.ConfigureCommand'>>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <function change_name at 0x10be17560>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <function change_name at 0x10be2c9a0>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <function add_history_commands at 0x10c0587c0>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <bound method BasicCommand.add_command of <class 'awscli.customizations.devcommands.CLIDevCommand'>>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <bound method BasicCommand.add_command of <class 'awscli.customizations.login.login.LoginCommand'>>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <bound method BasicCommand.add_command of <class 'awscli.customizations.login.logout.LogoutCommand'>>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <function add_waiters at 0x10c1bfba0>
2026-04-10 19:19:31,597 - MainThread - botocore.hooks - DEBUG - Event building-command-table.main: calling handler <bound method AliasSubCommandInjector.on_building_command_table of <awscli.alias.AliasSubCommandInjector object at 0x10c260590>>
...
host;x-k8s-aws-id
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
2026-04-10 19:19:31,771 - MainThread - botocore.auth - DEBUG - StringToSign:
AWS4-HMAC-SHA256
20260410T101931Z
20260410/ap-northeast-2/sts/aws4_request
89c64ee00dd1feff39e6cf18a80b488b68d9e719d6f16b6132b95e4ef11f505e
2026-04-10 19:19:31,772 - MainThread - botocore.auth - DEBUG - Signature:
d1e6d45616eb8c663b8d9784f7ba911bd0091b80a6dd19c6186f8263a4cb8d47
{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {},
"status": {
"expirationTimestamp": "2026-04-10T10:33:31Z",
"token": "k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFRQlZQNzRVWjdNNUFFWFVBJTJGMjAyNjA0MTAlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDEwVDEwMTkzMVomWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2lnbmF0dXJlPWQxZTZkNDU2MTZlYjhjNjYzYjhkOTc4NGY3YmE5MTFiZDAwOTFiODBhNmRkMTljNjE4NmY4MjYzYTRjYjhkNDc"
}
}
이제 토큰 내용을 조금 나눠서 보겠습니다.
❯ TOKEN_DATA=$(aws eks get-token --cluster-name myeks | jq -r '.status.token')
echo $TOKEN_DATA
k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFRQlZQNzRVWjdNNUFFWFVBJTJGMjAyNjA0MTAlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDEwVDEwMjAyNVomWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2lnbmF0dXJlPTc3ZWE1MjA5MjhkODE2NmY5MjQ1NTBjYWUyY2VhMTIyMzM3ZGQ5Y2FmYWU5NDNiOWU2MTBjNTNhMDU4NTc1NjM
토큰 구조를 확인하기 위해 값을 분리해서 보겠습니다.
❯ IFS='.' read header payload signature <<< "$TOKEN_DATA"
❯ echo "$header"
echo "$signature"
k8s-aws-v1
시그니처 값은 토큰 본문에 그대로 보이지는 않지만, 디버그 출력에서는 Signature 항목으로 확인할 수 있습니다.
2026-04-10 19:19:31,772 - MainThread - botocore.auth - DEBUG - Signature:
d1e6d45616eb8c663b8d9784f7ba911bd0091b80a6dd19c6186f8263a4cb8d47
페이로드 부분만 펼쳐 보면 알고리즘, Credential, 날짜, SignedHeaders, 그리고 서명 관련 정보가 들어 있는 것을 확인할 수 있습니다. 즉, STS 요청을 검증하는 데 필요한 정보가 포함되어 있다고 보면 됩니다. 기본 정보는 이렇게 확인할 수 있습니다.
❯ echo "$payload" | fold -w 4 | sed '$ d' | tr -d '\n' | base64 --decode
https://sts.ap-northeast-2.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAQBVP74UZ7M5AEXUA%2F20260410%2Fap-northeast-2%2Fsts%2Faws4_request&X-Amz-Date=20260410T102025Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=77ea520928d8166f924550cae2cea122337dd9cafae943b9e610c53a058575%
참고) aws eks get-token 명령어가 내부적으로 수행하는 SigV4(Signature Version 4) 서명 및 토큰 변환 과정은 크게 4단계
- 정규 요청(Canonical Request) 생성
- 먼저, 호출할 HTTP 요청의 모든 정보를 일정한 형식으로 정리합니다. 데이터가 조금이라도 바뀌면 서명이 달라지기 때문에 '표준 형식'을 맞추는 것이 중요합니다.
- 대상: STS 서비스의 GetCallerIdentity 액션
- 포함 정보:
- HTTP 메서드 (GET)
- 엔드포인트 (예: sts.us-east-1.amazonaws.com)
- 쿼리 파라미터 (Action=GetCallerIdentity, Version=2011-06-15 등)
- 헤더: 특히 EKS에서는 x-k8s-aws-id (클러스터 이름) 헤더를 필수적으로 포함시켜 서명합니다.
- 이 정보들을 줄바꿈으로 연결한 후 해시(SHA256)하여 **고유한 값(Hashed Canonical Request)**을 만듭니다.
- 서명할 문자열(String to Sign) 생성
- 알고리즘: AWS4-HMAC-SHA256
- 요청 시각: ISO8601 형식의 타임스탬프 (예: 20240325T120000Z)
- Credential Scope: 날짜/리전/서비스/aws4_request (예: 20240325/us-east-1/sts/aws4_request)
- 이 값들과 1단계의 해시값을 합쳐 하나의 문자열로 만듭니다.
- 서명 키(Signing Key) 유도 및 서명
- HMAC-SHA256("AWS4" + **SecretKey**, 날짜) = DateKey
- HMAC-SHA256(DateKey, 리전) = RegionKey
- HMAC-SHA256(RegionKey, 서비스) = ServiceKey
- HMAC-SHA256(ServiceKey, "aws4_request") = Signing Key
- Pre-signed URL 생성 및 토큰 변환
- URL 구성: https://sts.amazonaws.com/?Action=GetCallerIdentity&X-Amz-Signature=...&X-Amz-Credential=...&... 형태의 URL이 만들어집니다. 이를 Pre-signed URL이라고 합니다. 이 URL은 그 자체로 "인증이 완료된 요청서"입니다.
- Base64 인코딩: 이 긴 URL 주소를 네트워크로 주고받기 편하게 Base64 방식으로 인코딩합니다.
- 접두사 추가: 인코딩된 문자열 앞에 k8s-aws-v1.이라는 식별자를 붙입니다.
- 최종 결과물 예시
- k8s-aws-v1.aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8/QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlgtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQS4uLi4=
- 왜 이러나요?
- EKS 클러스터(Kubernetes API 서버)는 여러분의 AWS Secret Key를 모릅니다. 하지만 여러분이 보낸 이 토큰(Pre-signed URL)을 받아서 AWS STS 서버에 대신 던져볼 수 있습니다.
- STS가 "오, 이거 유효한 서명이야. 이 사람은 IAM 사용자 'Alice'가 맞아"라고 응답하면, EKS는 그 응답을 보고 "좋아, Alice는 우리 클러스터 관리자니까 접속 허용!"이라고 판단하는 구조입니다.
참고) 서명 키(Signing Key)를 생성하는 과정 : SigV4 보안의 핵심 - Docs

여기서는 개인의 Secret Access Key와 날짜를 기준으로 SHA-256 기반 HMAC 계산을 반복합니다. 날짜 키, 리전 키, 서비스 키를 차례로 만들고, 최종적으로 나온 결과를 기준으로 STS 요청에 필요한 서명을 구성합니다. 중요한 점은 토큰 생성 자체가 Client-side에서 일어난다는 것입니다. 즉, 토큰을 만드는 행위는 로컬에서 시작되고, AWS CLI만 있으면 이 절차를 재현해 볼 수 있습니다.
핵심은 Secret Access Key 자체를 외부에 노출하지 않고, 일회성 증명값을 만든다는 점입니다. Secret Access Key를 그대로 보내면 안 되기 때문에 이런 방식의 서명 절차를 사용하는 것입니다.
그래서 이런 네 가지의 단계를 사용을 합니다.
- 서명 키(Signing Key) 유도 4단계 (Key Derivation) : 모든 단계에는 HMAC-SHA256 알고리즘이 사용
- 단계 1: 날짜 키 (DateKey) 생성 가장 먼저 시크릿 키를 사용하여 해당 날짜에만 유효한 키를 만듭니다.
- Key: AWS4 + Your_Secret_Access_Key
- Data: 20260401 (현재 날짜)
- 결과: 해당 날짜에만 유효한 해시값
- 단계 2: 리전 키 (RegionKey) 생성 날짜 키를 재료로 삼아 특정 지역(예: us-east-1)을 결합합니다.
- Key: DateKey (1단계 결과)
- Data: us-east-1
- 결과: 해당 날짜 + 해당 리전에서만 유효한 해시값
- 단계 3: 서비스 키 (ServiceKey) 생성 리전 키를 재료로 특정 서비스(예: sts)를 결합합니다.
- Key: RegionKey (2단계 결과)
- Data: sts
- 결과: 해당 날짜 + 리전 + 서비스에서만 유효한 해시값
- 단계 4: 최종 서명 키 (Signing Key) 생성 마지막으로 이 모든 것이 'AWS4 요청'임을 확정 짓습니다.
- Key: ServiceKey (3단계 결과)
- Data: aws4_request
- 결과: 최종 서명 키(Derived Signing Key)
- 단계 1: 날짜 키 (DateKey) 생성 가장 먼저 시크릿 키를 사용하여 해당 날짜에만 유효한 키를 만듭니다.
그러면 AWS는 이 값을 어떻게 검증할까요? AWS도 같은 기준으로 서명 결과를 재현해서 비교할 수 있어야 합니다.
심화 참고) AWS는 (고객들의) Secret Access Key를 서명 검증을 위해 동일한 결과를 재현할 수 있는 안전한 형태로 보관
일반적인 비밀번호 vs AWS 시크릿 키
보통 일반적인 웹사이트는 보안을 위해 비밀번호를 단방향 해시(One-way Hash) 처리하여 저장합니다. 즉, 서버도 사용자의 실제 비밀번호가 무엇인지 모르는 게 정상입니다. 하지만 AWS IAM 시크릿 키는 성격이 다릅니다
- 용도: 사람이 로그인할 때 쓰는 것이 아니라, 기계(CLI, SDK)가 수학적 서명을 생성할 때 사용합니다.
- 검증 방식: 앞서 설명드린 SigV4 과정에서 보셨듯이, 서버(AWS)가 클라이언트와 동일한 서명 키를 생성해낼 수 있어야 합니다. 그러려면 **서버는 원본 시크릿 키(또는 그에 준하는 값)**를 알고 있어야만 합니다.
AWS는 이를 어떻게 안전하게 관리할까?
- AWS가 여러분의 키를 가지고 있다고 해서, AWS 직원이 메모장에 적어두고 보는 것은 절대 아닙니다.
- HSM(Hardware Security Module) 보관: AWS는 IAM 자격 증명을 매우 강력하게 암호화된 특수 데이터베이스에 저장합니다.
- 검증 시에만 사용: 여러분이 보낸 서명값을 검증하는 순간에만 시스템 내부적으로 키를 꺼내어 계산을 수행하고, 다시 암호화 상태로 둡니다.
- 권한 분리: AWS 내부 직원이라 할지라도 고객의 Plain-text(평문) 시크릿 키를 조회하는 것은 기술적, 정책적으로 엄격히 차단되어 있습니다.
AWS Signature V4 검증 과정
- 클라이언트 측
- Secret Key → HMAC → Signature 생성
- (STS / aws-iam-authenticator Case) Secret Key로 presigned STS URL 생성
- AWS 서버 측
- (저장된 Secret Key 기반) → 동일한 HMAC 계산 → Signature 비교
- (STS / aws-iam-authenticator Case) Secret Key 기반으로 서명 검증, STS 호출 유효성 판단
- 즉, 같은 입력 → 같은 출력
왜 해시만 저장하면 안되나?
- 비밀번호처럼 hash(secret) 이렇게 저장 안됨..
- Signature V4는 HMAC(**secret**, data) 즉, secret 자체가 필요
정리하면, 지금 로컬에서 토큰을 만드는 행위는 Client-side에서 일어납니다.
aws eks get-token --cluster-name myeks
AWS IAM Authenticator설명을 봐도, Client-side에서 토큰을 만들고 그 토큰을 AWS STS 쪽 검증 흐름으로 보내 서명을 비교하는 구조입니다. 이 과정을 통해 신원이 확인되고 인증이 성공합니다.

[2] 클라이언트(kubectl)가 K8S(EKS) API 서버(Endpoint)에 Action 요청 : 일반적인 k8s api 요청 호출 + Bearer Token 포함

이제 생성된 토큰을 Bearer Token에 담아 실제 리소스 요청을 보내는 과정을 살펴보겠습니다. 예를 들어 kubectl get nodes 명령을 실행하면, 내부적으로는 토큰 내부에 포함된 Presigned URL을 활용해 인증 과정이 진행됩니다.
이 과정에서 kubectl은 내부의 client-go 라이브러리에 포함된 Credential Plugin을 사용합니다. 별도의 도구 설치 없이도 이 라이브러리 덕분에 Presigned URL을 Bearer Token으로 안전하게 전달할 수 있는 것이죠.
실제로 어떤 데이터가 오가는지 확인하기 위해 kubectl의 로그 레벨(Debug 수준)을 높여서 실행해 보겠습니다.
로그 상단의 Request Body 부분을 보면, kubectl이 내부적으로 생성한 curl 커맨드 형태의 요청을 확인할 수 있습니다.
kubectl get node -v=10
...
I0401 15:48:58.414931 87026 helper.go:113] "Request Body" body=""
## kubectl은 보안 민감 정보(Authorization 헤더 등)를 일반적인 로그 레벨에서 마스킹(Masking) 처리하여 출력하지 않도록 설계되어 있습니다.
## curl 명령어를 흉내 내어 출력할 때 Authorization 헤더는 고의적으로 제외
I0401 15:48:58.414961 87026 round_trippers.go:527] "Request" curlCommand=<
curl -v -XGET -H "Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json"
한 가지 흥미로운 점은, kubectl은 보안을 위해 Authorization 헤더와 같은 민감 정보를 로그에서 자동으로 마스킹(Masking) 처리한다는 것입니다. 따라서 일반적인 로그 레벨에서는 실제 토큰 값이 보이지 않습니다.
이를 직접 확인해 보기 위해, 앞서 수동으로 발급했던 토큰을 이용해 직접 curl 요청을 구성해 볼 수 있습니다
TOKEN_DATA=$(aws eks get-token --cluster-name myeks | jq -r '.status.token')
curl -k -v -XGET
-H "Authorization: Bearer $TOKEN_DATA"
-H "Accept: application/json"
'https://0EA4A3B074D74BA56D4313FB2813E142.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500'
curl -k -s -XGET
-H "Authorization: Bearer $TOKEN_DATA"
-H "Accept: application/json"
'https://0EA4A3B074D74BA56D4313FB2813E142.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500' | jq
결과
# 출력 결과 예시
User-Agent: curl/8.7.1
Authorization: Bearer k8s-aws-v1.aHR0cHM6Ly9zdHMu...
Accept: application/json
[3] k8s(eks) api 는 ‘웹 토큰 인증’ 방식을 통한 인증을 위해서, Token Review 요청 ⇒ AWS 인증 확인 후 응답 시 : 유저, K8S Subject(group) 등

사용자가 Bearer Token을 담아 요청을 보내면, EKS API 서버는 이를 받아 본인이 직접 검증하는 대신 외부 시스템에 확인을 요청합니다. 앞서 말씀드린 것처럼 EKS는 자체적인 유저 DB를 들고 있지 않기 때문입니다.
이때 사용되는 방식이 바로 쿠버네티스의 Webhook Token Authentication입니다.
- 요청 수신: EKS API 서버가 Authorization: Bearer <TOKEN> 헤더가 포함된 요청을 받습니다.
- 인증 위임: API 서버는 설정된 Webhook을 통해 이 토큰을 AWS의 인증 서비스(IAM)로 전달합니다.
- 검증 및 응답: AWS IAM은 전달받은 토큰(STS Presigned URL)을 확인하여 해당 사용자의 신원(ARN)을 파악하고, 그 결과를 다시 API 서버에 알려줍니다.
K8S Available authentication methods include - Docs
- X.509 client certificates
- Bootstrap tokens
- Service account tokens
- Static token file
- External integrations
실제로 쿠버네티스 API 리소스를 확인해 보면 authentication.k8s.io 그룹 안에 TokenReview가 정의되어 있는 것을 볼 수 있습니다.
# TokenReview 리소스 확인
kubectl api-resources | grep authentication
# 결과: tokenreviews authentication.k8s.io/v1 false TokenReview
또, kubectl explain tokenreviews 명령으로 상세 설명을 확인해 보면, "알려진 사용자(Known User)에 대해 토큰 인증을 시도한다"라고 명시되어 있습니다. 또한, 성능 향상을 위해 API 서버 측에서 이 요청 결과를 캐싱할 수도 있다고 합니다.
> kubectl explain tokenreviews
...
DESCRIPTION: TokenReview attempts to authenticate a token to a known user.
Note: TokenReview requests may be cached by the webhook token authenticator plugin
in the kube-apiserver.
이제 직접 토큰을 담은 매니페스트 파일을 작성하여 인증을 요청해 보겠습니다. (토큰은 15분마다 만료되므로 실습 직전에 새로 발급받는 것이 좋습니다.)
# 직접 TokenReview 요청 해보기!
TOKEN_DATA=$(aws eks get-token --cluster-name myeks | jq -r '.status.token')
cat > token-review.yaml << EOF
apiVersion: authentication.k8s.io/v1
kind: TokenReview
metadata:
name: mytoken
spec:
token: ${TOKEN_DATA}
EOF
cat token-review.yaml
실제로 kubectl create -f token-review.yaml -v=9 명령을 실행해 보면, API 서버로부터 받은 상세한 Response Body를 확인할 수 있습니다. 이 JSON 데이터 안에는 우리의 신원이 어떻게 검증되었는지에 대한 모든 정보가 담겨 있습니다.
"Response Body" body=<
{"kind":"TokenReview","apiVersion":"authentication.k8s.io/v1",
"metadata":{"name":"mytoken","managedFields":[{"manager":"kubectl-create","operation":"Update","apiVersion":"authentication.k8s.io/v1",
"time":"2026-04-10T10:50:59Z","fieldsType":"FieldsV1","fieldsV1":{"f:spec":{"f:token":{}}}}]},
"spec":{"token":"k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFRQlZQNzRVWjdNNUFFWFVBJTJGMjAyNjA0MTAlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDEwVDEwNDczN1omWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2lnbmF0dXJlPWM0MWE2ZGQ0MmEyYTU3Y2U3MGM2MWQwM2Y3ODIzMWIyNTFmNDY0MDQ0NzJhOGQyOTllMDgwODljZTBlZGU1MzA"},"status":{"**authenticated":true**,"user":{"username":"arn:aws:iam::003590317363:user/admin","uid":"aws-iam-authenticator:003590317363:AIDAQBVP74UZ6TZHLBQ4V","groups":["system:authenticated"],"extra":{"accessKeyId":["AKIAQBVP74UZ7M5AEXUA"],"arn":["arn:aws:iam::003590317363:user/admin"],"canonicalArn":["arn:aws:iam::003590317363:user/admin"],"principalId":["AIDAQBVP74UZ6TZHLBQ4V"],"sessionName":[""],"sigs.k8s.io/aws-iam-authenticator/principalId":["AIDAQBVP74UZ6TZHLBQ4V"]}},
"audiences":["https://kubernetes.default.svc"]}}

TokenReview 매니페스트를 실행하면, 해당 요청은 Webhook Token Authentication을 통해 AWS IAM Authenticator 서버로 전달됩니다.
이후 AWS STS 서비스가 GetCallerIdentity 이벤트를 호출하여 토큰의 유효성을 직접 검증합니다. 검증이 완료되면 그 결과가 다시 API 서버로 응답되는데, 이때 AWS 사용자 정보와 매핑된 Kubernetes 주체(Subject) 정보가 함께 전달됩니다. 이 주체 정보는 이후 RBAC이 동작함에 있어 중요합니다.
지금까지 TokenReview를 통해 인증이 처리되는 전체 흐름을 살펴보았습니다.
[4] Server side (aws-iam-authenticator server) 는 AWS STS GetCallerIdentity 호출(제출)을 통해 서명 검증(인증!) 후 사용자 정보 반환
이제 본격적인 인증 단계입니다. 핵심은 원천 인증이 AWS IAM(Identity and Access Management)에서 일어난다는 점입니다.
사용자가 클라이언트 측(Client-side)에서 생성한 토큰을 AWS IAM이 검증하는 구조입니다. 즉, 사용자는 쿠버네티스 API를 호출하고 있지만, 실제 인증은 쿠버네티스가 직접 수행하지 않습니다. 대신 쿠버네티스는 Webhook Token Authentication 플러그인을 통해 외부 인증 시스템인 AWS IAM에 인증 처리를 위임합니다.
이 과정에서 Webhook Token Authentication 서버는 AWS STS의 GetCallerIdentity 이벤트를 호출하여 신원을 확인합니다. STS에서 "해당 토큰을 가진 사용자가 맞다"라고 확인해주면, 비로소 인증이 완료되었다고 판단하는 흐름으로 이해하시면 됩니다.

명령을 실행했으므로 CloudTrail에서도 GetCallerIdentity 이벤트를 확인할 수 있습니다. 제 경우에는 IAM User가 admin이므로 해당 사용자 기준의 이벤트가 보입니다.

이 이벤트를 자세히 보면 사용자 ARN, event source, event name 등이 함께 기록됩니다. 여기서 GetCallerIdentity 호출을 확인할 수 있습니다.
그리고 로그를 보다 보면 AIDA, AKIA 같은 접두사가 보입니다. 이 접두사는 각각 의미가 있습니다. 예를 들어 AKIA는 장기 Access Key이고, ASIA는 임시 세션 키를 의미합니다.
그리고 AIDA는 IAM 사용자 자체를 식별하는 고유 ID입니다. 예를 들어 동일한 이름의 IAM 사용자를 다시 만들어도 AWS는 이 ID로 서로 다른 사용자를 구분합니다.

접두사대상 리소스 (Resource Type)특징 및 설명
| AKIA | IAM Access Key | Permanent. IAM 사용자의 고정된 액세스 키입니다. 직접 삭제하기 전까지 유지됩니다. |
| ASIA | Temporary Access Key | Session. AssumeRole이나 STS를 통해 발급된 임시 키입니다. 만료 시간이 존재합니다. |
| AIDA | IAM User ID | IAM 사용자 자체를 식별하는 고유 ID입니다. (콘솔엔 잘 안 보이지만 API 결과에 포함됨) |
| AROA | IAM Role ID | IAM 역할(Role)을 식별하는 내부 ID입니다. |
| ANPA | IAM Managed Policy | AWS 관리형 또는 고객 관리형 정책의 고유 ID입니다. |
| ANVA | IAM Group ID | IAM 사용자 그룹을 식별하는 ID입니다. |
| ACCA | AWS Account | AWS 계정 자체를 나타낼 때 쓰이는 내부 식별자입니다. |
이 내용을 확인한 뒤에는 authenticator 로그도 함께 볼 수 있습니다. 앞에서 Control Plane 로그 5종을 모두 활성화한 이유가 바로 이 로그를 보기 위해서입니다.
이 로그는 CloudWatch Log Group으로 들어옵니다. 실제 로그를 보면 각 인스턴스에서 발생한 authenticator 관련 항목을 확인할 수 있습니다. 예를 들어 admin 사용자 기준으로 보면 관련 사용자 정보가 보입니다. 그리고 path에 authenticator라고 표시된 Endpoint가 있는데, 이것이 IAM Authenticator 서버 경로와 연결되는 부분입니다.

그리고 STS Endpoint를 호출할 때 authenticator 관련 로그만 따로 보고 싶다면 CloudWatch Logs Insights를 사용하면 편리합니다. 현재 Log Group을 기준으로 쿼리하면 됩니다.
여기서 쿼리를 실행하면 전체 Log Group 안의 여러 로그 스트림 중에서 원하는 패턴에 맞는 로그만 골라서 볼 수 있습니다. 이렇게 출력되는 로그는 EKS 환경을 사용하는 주체가 신원 확인을 위해 STS까지 검증을 거친 기록입니다.

이런 패턴 기반 조회는 Datadog 같은 도구에서도 많이 활용합니다. 필요한 패턴만 모아서 보면 세부 내용을 확인하기 좋습니다.

AWS IAM 주체 → K8S Subject 매칭 + 인가! k8s Authz : 방안1(EKS API - Docs) , 방안2(aws-auth , ConfigMap is deprecated - Docs)
이제 다시 그림으로 돌아가서 정리해보겠습니다. Webhook Token Authenticator가 신원 확인을 요청하고, STS를 통해 서명 검증이 이루어집니다. 즉, 패스워드를 직접 전달하는 구조가 아니라 서명 검증을 통해 신원을 확인하는 구조입니다.
클라이언트가 개인 키 기반으로 만든 서명값을 AWS 측에서도 같은 기준으로 다시 계산해 비교한다고 이해하시면 됩니다.
이 값이 일치하면 신원이 확인됩니다. 그다음에는 RBAC과 연결되는 접근 제어 방식을 봐야 하고, 여기에는 AWS 기준으로 두 가지 방법이 있습니다.

이제 이게 지금 확인(해당 그림밑에 AuthN 부분) 이 됐는데 RBAC을 하기 위한 AWS 방안이 두 개가 있습니다

웹에서, EKS 클러스터 액세스에 들어가보면

Authentication Mode에서는 ConfigMap, API, 그리고 두 방식을 함께 사용하는 모드가 있습니다. 두 방식을 함께 사용할 경우에는 중복되는 사용자 정책 처리 순서도 고려해야 합니다.
이 경우에는 EKS API 방식이 우선합니다. 따라서 현재는 API 방식을 사용하는 것이 자연스럽고, ConfigMap 방식은 deprecated 방향으로 가고 있습니다.
정리하면 기본적으로는 두 가지 축으로 이해하시면 됩니다. 현재 ConfigMap을 사용 중이라면 EKS API 방식으로 마이그레이션하는 것을 권장합니다. 다만 두 방식 모두 인증 단계에서는 TokenReview를 사용합니다.
구분aws-auth ConfigMap 방식Access Entry (EKS API) 방식
| 인증 요청 | TokenReview (동일) | TokenReview (동일) |
| 데이터 저장소 | K8s 내부의 ConfigMap (etcd) | AWS EKS 관리형 API (Internal DB) |
| 관리 주체 | 사용자가 YAML로 직접 관리 | AWS API/콘솔에서 직접 관리 |
| 권한 부여(인가) | RBAC RoleBinding 필요 | Access Policy (EKS 전용 정책) 사용 가능 |
인증이 끝난 뒤에는 권한, 즉 인가가 중요합니다. Access Entry 방식은 접근 관리를 AWS 측 API 기준으로 함께 가져갈 수 있다는 점이 장점입니다.
즉, 인증과 접근 관리를 최대한 한 경로로 관리하기 편한 방식이 API 기반 Access Entry 방식입니다. 클러스터가 여러 개일수록 Kubernetes RBAC과 ConfigMap을 각각 따로 관리하는 것은 부담이 커집니다.
굳이 불편하게 이중 관리할 필요가 없습니다. AWS 쪽에서는 EKS 전용 정책을 계속 확장하고 있고, 기본적인 접근 제어는 EKS API 기준으로 처리할 수 있습니다. 물론 커스텀 RBAC이 필요한 경우는 별도로 구성해야 하지만, 일반적인 경우에는 Access Entry 방식이 더 편합니다. 반면 ConfigMap 방식은 공식 문서에서도 deprecated 방향으로 안내합니다.

따라서 새로 구성할 때는 ConfigMap 방식보다 EKS API 방식을 우선 검토하시면 됩니다. 기존에 ConfigMap 방식을 쓰고 있다면 마이그레이션 가이드를 참고해 EKS API 방식으로 전환하는 것을 권장합니다.
aws-auth ConfigMap 방식을 보겠습니다. 인증 검증이 끝나면, 사용자 주체가 User인지 Role인지에 따라 mapUsers 또는 mapRoles를 확인합니다. 여기서 ARN이 일치하는 항목을 찾고, 매핑된 그룹 정보를 확인합니다. 이 그룹 정보가 Kubernetes Subject와 연결되고, 최종적으로 TokenReview 응답에 반영됩니다.


즉, 이 Subject 정보를 받아 TokenReview 응답으로 돌려줍니다.
API Server 입장에서는 "이 사용자의 신원을 확인해 달라"는 요청을 보냈고, 응답으로 "신원 확인이 끝났고 이 사용자는 어떤 그룹에 속한다"는 정보를 받는 구조입니다. 이후에는 이 그룹 정보를 기준으로 RBAC을 확인합니다. 따라서 핵심은 TokenReview 응답에 어떤 Kubernetes 주체 정보가 들어오느냐입니다.
그리고 이 값이 RBAC과 비교됩니다. 다시 말씀드리면, 지금 설명한 것은 ConfigMap 방식입니다. 반면 EKS API 방식을 사용하면 이 일부 흐름을 AWS 측 API 기준으로 더 일관되게 관리할 수 있습니다. ConfigMap 방식은 결국 사용자가 직접 관리해야 합니다.
운영 관점에서 보면 이런 항목을 직접 만들고 관리해야 합니다. 그리고 ConfigMap 방식이 권장되지 않는 이유도 여기서 나옵니다. 예전에는 특히 이런 실수가 곧바로 장애로 이어지기도 했습니다.
❯ kubectl get cm -n kube-system aws-auth -o yaml
apiVersion: v1
data:
mapRoles: |
- rolearn: arn:aws:iam::003590317363:role/myeks-ng-1
groups:
- system:bootstrappers
- system:nodes
username: system:node:{{EC2PrivateDNSName}}
kind: ConfigMap
metadata:
creationTimestamp: "2026-04-10T09:43:02Z"
name: aws-auth
namespace: kube-system
resourceVersion: "990"
uid: 80fb927e-720e-4836-b28e-63981b2d286e
지금은 이 문제가 그대로 재현되지는 않지만, aws-auth를 보면 mapRoles 안에 노드 관련 항목이 들어 있습니다. 이유는 워커 노드도 Kubernetes API를 호출해 자신을 등록하고, kubelet 통신에 필요한 권한을 사용해야 하기 때문입니다.
노드도 결국 EKS를 사용할 권한이 필요하므로, IAM 주체로 보면 User가 아니라 Role 기준으로 mapRoles에 들어갑니다. 그런데 만약 운영자가 이 내용을 모르고 해당 항목을 지워 버리면 문제가 생길 수 있습니다. 예전에는 이런 실수 하나로 노드가 NotReady로 빠지는 사례도 있었습니다. 왜냐하면 노드가 system:nodes 권한을 더 이상 행사하지 못하기 때문입니다.
즉, ConfigMap 하나를 잘못 수정해도 장애가 날 수 있다는 점이 운영 부담입니다. 이런 불편함 때문에 등장한 방식이 EKS API Cluster Access Entry입니다.
앞에서 본 것처럼 인가에도 Webhook 방식이 있습니다. 즉, 인가 역시 다른 시스템을 통해 권한 확인을 처리할 수 있습니다. EKS에서는 EKS API 방식을 사용하면 인증뿐 아니라 인가 관리도 AWS 쪽 정책과 함께 더 일관되게 가져갈 수 있습니다. 그래서 관리형 정책을 매핑하면 기본적인 경우에는 Kubernetes RBAC RoleBinding을 직접 많이 만들지 않아도 됩니다. 물론 필요한 커스텀 권한은 별도로 구성해야 합니다.
[5] 방안1 EKS API - Docs : AWS IAM 주체 → K8S Subject 매칭 + k8s Authz ‘Webhook’ 인가! - SubjectAccessReview
실제 kube-apiserver 가 어떻게 설정되어 있는지 확인하기 위해 CloudWatch 로그를 살펴보겠습니다. kube-apiserver 로그 그룹으로 이동하면 두 개의 로그 스트림이 있는 것을 확인할 수 있습니다.
하나를 클릭하여 최초 프로비저닝 시점의 로그를 검색해 보면, API 서버 인스턴스가 실행될 때 세팅된 플래그(Flag) 값들이 로그에 찍혀 있는 것을 볼 수 있습니다.

설정을 보면 Token Webhook이 지원되도록 구성 파일에 관련 정보가 포함되어 있습니다.
- 인증 Webhook을 지원한다는 것은, 쿠버네티스가 직접 신원을 확인하는 대신 외부의 다른 인증 시스템을 통해 인증을 대행하겠다는 뜻입니다.
- Authorization (인가) 그 아래에는 Authorization(인가) 모드에 대한 설정도 확인할 수 있습니다. 인가 모드에는 여러 가지 방식이 있으며, 현재 클러스터가 어떤 우선순위로 권한을 검증하는지 해당 플래그를 통해 파악할 수 있습니다.

노드나 RBAC 설정에서 흔히 접하는 RBAC은 우리가 알고 있는 표준 Kubernetes RBAC 방식입니다. 하지만 EKS는 API 서버를 사용할 때 권한 체크를 위한 Webhook 인가(Authorization) 기능도 제공합니다. 즉, EKS API 방식을 사용하면 인증뿐만 아니라 인가 단계까지 AWS 시스템에 위임하여 처리할 수 있다는 개념입니다.
아래 그림을 한번 봅시다.
이러한 인가 위임 방식은 EKS Access Entry 또는 Cluster Access Management라 불리는 기능을 통해 구현되며, 핵심 컴포넌트인 EKS Authorizer가 그 역할을 수행합니다.
- EKS Authorizer = 인가(Authorization)

인증(Authentication)뿐만 아니라 인가(Authorization) 단계에서도 EKS가 주도적인 역할을 할 수 있습니다. 표준 Kubernetes RBAC에만 의존하지 않고, EKS 자체적으로 권한을 확인해주겠다는 개념입니다.
이러한 방식은 Cluster Access Management, EKS API 방식, 혹은 Access Entry라고 불립니다. 기존의 aws-auth ConfigMap 방식과 달리, EKS 컨트롤 플레인이 관리하는 Access Entry DB를 직접 활용하는 것이 특징입니다. 시스템은 이 DB를 조회하여 주체(Subject) 정보를 확인하고, 매핑된 Access Policy를 대조하여 권한이 적절한지 판단합니다. 즉, 정책이 일치하면 EKS 단에서 인증과 인가가 모두 완료되므로 사용자가 별도의 K8s RBAC을 복잡하게 신경 쓸 필요가 없어집니다.
다만, 모든 상황에서 만능인 것은 아닙니다. EKS가 사전에 정의하여 제공하는 정책 범위 내에서는 매우 편리하지만, 클러스터 내부의 아주 세세한 RBAC 설정까지 EKS가 모두 파악하고 제어할 수는 없습니다. 따라서 EKS 제공 정책에 없는 세밀한 권한 설정이 필요한 경우에는 여전히 수동 설정이 병행되어야 합니다.
결론적으로 EKS가 인가 영역까지 지원할 수 있는 이유는 앞서 살펴본 Authorization Webhook 설정이 구현되어 있기 때문입니다.
이 인가 방식이 어떻게 동작하는지 구체적으로 알아보겠습니다.
- 인가 요청 발생: 사용자가 kubectl delete pod 명령을 내리면, API 서버는 "이 유저가 이 작업을 할 권한이 있나?"를 확인해야 합니다.
- 외부 위임 (Webhook Call): API 서버는 SubjectAccessReview라는 요청 객체를 생성하여 EKS의 인가 웹훅 서비스(AWS 관리 영역)로 보냅니다.
- "사용자 A가 B 네임스페이스의 Pod을 삭제하려고 하는데, 허가해줄까?"
- AWS의 판단: EKS 인가 웹훅은 AWS API 내부에 저장된 Access Entry와 연동된 Access Policy 데이터를 즉시 조회합니다.
- "어, 사용자 A는 AmazonEKSClusterAdminPolicy가 걸려 있네? 이건 모든 권한 허용이야."
- 응답 (Decision): 웹훅이 다시 API 서버에 응답을 보냅니다.
- allowed: true (허용함)
- 최종 실행: API 서버는 외부(AWS)에서 "허용" 응답이 왔으므로, 내부 RBAC(RoleBinding)에 해당 유저 정보가 없더라도 명령을 실행합니다.
주의할 점: "하이브리드" 동작 - 만약 AWS 관리형 정책을 쓰지 않고, 직접 만든 커스텀 RBAC을 쓰고 싶다면 어떻게 될까요?
- AWS 웹훅이 먼저 검토하고 "모르는 유저네? 결정 보류(No Opinion)"라고 응답합니다.
- 그러면 API 서버는 다음 단계인 내부 RBAC 엔진으로 넘어가서, 사용자가 직접 작성해둔 RoleBinding이 있는지 찾아봅니다.
- 결국 EKS는 AWS가 관리하는 영역(Managed)과 사용자가 관리하는 영역(Custom RBAC)을 동시에 지원하기 위해 이 웹훅 구조를 사용함.
실제 콘솔의 IAM 액세스(IAM Access) 항목을 보면 이 구조를 명확히 확인할 수 있습니다. 이것이 바로 ConfigMap 방식이 아닌 EKS API 방식(Cluster Access Management)입니다.
현재 설정된 내용을 보면 Admin 계정의 보안 주체 ARN에 AmazonEKSClusterAdminPolicy 정책이 연결되어 있으며, 이를 통해 클러스터 전체를 관리할 수 있는 최고 권한이 부여되어 있는 상태입니다.

현재 관리 콘솔의 액세스 항목에서 확인할 수 있는 주체들은 각각의 역할에 따라 다음과 같이 인증·인가가 처리됩니다.
- admin: 이 주체는 EKS API를 사용할 때 Access 정책에 의해 인증과 인가 과정이 즉시 완료됩니다. 별도의 K8s Role 설정 없이 EKS API를 통해 직접 권한을 행사하는 구조입니다.
- myeks-ng-1: 노드는 별도의 K8s Subject를 가집니다. 이 Subject에 매핑된 권한이 쿠버네티스 측에서 확인되어야 하며, 그 범위 내에서 노드로서의 권한을 행사합니다.
- AmazonEKS: AWS가 클러스터의 메트릭 등을 수집하고 관리하기 위해 사용하는 주체입니다. 관리 용도에 필요한 최소한의 권한들이 묶여 있으며, 사용자가 별도로 관리할 필요는 없습니다.
한 가지 특이점은 EKS를 설치한 IAM 주체의 정보입니다. 원래는 system:masters 그룹에 속해야 하지만, 실제 출력 결과에서는 그룹 이름이 나타나지 않습니다. 이는 AWS가 인증과 인가를 직접 관리하는 과정에서 보안이나 운영상 실수(삭제 등)를 방지하기 위해 의도적으로 정보를 노출하지 않기 때문입니다.
이러한 액세스 정보는 아래의 AWS CLI 명령어를 통해 직접 조회하여 확인할 수 있습니다.
❯ aws eks list-access-entries --cluster-name myeks | jq
{
"accessEntries": [
"arn:aws:iam::003590317363:role/aws-service-role/eks.amazonaws.com/AWSServiceRoleForAmazonEKS",
"arn:aws:iam::003590317363:role/myeks-ng-1",
"arn:aws:iam::003590317363:user/admin"
]
}
인가를 Webhook 방식으로 처리할 때 사용하는 핵심 리소스가 바로 SubjectAccessReview입니다. 이름 그대로 "특정 주체(Subject)의 액세스(Access) 요청에 대해 검토(Review)를 부탁한다"는 의미입니다.
쿠버네티스 API 리소스를 확인해 보면 authorization.k8s.io 그룹 내에 관련 리소스들이 정의되어 있는 것을 볼 수 있습니다.
❯ kubectl api-resources | grep subject
selfsubjectreviews authentication.k8s.io/v1 false SelfSubjectReview
localsubjectaccessreviews authorization.k8s.io/v1 true LocalSubjectAccessReview
selfsubjectaccessreviews authorization.k8s.io/v1 false SelfSubjectAccessReview
selfsubjectrulesreviews authorization.k8s.io/v1 false SelfSubjectRulesReview
subjectaccessreviews authorization.k8s.io/v1 false SubjectAccessReview.
kubectl explain subjectaccessreviews를 통해 상세 설명을 확인해 보면, 특정 유저나 그룹이 특정 액션(노드 조회, 파드 생성 등)을 수행할 수 있는지 여부를 체크하는 용도임을 알 수 있습니다. 즉, 직접 권한을 행사하기 전에 "내가 이 행동을 할 수 있는지"를 대신 물어보는 역할을 합니다.
❯ kubectl explain subjectaccessreviews
GROUP: authorization.k8s.io
KIND: SubjectAccessReview
VERSION: v1
DESCRIPTION:
SubjectAccessReview checks whether or not a user or group can perform an
authentication.k8s.io
이러한 인가 흐름을 시각적으로 더 편하게 확인하기 위해 Krew 플러그인인 rbac-tool을 활용할 수 있습니다. whoami 명령을 실행하면 현재 사용 중인 자격 증명에 대한 리뷰 응답을 간결하게 보여줍니다.
❯ kubectl rbac-tool whoami
{Username: "arn:aws:iam::003590317363:user/admin",
UID: "aws-iam-authenticator:003590317363:AIDAQBVP74UZ6TZHLBQ4V",
Groups: ["system:authenticated"],
Extra: {accessKeyId: ["AKIAQBVP74UZ7M5AEXUA"],
arn: ["arn:aws:iam::003590317363:user/admin"],
canonicalArn: ["arn:aws:iam::003590317363:user/admin"],
principalId: ["AIDAQBVP74UZ6TZHLBQ4V"],
sessionName: [""],
sigs.k8s.io/aws-iam-authenticator/principalId: ["AIDAQBVP74UZ6TZHLBQ4V"]}}
실제로 SubjectAccessReview를 통해 인가 요청을 시도해 보겠습니다.
지금 저의 ARN은 이제 system:masters 그룹에 들어가 있죠. 내가 하려는 행위는 kube-system 네임스페이스의 파드를 조회하려고 해요. 나는 이 그룹에 있어요. 그리고 이 유저로 해서 이게 내가 권한이 가능한지 체크해줘 라고 리뷰 요청을 하는 겁니다.
> ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
> cat > sar-request.yaml << EOF
apiVersion: authorization.k8s.io/v1
kind: SubjectAccessReview
spec:
user: "arn:aws:iam::${ACCOUNT_ID}:user/admin"
groups:
- system:masters
resourceAttributes:
namespace: "kube-system"
verb: "get"
resource: "pods"
EOF
# 요청 실행 및 디버그 로그 확인
> kubectl create -f sar-request.yaml -v=8
이 요청에 대해 응답이 오는 것 중 가장 핵심은 allowed: true 아니면 **false**입니다. "너 이 권한 행사 가능해? 아니면 불가능해?"를 결정하는 부분입니다. 결과를 보면 allowed: true로 성공 응답이 나왔습니다. 스펙에 "나 이거 하고 싶고 이 그룹에 포함된 애야"라고 요청했을 때, Webhook을 통해서 인가 처리가 정상적으로 통과된 것을 확인할 수 있습니다.
{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SubjectAccessReview",
"metadata": {},
"spec": {
"groups": [
"system:masters"
],
"resourceAttributes": {
"namespace": "kube-system",
"resource": "pods",
"verb": "get"
},
"user": "arn:aws:iam::003590317363:user/admin"
},
"status": {
"allowed": true
}
}
EKS에서 제공하는 EKA API Access Entry 관리형 정책 확인 - Docs
EKS API 방식을 쓸 때 관리형 정책은 그냥 갖다 쓰면 된다고 했습니다. 지금 내 주체인 admin IAM 계정이 어떤 정책을 매핑해서 쓰고 있는지 CLI로 확인해 보겠습니다.
> export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
> aws eks list-associated-access-policies --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/admin | jq
{
"associatedAccessPolicies": [
{
"policyArn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
"accessScope": {
"type": "cluster",
"namespaces": []
},
"associatedAt": "2026-04-10T18:41:45.928000+09:00",
"modifiedAt": "2026-04-10T18:41:45.928000+09:00"
}
],
"clusterName": "myeks",
"principalArn": "arn:aws:iam::003590317363:user/admin"
}
조회해보면 제 계정은 AmazonEKSClusterAdminPolicy라는 최고 관리 정책을 매핑해서 쓰고 있습니다.
전체 관리형 정책들을 쭉 보면 여러 가지가 있습니다. AmazonEKSClusterAdminPolicy는 최고 관리 권한이고, 이외에도 컴퓨팅 관리, Viewer(읽기 전용), 혹은 AI옵스나 로드 밸런서 컨트롤러 같은 특정 역할에 필요한 정책들을 상황에 맞춰 매핑해서 쓰시면 됩니다.
해당 명령어 - aws eks list-access-policies --output table
그래서 지금 관리형 정책 같은 경우에는 공식 문서에 보면 잘 나와 있으니 참고
https://docs.aws.amazon.com/eks/latest/userguide/access-policy-permissions.html
Review access policy permissions - Amazon EKS
services.k8s.aws, acm.services.k8s.aws, acmpca.services.k8s.aws, apigateway.services.k8s.aws, apigatewayv2.services.k8s.aws, applicationautoscaling.services.k8s.aws, athena.services.k8s.aws, bedrock.services.k8s.aws, bedrockagent.services.k8s.aws, bedrocka
docs.aws.amazon.com
[6] 방안1 EKS API 을 통한 인증/인가를 마친 후, K8S(EKS) API는 K8S Action 실행 후 클라이언트에 최종 응답!
확인 결과 "맞다, 명령 실행해도 된다"라고 응답이 오면, 그때서야 요청했던 액션(예: 파드 리스트 조회)을 실행해서 클라이언트에 리턴을 해줍니다.
여기서 system:masters 그룹은 아주 특이한 녀석입니다. 얘는 아예 인가 프로세스를 타질 않아요. 인증만 끝나면 인가 단계를 우회해서 바로 슈퍼유저 권한을 가져버립니다. 일반적인 쿠버네티스 환경에서 이 그룹에 속한 친구들의 가장 큰 특징이죠.
system:masters 그룹 사용자 특징: 인증 완료 후 → 인가(Authorization) 단계를 우회. 즉, 인증 즉시 슈퍼유저 권한 획득. Docs
그래서 보안상 이 그룹을 직접 쓰기보다는, 슈퍼유저 권한이 필요하더라도 별도의 ClusterRole을 만들어서 쓰라는 게 일반적인 쿠버네티스의 권고 사항이기도 합니다.
그리고 아까 토큰 만드는 거 관련해서, 로컬에서 기본 만료 시간이 보통 15분 정도 됩니다. "매번 15분마다 내가 직접 만들어야 하나?" 싶겠지만, kubectl을 쓰면 지가 알아서 자동으로 갱신해주니까 사용하기엔 아주 편리하죠.
참고로, 이 15분이라는 시간조차도 더 캐싱해서 응답 속도를 끌어올리는 아이디어를 테스트해보시는 분들도 있더라고요. 성능 최적화에 관심 있다면 이런 캐싱 구조도 한 번 조사해보자.
EC2에서 Node IAM Role(EC2 Instance Profile)을 사용(Assume)하여 K8S(EKS) API를 호출 시 과정
이제 Node IAM Role에 대해 살펴보겠습니다. 위에서 다룬 내용은 우리가 가진 장기 키(Access Key/Secret Key)로 서명해서 토큰을 만드는 과정이었죠.
그런데 주체가 사람이 아니라 애플리케이션, 대표적으로 EC2 Instance Profile인 경우는 상황이 좀 다릅니다. EC2는 장기 키를 직접 들고 있지 않아요. 대신 IAM Role을 수임(Assume)해서 임시 키를 발급받고, 그 키를 기반으로 토큰을 만들어 호출하는 과정을 거칩니다.
여기서 역할을 수임한다는 말은 위임 계약에 따라 상대방의 업무나 권한을 대신 맡아 처리한다는 뜻입니다.

사람이 직접 IAM Role을 Assume 한다는 건, 평상시에는 권한이 없다가 필요할 때만 특정 역할을 빌려 써서 해당 권한을 부여받는 방식입니다. 그 권한으로 업무를 보고 시간이 지나면 자연스럽게 만료되죠.
STS AssumeRole을 통해 받는 임시 키 세트에는 반드시 만료 시간이 포함되어 있습니다.
예제 실습해봅시다.
# user2 사용자에 인라인 정책을 추가 : STS 서비스의 AssumeRole Action 허용
cat <<EOF > allow-assume-role.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "allowassumerole",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "*"
}
]
}
EOF
cat allow-assume-role.json | jq
aws iam put-user-policy --user-name user2 --policy-name allow-assume-role --policy-document file://allow-assume-role.json
# AmazonS3FullAccess 권한을 가진 IAM Role 생성
> export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
cat <<EOF > MyAccount-AssumeRole.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"AWS": "$ACCOUNT_ID"
},
"Condition": {}
}
]
}
EOF
> cat MyAccount-AssumeRole.json | jq
> aws iam create-role --role-name assume-role-s3full --assume-role-policy-document file://MyAccount-AssumeRole.json --max-session-duration 7200
{
"Role": {
"Path": "/",
"RoleName": "assume-role-s3full",
"RoleId": "AROA5ILF2FJITESCKZOAO",
"Arn": "arn:aws:iam::911283464785:role/assume-role-s3full",
"CreateDate": "2023-09-03T06:46:47+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"AWS": "911283464785"
},
"Condition": {}
}
]
}
}
}
# assume-role-s3full IAM Role 에 AmazonS3FullAccess 정책 적용
> aws iam attach-role-policy --role-name assume-role-s3full --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
# AssumeRole 을 통해 임시자격증명 발급
> export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text --profile user2)
> aws sts assume-role --role-arn arn:aws:iam::"$ACCOUNT_ID":role/assume-role-s3full --role-session-name MyS3accessSession --profile user2
{
"Credentials": {
"AccessKeyId": "ASIA5ILF2FJI4JPGYCJ4",
"SecretAccessKey": "VxLRnDNrgDFcsAWd+Kruqtdjr75Ej/OioApIIRHO",
"SessionToken": "IQoJb3JpZ2luX2VjEE8aDmFwLW5vcnRoZWFzdC0yIkcwRQIgZMrgq7zxDCTVY39s+COI/DhesFgol/XbmbocHkcBKqACIQD6JfI/2nru3xmOrrqF+iw7Fm9dIAK5hpnkCNRIzGt95CqeAggoEAIaDDkxMTI4MzQ2NDc4NSIMTSm0mTnWwl9tj2IVKvsBr4qP7ZZNg0YxcpFFmsECtxi9WTAQH6a3uyt6Lb5KTpN93gZgnr0LdqFl2ZTj6TBZ7Wdrj47uao0ZalxFK8ltGuBEQLUbNHfF432/GhYnLnmSt2GNwGLcEcrEAL6ILE8l9CsUFv4VKcEcE3OJ8f8K5r3xrR1Zp5dy8Vj2KCf92LS9MDP5YqXJUvYQaBloiIrKeBiov9ykwclJ3y+D0+g9p4DklE8kLNjcj28PopNAp1VM+Rilf/lpOTjWWi0VM1GQO+qEdivSBPWUtJlvq15mlA89DsPEDPM/2K4hI3lJUI59djTYJCfGAJj55srqA52sXPcTC6HWh9I60+AwodnQpwY6nQEk/pdlajJGV7SKkAzWAWylivc2POARHYa3VYoDlK8g7tvghxVSjOVOcwGsfYvPihxFTjuw1k/tYWepg+6sgE8BvaxbWbMj0QZPBqcv6S4sgShXq0P6njDU02y+ZtD0H8ZfS/eapd0lUfEbbXxzIbhmjGAwIM9ihsTD+EhaVTskaYnA//pFq8pem06MoXFWy69hYlLJswZYFOmOXlsh",
"Expiration": "2023-09-03T07:50:09+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "AROA5ILF2FJITESCKZOAO:MyS3accessSession",
"Arn": "arn:aws:sts::911283464785:assumed-role/assume-role-s3full/MyS3accessSession"
}
}
# 위에서 출력된 AccessKeyId , SecretAccessKey , SessionToken 으로 임시자격증명 적용
> export AWS_ACCESS_KEY_ID="ASIA5ILF2FJI4JPGYCJ4"
> export AWS_SECRET_ACCESS_KEY="VxLRnDNrgDFcsAWd+Kruqtdjr75Ej/OioApIIRHO"
> export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjEE8aDmFwLW5vcnRoZWFzdC0yIkcwRQIgZMrgq7zxDCTVY39s+COI/DhesFgol/XbmbocHkcBKqACIQD6JfI/2nru3xmOrrqF+iw7Fm9dIAK5hpnkCNRIzGt95CqeAggoEAIaDDkxMTI4MzQ2NDc4NSIMTSm0mTnWwl9tj2IVKvsBr4qP7ZZNg0YxcpFFmsECtxi9WTAQH6a3uyt6Lb5KTpN93gZgnr0LdqFl2ZTj6TBZ7Wdrj47uao0ZalxFK8ltGuBEQLUbNHfF432/GhYnLnmSt2GNwGLcEcrEAL6ILE8l9CsUFv4VKcEcE3OJ8f8K5r3xrR1Zp5dy8Vj2KCf92LS9MDP5YqXJUvYQaBloiIrKeBiov9ykwclJ3y+D0+g9p4DklE8kLNjcj28PopNAp1VM+Rilf/lpOTjWWi0VM1GQO+qEdivSBPWUtJlvq15mlA89DsPEDPM/2K4hI3lJUI59djTYJCfGAJj55srqA52sXPcTC6HWh9I60+AwodnQpwY6nQEk/pdlajJGV7SKkAzWAWylivc2POARHYa3VYoDlK8g7tvghxVSjOVOcwGsfYvPihxFTjuw1k/tYWepg+6sgE8BvaxbWbMj0QZPBqcv6S4sgShXq0P6njDU02y+ZtD0H8ZfS/eapd0lUfEbbXxzIbhmjGAwIM9ihsTD+EhaVTskaYnA//pFq8pem06MoXFWy69hYlLJswZYFOmOXlsh"
# caller id 확인
> aws sts get-caller-identity | jq
# S3 버킷 생성
# NICKNAME=<각자 자신의 닉네임>
> NICKNAME=somang
#aws s3 mb s3://버킷(유일한 이름) --region ap-northeast-2
> aws s3 mb s3://ahss-2w-$NICKNAME-2 --region ap-northeast-2
# S3 버킷 조회
> aws s3 ls
# S3 버킷 삭제
> aws s3 rb s3://ahss-2w-$NICKNAME
> aws s3 rb s3://ahss-2w-$NICKNAME-2
# (참고) 임시자격증명 제거
> unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
임시 키나 토큰에는 항상 만료 시간이 들어갑니다. 그냥 습관적으로 '토큰 = 만료 기간이 있다'라고 이해하시면 됩니다.
보통 AccessKeyId, SecretAccessKey 두 개만 쓰는 걸 장기 키라고 하는데, 이걸 애플리케이션에 하드코딩하는 건 절대 하시면 안 되는 방식입니다. 그래서 이런 식으로 AssumeRole을 통해 임시 자격 증명을 요청하는 거죠.
이렇게 요청하면 정해진 기간 동안만 쓸 수 있는 Access Key, Secret Key, Session Token 세트를 줍니다. 이걸 입력해서 권한을 행사하는 상태를 'Assume 한다' 또는 한글로 **'역할을 수임한다'**라고 표현합니다. 내가 평소엔 권한이 없다가, 이 명령을 실행할 때만 그 직책을 맡아서 처리한다는 뜻이에요.
이제 노드에 있는 IAM Role을 가지고 이 내용을 좀 더 자세히 알아볼게요. 액세스 항목에서 보셨던 이 부분입니다.
[0] Access Entry 정보 확인

이 항목을 보시면 유형이 Standard가 아니라 EC2_LINUX로 되어 있습니다. 사용자 이름도 system:node:{{EC2PrivateDNSName}} 같은 이상한 DNS 이름이 들어가 있는데, 이걸 좀 이해해볼 필요가 있습니다.
조회된 내용을 다시 보면, 이 주체는 인증과 인가를 거쳐 **system:nodes**라는 Kubernetes 그룹에 매핑됩니다.
> aws eks describe-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID\:role/myeks-ng-1 | jq # macOS
{
"accessEntry": {
"clusterName": "myeks",
"principalArn": "arn:aws:iam::003590317363:role/myeks-ng-1",
"kubernetesGroups": [
"system:nodes"
],
"username": "system:node:{{EC2PrivateDNSName}}",
"type": "EC2_LINUX"
}
}
워커 노드의 EC2도 IAM Role을 가지고 있기 때문에 EKS API를 호출할 수 있습니다.
과정은 사람과 마찬가지예요. EC2도 자신만의 토큰 서명을 만들어야 하는데, 여기서 중요한 차이점은 Session Token이 추가된다는 점입니다. 아까 봤던 장기 키 기반 그림에는 이게 없었죠.

그냥 시크릿 키만 가지고 하는 게 아니라, 세션 토큰까지 추가해서 쓰는 방식입니다.
일단 1번 노드에 접속해서 이 사용자가 AWS API를 호출할 수 있는지 확인해 보면, 서버 자체에서 바로 사용이 가능한 걸 볼 수 있습니다. sts get-caller-identity를 쳐보면 Assumed-Role이라고 출력되죠.
[root@ip-192-168-23-192 ~]# aws sts get-caller-identity --query Arn
"arn:aws:sts::003590317363:assumed-role/myeks-ng-1/i-01e44c1379c4000ac"
여기서 Assumed라는 표현이 핵심인데, 이건 EC2 인스턴스가 AWS 서비스를 사용하기 위해 임시 기간 동안 역할을 수임(Assume)했다는 뜻입니다. 출력 내용을 보면 수임한 역할의 이름과 그 뒤에 해당 인스턴스의 아이디가 붙어 있는 걸 확인할 수 있습니다.
근데, 실제로 테스트를 해보면 권한에 따라 결과가 다르게 나타납니다.
먼저 S3 목록 조회를 시도하면 AccessDenied 에러가 발생합니다.
[root@ip-192-168-23-192 ~]# aws s3 ls
An error occurred (AccessDenied) when calling the ListBuckets operation...
반면, VPC 조회 같은 명령은 정상적으로 실행됩니다.
[root@ip-192-168-23-192 ~]# aws ec2 describe-vpcs --no-cli-pager
{
"Vpcs": [
{
"OwnerId": "003590317363",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-0f16e26f952467e56",
"CidrBlock": "172.31.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": true,
"BlockPublicAccessStates": {
"InternetGatewayBlockMode": "off"
},
"VpcId": "vpc-0b01924fb5a0b4c67",
"State": "available",
"CidrBlock": "172.31.0.0/16",
"DhcpOptionsId": "dopt-0ed890fc7a0c363e8"
},
{
"OwnerId": "003590317363",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-0688450617ec00865",
"CidrBlock": "192.168.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": false,
"Tags": [
{
"Key": "Environment",
"Value": "cloudneta-lab"
},
{
"Key": "Name",
"Value": "myeks-VPC"
}
],
"BlockPublicAccessStates": {
"InternetGatewayBlockMode": "off"
},
"VpcId": "vpc-0436a16630f398d07",
"State": "available",
"CidrBlock": "192.168.0.0/16",
"DhcpOptionsId": "dopt-0ed890fc7a0c363e8"
}
]
}
왜 이런 차이가 발생하는지 권한 정책 관점에서 보면 명확합니다.
- VPC 조회가 성공하는 이유: 노드가 사용하는 역할에 연결된 정책 중에 VPC 조회 액션이 포함되어 있기 때문입니다. 아마도 EKS CNI 관련 정책(AmazonEKS_CNI_Policy) 같은 곳에 해당 권한이 들어있어서 조회가 가능한 거죠.
- S3 조회가 실패하는 이유: 반대로 이 역할에 연결된 그 어떤 정책에서도 S3 관련 액션을 허용하고 있지 않기 때문입니다. 아무리 역할을 수임했어도 허용된 정책이 없으면 S3 조회를 할 수 없는 게 당연합니다.

"얘가 AWS 쪽으로 호출이 되네?" 싶을 때, 실제로 자격 증명을 만들어서 확인할 수 있습니다
[1] 워커 노드 1대 SSM 진입 후 기본 정보 확인 : 임시 토큰 생성 : Client side (aws-iam-authenticator token) - Github
아래에 apiVersion: client.authentication.k8s.io/v1beta1 라고 되어있다.
[root@ip-192-168-23-192 ~]# aws eks --region ap-northeast-2 update-kubeconfig --name myeks
Added new context arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks to /root/.kube/config
[root@ip-192-168-23-192 ~]# cat /root/.kube/config
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJT0hZYzlOUzIxOGt3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpBME1UQXdPVE15TVRWYUZ3MHpOakEwTURjd09UTTNNVFZhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURJUzJPMEhJcTRVYk1kNHlsVzJjQXQ0WEFrcVJxdHovR2xERmcxdlFKQU1qTy84MmdjRWNKZTFNenoKdWtMeE5RT01XWHVsaGxHUkRaSG9HUk5uWkNHM01MLzJTejgvb203TnlkR3pCUHZabGtmNjFHbWxNZUhVSXFKUQpmTE4zYlNMblRyL1BIdjRxNkhqc3VOZHBEbmNVZHZWRTJnV0ZiaVRobURWQzQ4Z3h1aE1TeG94dVhESzdVZUZaCjV6OExGS3piZXVXclkvOFNjbDVnOWRxQUZuU21ycnFaLzZaS2NhRDRDcDRueXA4S1czeUUwTVVXMzhlc082ckwKRkt5MlZZblFFU3dNbDNobGFvbTdrQVdrNWw2aDVtQmlPL2ZxR3lMZWh0eDFHOEN1QlZZSGd4ZEtIVlo0TUNsZQoxQXlLYi9qcXYvWSt6c2dxY3Ntdi9BTkZWbWtKQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUMnBsaWxPOUprdWZVQmZBdjdoS2hoQWxwYkRqQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQkFJcXA0TXgwcQo2WHRMOWtML2MwUFpDbUxpbVV1RXNaWlI5Mkh3NDNHdllLN2VEcElMcmhXZDlvTlhvYjZOU3BOUUtPYlh4cDVaCjZiTkRRUXhBaG5DVG5pQ2k4MnM1OXRqWWF6bXRnSzlNcDAxWnJtaWNwVGZiVkljQ1BKYXhBemp0NGV1RDEvdVMKcHRlWUVxSGNscHg4N0twZC9zelF1YVg0MnBjU0JBTU51Z1AxQWVkRmJrKytLUGIwUk02QVNGd01PUDhKOGRDcgovQ2pOdXlxMVJ6Skk1TDBMVWp5WVNoNm5vbWxhWXo2VzlucmhEN3JEN1R2SWNHMlJrQTlZT1liTll2TXFxOFNnCkpUb21leGtDZG9yc1JRd2tHeG5lbVFKaFRYVHQzZW1kSUQ2eE01M3NpZVc4VC9hYWd6YkRDYlpRNnlBbXorbWMKVXdrV1gxOUxabGhhCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://0EA4A3B074D74BA56D4313FB2813E142.gr7.ap-northeast-2.eks.amazonaws.com
name: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
contexts:
- context:
cluster: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
user: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
name: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
current-context: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
kind: Config
preferences: {}
users:
- name: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- --region
- ap-northeast-2
- eks
- get-token
- --cluster-name
- myeks
- --output
- json
command: aws
.여기서 직접 토큰을 요청해 보면 아주 긴 문자열이 발급되는데, 아까 설명한 것처럼 Session Token까지 포함되어 있어서 장기 키로 만들 때보다 훨씬 깁니다.
export CLUSTER_NAME=myeks
[root@ip-192-168-23-192 ~]# aws eks get-token --cluster-name $CLUSTER_NAME | jq
{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {},
"status": {
"expirationTimestamp": "2026-04-10T14:42:22Z",
"token": "k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFTSUFRQlZQNzRVWjVEQTM0VE1FJTJGMjAyNjA0MTAlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDEwVDE0MjgyMlomWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2VjdXJpdHktVG9rZW49SVFvSmIzSnBaMmx1WDJWakVHWWFEbUZ3TFc1dmNuUm9aV0Z6ZEMweUlrZ3dSZ0loQUlJSDJoNHZhJTJCYkI2Z2h6ckFUV2klMkZDS2lWZWNGQ3E0JTJGYVd6cjBrb2dRU0FBaUVBNUZVTmREU0kzdzdhJTJGWHVxUU1jOTVVUThxUkkwMTlUdGZabzg3U3BkSzA4cXpBVUlMeEFBR2d3d01ETTFPVEF6TVRjek5qTWlETm9tVTMlMkZUJTJCS2ZROGZqOHRpcXBCY280Vmg1JTJCZ2lDODlrV2RzU0FQcUFzOVRaRiUyQjJMNklnMDR5NFQlMkJhRlNHU28yeUd3JTJCRkQlMkJ1aENpdnk5bGpNaiUyRmN4Mm5odyUyRnlpaEl3JTJGcXNrT1FMT0tKMzhIMWtXSjNFUFMzMUNuVSUyQkpsWWUlMkJLcU41eEJNJTJGUUZ3eXg1ZUlHVkNPSU1rSVd1OSUyRmh1VGlPQjltJTJCbWlIWjglMkZPak5SYjZqRkphTHNzN2NzVm14RWtPeU9pUzM2ZjEzRDZ5WXZmb0NhckdyN3clMkJGYnBBaXM5STltbTJrd0JmJTJCZ0RHUkxJZTFDODJTeUhycURrJTJCVm96emhuZVZXTjl1eGRhUWlUQ0FMNmNXVU1TelRSJTJGYiUyQjczQ24yTkU4NWtDd0NNR2w2TTRBa2clMkZvNnlMV2VvbWoxVDhPOFhjQXglMkZwNTRPZnNpUHNheXJtT0tyYmQzZmNaMVFVdTFkNGJLRENWZ0pxaUxpNHFta3hFVVh1VDRYek5wUGFOc1ZxR2FieSUyRklBT2xudWUwWENDJTJCeCUyRlhlMTNldCUyQmFZbW4lMkZSVjViVk9ZR3BxZSUyRjZyV3hBdnRCT3RGWWtxTUZlVCUyRlZhSktQT3lPZDhpMmtUaXpySHVpaEhvZzhTVTV0SnFyYlNBJTJGNTRQSklUVExFNTJhSE1hcGNXcjFzVzljU2JabXFEYlJNJTJCV0R5OSUyQjROUERpZVkwOGRzTVdJZiUyQjdOaFF2WXElMkJRNkIzWWwlMkJxenh6dlBKdUthJTJCVnh3Q1diTlE2ak9JQ1hCU29VU2VicEI3U2VXVWJXWlFhemFZUnJHZ1l1M3d2VWd5M3h5Q1ZkVkxxOURsdUZSMVNCamNXMm5IdG5DTmJCaE5PN1gwdlRrT1drN1NOYyUyRlc2JTJCWm0yeFNYZjRQbGFjVmZGc043Qll1SGtQRUMzJTJGSjZBdiUyQk9KWWtTJTJCSUJUakNjaDZ1NTF5MkR4WTE2bnNxTjNZVVhDRzh0OG5QQzh2N3FkcUpHYVJrSyUyQjlxUlhQQTl5UzFrR084SXFKbVklMkZzaU11TUx4d0RQdmhvSjNIZE1pTzBUQUVxNmo2TCUyQmV3V0JTalQwS0VjdCUyQlBESkZ2em1JbFlIc0VSa1VndkclMkYxVVp5Qk5KcklaTkNyWUglMkZXaWh2Y05HaFc0UVlvWSUyRmElMkJaVjdsemlrZFRwMGw3TjVISWpjcHEycm9vJTJCJTJCSW80R09Jb0lUbnk4TU9iRFBpZlpLSEUwTURSbm9ES2hBakNBaXVUT0JqcXhBWTVGdkpSVm9kcyUyRmdZTVp4MGJONHQ4R2dFMDAwdklrbkhjcGZ1N0pxZ3VndVhwZTNYeWg5QmJnaVdUZHE2UjJMUVBzMXY1MjVuclp6JTJCQWdzc0pzZ3dKOE4xRWJ2MWwlMkJHYWVzWFA4aWVDUUVZM09zY29GTmVpMmZHNm1GNERRYyUyRmRzWXRwVHhHJTJGWFhYN3hTN2JMQVkzRmVQcDdabjJOdFZhTE1lRnQ2MWZvUXJxU1ZLTGJpTkZLZjEyWGR4V29Odm00eHNnZGh6dFA3NjJ2MjNaejVLTTN0THk0eUpXMjQxOHp1QiUyQkxVWkFKQTJBJTNEJTNEJlgtQW16LVNpZ25hdHVyZT03NzQ4ZmZlODUxZTNkMjIzYmFkM2IwOTk2OTQ4Y2MxZTllYTE5M2JmYmY3ZDczOTY4YmJjYWEzNjE2MzdhNmE2"
}
}
디버그 모드로 확인해 보면, 내부적으로 어떻게 시그니처를 만들고 AWS STS로 요청을 보내는지 그 과정이 로그에 고스란히 찍힙니다.
[root@ip-192-168-23-192 ~]# aws eks get-token --cluster-name $CLUSTER_NAME --debug | jq
2026-04-10 14:28:38,371 - MainThread - botocore.auth - DEBUG - Signature:
33192d3744bccf64b42596ba22b9c737faaa4882f5b2c20f509a65e0d6a4ccab
EC2도 뭔가 자격증명이 되는구나. 확인을 했다. 이제 EKS API를 호출해서 확인을 좀 해보도록 하자.
[2] 클라이언트(kubectl)가 K8S(EKS) API 서버(Endpoint)에 Action 요청 : 일반적인 k8s api 요청 호출 + Bearer Token 포함
여기서 이제 kubectl 명령어를 날리려면 클라이언트 도구가 있어야 되겠죠. 다운받고 설치를 하면 됩니다.
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.35.2/2026-02-27/bin/linux/amd64/kubectl install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl kubectl version
Client Version: v1.35.2-eks-f69f56f
Kustomize Version: v5.7.1
Server Version: v1.35.2-eks-f69f56f
kubectl을 다운로드하고 실행해서 조회를 해보면, 파드를 조회할 수 있는 권한은 없다고 나옵니다. 노드 전체를 조회하는 것도 권한이 없어서 안 됩니다.
그런데 자기 자신의 노드는 조회가 됩니다. -v=10 옵션을 주고 확인해 보면 자기 노드 정보는 정상적으로 호출되는 걸 볼 수 있습니다.
kubectl get pod -A
Error from server (Forbidden): pods is forbidden: User "system:node:ip-192-168-23-192.ap-northeast-2.compute.internal" cannot list resource "pods" in API group "" at the cluster scope: can only list/watch pods with spec.nodeName field selector
kubectl get node
Error from server (Forbidden): nodes is forbidden: User "system:node:ip-192-168-23-192.ap-northeast-2.compute.internal" cannot list resource "nodes" in API group "" at the cluster scope: node 'ip-192-168-23-192.ap-northeast-2.compute.internal' cannot read all nodes, only its own Node object
kubectl get node ip-192-168-17-92.ap-northeast-2.compute.internal -v=10
...
ns":{"k:{\"type\":\"DiskPressure\"}":{"f:lastHeartbeatTime":{}},"k:{\"type\":\"MemoryPressure\"}":{"f:lastHeartbeatTime":{}},"k:{\"type\":\"PIDPressure\"}":{"f:lastHeartbeatTime":{}},"k:{\"type\":\"Ready\"}":{"f:lastHeartbeatTime":{},"f:lastTransitionTime":{},"f:message":{},"f:reason":{},"f:status":{}}},"f:images":{}}},"subresource":"status"}]}}}]}
>
NAME STATUS ROLES AGE VERSION
ip-192-168-23-192.ap-northeast-2.compute.internal Ready <none> 4h48m v1.35.2-eks-f69f56
이게 가능한 이유는 아까 살펴본 RBAC 중에서 Node Authorizer와 Webhook 같은 특수 인가 모드 때문입니다. 노드가 자기 자신의 노드 정보는 호출이 가능하도록 설계되어 있기 때문입니다.
이제 이 노드가 생성한 토큰을 가지고 TokenReview를 통해 신원을 확인해 보겠습니다. 호출하는 주소를 해당 EKS 엔드포인트로 바꿔서 처리해 보며, 이 토큰이 API 서버에서 어떻게 주체 정보로 변환되는지 체크해 보겠습니다.
TOKEN_DATA=$(aws eks get-token --cluster-name myeks | jq -r '.status.token') echo $TOKEN_DATA
# 2. curl 호출 : 로그에 찍힌 URL과 헤더에 토큰 추가하여 실행 => 15분 후 토큰 만료되니, 15분 이후에는 토큰 재발급해서 사용 필요! ## Authorization: Bearer 확인!
curl -k -v -XGET -H "Authorization: Bearer $TOKEN_DATA" -H "Accept: application/json" 'https://0EA4A3B074D74BA56D4313FB2813E142.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes/ip-192-168-23-192.ap-northeast-2.compute.internal'
> GET /api/v1/nodes/ip-192-168-23-192.ap-northeast-2.compute.internal HTTP/2
> Host: 0EA4A3B074D74BA56D4313FB2813E142.gr7.ap-northeast-2.eks.amazonaws.com
> User-Agent: curl/8.17.0
> Authorization: Bearer k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFTSUFRQlZQNzRVWjVEQTM0VE1FJTJGMjAyNjA0MTAlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVz...
curl -k -v -XGET -H "Authorization: Bearer $TOKEN_DATA" -H "Accept: application/json" 'https://0EA4A3B074D74BA56D4313FB2813E142.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes/ip-192-168-23-192.ap-northeast-2.compute.internal' | jq
[5 bytes data]
100 12073 0 12073 0 0 774903 0 --:--:-- --:--:-- --:--:-- 804866
* Connection #0 to host 0EA4A3B074D74BA56D4313FB2813E142.gr7.ap-northeast-2.eks.amazonaws.com:443 left intact
{
"kind": "Node",
"apiVersion": "v1",
"metadata": {
"name": "ip-192-168-23-192.ap-northeast-2.compute.internal",
"uid": "97f8d498-dc0d-461a-9a9c-fc7b583756e2",
"resourceVersion": "52073",
"creationTimestamp": "2026-04-10T09:43:42Z",
...
[3] k8s(eks) api 는 ‘웹 토큰 인증’ 방식을 통한 인증을 위해서, Token Review 요청 ⇒ AWS 인증 확인 후 응답 시 : 유저, K8S Subject(group) 등
여기 TokenReview를 보시면 TokenReview의 응답 값이 어떻게 나오는지를 좀 이해하셨을 겁니다.
# 워커 노드 1대 SSM 진입 Token 메모 TOKEN_DATA=$(aws eks get-token --cluster-name myeks | jq -r '.status.token')
echo $TOKEN_DATA # 메모
k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFTSUFRQlZQNzRVWjVEQTM0VE1FJTJGMjAyNjA0MTAlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDEwVDE0Mzg1NVomWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2VjdXJpdHktVG9rZW49SVFvSmIzSnBaMmx1WDJWakVHWWFEbUZ3TFc1dmNuUm9aV0Z6ZEMweUlrZ3dSZ0loQUlJSDJoNHZhJTJCYkI2Z2h6ckFUV2klMkZDS2lWZWNGQ3E0JTJGYVd6cjBrb2dRU0FBaUVBNUZVTmREU0kzdzdhJTJGWHVxUU1jOTVVUThxUkkwMTlUdGZabzg3U3BkSzA4cXpBVUlMeEFBR2d3d01ETTFPVEF6TVRjek5qTWlETm9tVTMlMkZUJTJCS2ZROGZqOHRpcXBCY280Vmg1JTJCZ2lDODlrV2RzU0FQcUFzOVRaRiUyQjJMNklnMDR5NFQlMkJhRlNHU28yeUd3JTJCRkQlMkJ1aENpdnk5bGpNaiUyRmN4Mm5odyUyRnlpaEl3JTJGcXNrT1FMT0tKMzhIMWtXSjNFUFMzMUNuVSUyQkpsWWUlMkJLcU41eEJNJTJGUUZ3eXg1ZUlHVkNPSU1rSVd1OSUyRmh1VGlPQjltJTJCbWlIWjglMkZPak5SYjZqRkphTHNzN2NzVm14RWtPeU9pUzM2ZjEzRDZ5WXZmb0NhckdyN3clMkJGYnBBaXM5STltbTJrd0JmJTJCZ0RHUkxJZTFDODJTeUhycURrJTJCVm96emhuZVZXTjl1eGRhUWlUQ0FMNmNXVU1TelRSJTJGYiUyQjczQ24yTkU4NWtDd0NNR2w2TTRBa2clMkZvNnlMV2VvbWoxVDhPOFhjQXglMkZwNTRPZnNpUHNheXJtT0tyYmQzZmNaMVFVdTFkNGJLRENWZ0pxaUxpNHFta3hFVVh1VDRYek5wUGFOc1ZxR2FieSUyRklBT2xudWUwWENDJTJCeCUyRlhlMTNldCUyQmFZbW4lMkZSVjViVk9ZR3BxZSUyRjZyV3hBdnRCT3RGWWtxTUZlVCUyRlZhSktQT3lPZDhpMmtUaXpySHVpaEhvZzhTVTV0SnFyYlNBJTJGNTRQSklUVExFNTJhSE1hcGNXcjFzVzljU2JabXFEYlJNJTJCV0R5OSUyQjROUERpZVkwOGRzTVdJZiUyQjdOaFF2WXElMkJRNkIzWWwlMkJxenh6dlBKdUthJTJCVnh3Q1diTlE2ak9JQ1hCU29VU2VicEI3U2VXVWJXWlFhemFZUnJHZ1l1M3d2VWd5M3h5Q1ZkVkxxOURsdUZSMVNCamNXMm5IdG5DTmJCaE5PN1gwdlRrT1drN1NOYyUyRlc2JTJCWm0yeFNYZjRQbGFjVmZGc043Qll1SGtQRUMzJTJGSjZBdiUyQk9KWWtTJTJCSUJUakNjaDZ1NTF5MkR4WTE2bnNxTjNZVVhDRzh0OG5QQzh2N3FkcUpHYVJrSyUyQjlxUlhQQTl5UzFrR084SXFKbVklMkZzaU11TUx4d0RQdmhvSjNIZE1pTzBUQUVxNmo2TCUyQmV3V0JTalQwS0VjdCUyQlBESkZ2em1JbFlIc0VSa1VndkclMkYxVVp5Qk5KcklaTkNyWUglMkZXaWh2Y05HaFc0UVlvWSUyRmElMkJaVjdsemlrZFRwMGw3TjVISWpjcHEycm9vJTJCJTJCSW80R09Jb0lUbnk4TU9iRFBpZlpLSEUwTURSbm9ES2hBakNBaXVUT0JqcXhBWTVGdkpSVm9kcyUyRmdZTVp4MGJONHQ4R2dFMDAwdklrbkhjcGZ1N0pxZ3VndVhwZTNYeWg5QmJnaVdUZHE2UjJMUVBzMXY1MjVuclp6JTJCQWdzc0pzZ3dKOE4xRWJ2MWwlMkJHYWVzWFA4aWVDUUVZM09zY29GTmVpMmZHNm1GNERRYyUyRmRzWXRwVHhHJTJGWFhYN3hTN2JMQVkzRmVQcDdabjJOdFZhTE1lRnQ2MWZvUXJxU1ZLTGJpTkZLZjEyWGR4V29Odm00eHNnZGh6dFA3NjJ2MjNaejVLTTN0THk0eUpXMjQxOHp1QiUyQkxVWkFKQTJBJTNEJTNEJlgtQW16LVNpZ25hdHVyZT1iYzZjYjlkODkxZDk4NDQ5ZjY5NTNlOTBmMWM2OWQyZTJhYzIyMjkwOGQ4ZGMxNzJhNTE5OWI4M2IyMmI0YjMy
이제 로컬 PC에서 TokenReview를 직접 날려보겠습니다. 요청을 보내면 응답 내용이 쭉 나오는데, status 항목을 유심히 보셔야 합니다.
결과를 보면 authenticated: true로 인증이 되었고, 특히 groups 부분을 보시면 system:nodes라는 그룹을 리턴받은 게 보이실 겁니다. 내가 노드의 토큰을 담아 요청했더니 서버가 이 정보를 보내준 거예요.
TOKEN_DATA=위에 메모한 토큰
❯ cat > token-review.yaml << EOF
apiVersion: authentication.k8s.io/v1
kind: TokenReview
metadata:
name: mytoken
spec:
token: ${TOKEN_DATA}
EOF
cat token-review.yaml
kubectl create -f token-review.yaml -v=9
},"status":{"authenticated":true,
"user":{"username":"system:node:ip-192-168-23-192.ap-northeast-2.compute.internal",
"uid":"aws-iam-authenticator:003590317363:AROAQBVP74UZVXK7XTSSM",
"groups":["system:nodes","system:authenticated"],
"extra":{"accessKeyId":["ASIAQBVP74UZ5DA34TME"],
"arn":["arn:aws:sts::003590317363:assumed-role/myeks-ng-1/i-01e44c1379c4000ac"],
"canonicalArn":["arn:aws:iam::003590317363:role/myeks-ng-1"],
"principalId":["AROAQBVP74UZVXK7XTSSM"],"sessionName":["i-01e44c1379c4000ac"],
"sigs.k8s.io/aws-iam-authenticator/principalId":["AROAQBVP74UZVXK7XTSSM"]}},
"audiences":["https://kubernetes.default.svc"]}}
이게 왜 중요하냐면, 우리가 방금 요청에 사용한 토큰이 바로 Node IAM Role 기반의 토큰이었기 때문입니다. 이 토큰으로 TokenReview를 요청했더니 응답에 다음과 같은 정보가 실려 온 거죠.
- authenticated: true
- groups: system:nodes, system:authenticated
여기서 system:authenticated는 인증이 되면 무조건 포함되는 값이고, 핵심은 system:nodes라는 그룹 정보가 리턴되어 왔다는 점입니다.
그럼 왜 이 그룹이 포함되어서 왔을까요? 그 이유는 아까 우리가 Access Entry에서 확인했던 설정값 때문입니다. EKS API 설정에 이 IAM Role이 system:nodes 그룹에 매핑되어 있었고, 서버는 그 값을 보고 "아, 너는 이 그룹에 속한 애구나"라고 판단해서 리턴해 준 것입니다.

[4] Server side (aws-iam-authenticator server) 는 AWS STS GetCallerIdentity 호출(제출)을 통해 서명 검증(인증!) 후 사용자 정보 반환
이 그림을 다시 보시면, EC2가 직접 토큰을 만들어서 요청하고 GetCallerIdentity를 수행하는 과정을 알 수 있습니다. 이때 이 이벤트를 요청한 주체는 당연히 EC2 Instance의 아이디가 됩니다. 그래서 사용자 이름이 EC2 Instance 아이디로 찍히게 되는 겁니다.

GetCallerIdentity의 사용자 이름이 이벤트 소스가 이거잖아요.

클릭해서 보시면 여기에 type Role이라고 돼 있죠. Role Assume한 이 인스턴스가 직접 신원 확인을 요청한 거죠. 자신의 노드에 있는 토큰을 가지고 GetCallerIdentity를 수행해서, 이런 이벤트 정보가 확인되는 것을 보실 수 있습니다.

[5] 방안1 EKS API - Docs : AWS IAM 주체 → K8S Subject 매칭 + k8s Authz ‘Webhook’ 인가! - SubjectAccessReview
직접 설치해서 실행해 보면 현재 그룹 정보가 아래 보면 표현됩니다. 이 TokenReview 항목으로 표출되는 것 같습니다. 어차피 인증 관련 정보를 보여주는 도구이기 때문에, UID와 groups 항목의 system:nodes 및 system:authenticated 정보가 포함되어 있고, extra 영역에는 AWS에서 관리하는 주요 정보 값들이 들어가 있습니다.
❯ aws ssm start-session --target $NODE1
Starting session with SessionId: admin-uad89qeo7uxt4fbhgyq4qe94c4
sh-5.2$ sudo su -
Last login: Fri Apr 10 14:16:35 UTC 2026 on pts/1
[root@ip-192-168-23-192 ~]# curl https://raw.githubusercontent.com/alcideio/rbac-tool/master/download.sh | bash
rbac-tool -h
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 9446 100 9446 0 0 37541 0 --:--:-- --:--:-- --:--:-- 37484
alcideio/rbac-tool info checking GitHub for latest tag
alcideio/rbac-tool info found version: 1.20.0 for v1.20.0/linux/amd64
alcideio/rbac-tool info installed ./bin/rbac-tool
rbac-tool
[root@ip-192-168-23-192 ~]# rbac-tool whoami
{Username: "system:node:ip-192-168-23-192.ap-northeast-2.compute.internal",
UID: "aws-iam-authenticator:003590317363:AROAQBVP74UZVXK7XTSSM",
Groups: ["system:nodes",
"system:authenticated"],
Extra: {accessKeyId: ["ASIAQBVP74UZ5DA34TME"],
arn: ["arn:aws:sts::003590317363:assumed-role/myeks-ng-1/i-01e44c1379c4000ac"],
canonicalArn: ["arn:aws:iam::003590317363:role/myeks-ng-1"],
principalId: ["AROAQBVP74UZVXK7XTSSM"],
sessionName: ["i-01e44c1379c4000ac"],
sigs.k8s.io/aws-iam-authenticator/principalId: ["AROAQBVP74UZVXK7XTSSM"]}}
빠져나와서 정보를 확인해 보면 이런 방식으로도 쓰입니다. 아까 Subject, 즉 주체가 포함된다고 말씀드렸는데, 이 명령어는 해당 주체가 어떤 ClusterRole이나 Role을 가지고 있고 어떻게 바인딩되어 있는지 확인하는 용도입니다.
K8s Subject 주체는 종류가 다양한데, 크게 세 가지 타입으로 구분됩니다.
K8s Subject 주체의 종류는 크게 세 가지입니다. 흔히 SA라고 부르는 ServiceAccount, 그리고 User와 Group이 있습니다. 방금 확인한 system:nodes는 Subject 타입 중 Group에 속합니다. 이 그룹을 사용하는 대상은 특정 RoleName이 부여되어 있고 바인딩이 구성되어 있는데, 스코프는 ClusterRole입니다.
❯ kubectl rbac-tool lookup system:nodes
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE | BINDING
---------------+--------------+-------------+-----------+-----------------------+------------------------
system:nodes | Group | ClusterRole | | eks:node-bootstrapper | eks:node-bootstrapper
이 정보를 rolesum(Krew 플러그인)으로 체크해 볼 수 있습니다. 옵션에서 -k는 주체를 나타내며, 생략 시 ServiceAccount가 기본값입니다. 필요에 따라 유저나 그룹을 직접 지정할 수도 있습니다.
해당 그룹에 매핑된 정책을 보면 ClusterRoleBinding(CRB), ClusterRole(CR)이 확인됩니다. 만약 일반 Role 기반이라면 앞의 'C'가 빠진 형태로 표시될 것입니다. 결과적으로 이 리소스들에 대해 특정 verbs(권한)를 실행할 수 있도록 설정되어 있습니다.
지금까지 워커 노드(EC2) 입장에서 EKS API를 호출하는 과정을 확인했습니다. 이런 방식으로 인증과 인가를 마치고 K8s RBAC을 확인한 뒤 최종 응답을 주게 됩니다.
❯ kubectl rolesum -k Group system:nodes
Group: system:nodes
Policies:
• [CRB] */eks:node-bootstrapper ⟶ [CR] */eks:node-bootstrapper
Resource Name Exclude Verbs G L W C U P D DC
certificatesigningrequests.certificates.k8s.io/selfnodeclient [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
certificatesigningrequests.certificates.k8s.io/selfnodeserver [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
별도 aws cli 환경에서, 신입 Devops 엔지어를 위한 eks 자격 증명 설정
신규 데브옵스 엔지니어를 위한 EKS 자격 증명 세팅 과정을 진행해 보겠습니다. 이 과정을 통해 앞서 살펴본 인증 및 인가 과정을 다시 한번 복습할 수 있습니다.
먼저 신입 사원을 위한 IAM 계정을 생성합니다. 콘솔보다 편리한 CLI를 이용해 사용자를 만들고, API 호출을 위한 액세스 키를 발급합니다.
❯ aws iam create-access-key --user-name testuser
❯ aws iam create-user --user-name testuser
{
"AccessKey": {
"UserName": "testuser",
"AccessKeyId": "AKIAQBVP74UZSLL74XEA",
"Status": "Active",
"SecretAccessKey": "3DpxxxxxxYc5suExxxxxxxxxxxJrpn1xxxxxxfOSV",
"CreateDate": "2026-04-10T15:32:35+00:00"
}
}
발급된 AccessKeyId와 SecretAccessKey는 보안상 노출되지 않도록 주의해야 합니다. 해당 사용자는 데브옵스 업무를 수행해야 하므로 관리자 권한(AdministratorAccess)을 부여합니다.
aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --user-name testuser
신입 사원의 작업 환경을 가정하여 로컬에서 aws-cli 컨테이너를 실행합니다. 초기 상태에서는 자격 증명이 없으므로 S3 조회 시 에러가 발생합니다.
❯ docker run -it --name aws-cli --entrypoint /bin/sh amazon/aws-cli
Unable to find image 'amazon/aws-cli:latest' locally
latest: Pulling from amazon/aws-cli
f24558f24158: Pull complete
1a6a90b80f3b: Pull complete
e53a91b7aa62: Pull complete
06e318cc815e: Pull complete
a33da10495c9: Pull complete
Digest: sha256:d342da23236133bce0b638d1fa7a8ead91e016c61ad007035ef6ca08ea87fa67
Status: Downloaded newer image for amazon/aws-cli:latest
sh-5.2# aws s3 ls
aws: [ERROR]: An error occurred (NoCredentials): Unable to locate credentials. You can configure credentials by running "aws login".
aws configure 명령어를 통해 앞서 발급한 자격 증명을 세팅합니다. 설정 후에는 권한에 따라 S3 조회가 가능해지며, 현재 호출자 정보를 통해 testuser로 정상 인증되었음을 확인할 수 있습니다.
sh-5.2# aws configure
AWS Access Key ID [****************4XEA]: AKIAQBVPsfsfsfsf74UZ5NIQNMPQ
AWS Secret Access Key [****************6M8f]: 2FqrbqtjRwo3ZC1nHsfsfsfsfl8tdfdffYHDnNTVV0RcHHztl6M8f
Default region name [ap-northeast-2]:
Default output format [None]:
sh-5.2# aws s3 ls
이제 사전 준비가 완료되었습니다. 이 사용자가 EKS 클러스터를 관리할 수 있도록 kubeconfig 파일을 생성합니다.
sh-5.2# CLUSTER_NAME=myeks
sh-5.2# aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias testuser
Added new context testuser to /root/.kube/config
sh-5.2# cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJT0hZYzlOUzIxOGt3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpBME1UQXdPVE15TVRWYUZ3MHpOakEwTURjd09UTTNNVFZhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURJUzJPMEhJcTRVYk1kNHlsVzJjQXQ0WEFrcVJxdHovR2xERmcxdlFKQU1qTy84MmdjRWNKZTFNenoKdWtMeE5RT01XWHVsaGxHUkRaSG9HUk5uWkNHM01MLzJTejgvb203TnlkR3pCUHZabGtmNjFHbWxNZUhVSXFKUQpmTE4zYlNMblRyL1BIdjRxNkhqc3VOZHBEbmNVZHZWRTJnV0ZiaVRobURWQzQ4Z3h1aE1TeG94dVhESzdVZUZaCjV6OExGS3piZXVXclkvOFNjbDVnOWRxQUZuU21ycnFaLzZaS2NhRDRDcDRueXA4S1czeUUwTVVXMzhlc082ckwKRkt5MlZZblFFU3dNbDNobGFvbTdrQVdrNWw2aDVtQmlPL2ZxR3lMZWh0eDFHOEN1QlZZSGd4ZEtIVlo0TUNsZQoxQXlLYi9qcXYvWSt6c2dxY3Ntdi9BTkZWbWtKQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUMnBsaWxPOUprdWZVQmZBdjdoS2hoQWxwYkRqQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQkFJcXA0TXgwcQo2WHRMOWtML2MwUFpDbUxpbVV1RXNaWlI5Mkh3NDNHdllLN2VEcElMcmhXZDlvTlhvYjZOU3BOUUtPYlh4cDVaCjZiTkRRUXhBaG5DVG5pQ2k4MnM1OXRqWWF6bXRnSzlNcDAxWnJtaWNwVGZiVkljQ1BKYXhBemp0NGV1RDEvdVMKcHRlWUVxSGNscHg4N0twZC9zelF1YVg0MnBjU0JBTU51Z1AxQWVkRmJrKytLUGIwUk02QVNGd01PUDhKOGRDcgovQ2pOdXlxMVJ6Skk1TDBMVWp5WVNoNm5vbWxhWXo2VzlucmhEN3JEN1R2SWNHMlJrQTlZT1liTll2TXFxOFNnCkpUb21leGtDZG9yc1JRd2tHeG5lbVFKaFRYVHQzZW1kSUQ2eE01M3NpZVc4VC9hYWd6YkRDYlpRNnlBbXorbWMKVXdrV1gxOUxabGhhCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://0EA4A3B074D74BA56D4313FB2813E142.gr7.ap-northeast-2.eks.amazonaws.com
name: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
contexts:
- context:
cluster: arn:aws:eks:ap-northeast-2:003590317363:cluster/myeks
user: testuser
name: testuser
current-context: testuser
kind: Config
preferences: {}
users:
- name: testuser
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- --region
- ap-northeast-2
- eks
- get-token
- --cluster-name
- myeks
- --output
- json
command: aws
kubeconfig를 확인해 보면 이전 과정과 원리가 동일합니다. 클라이언트 사이드에서 토큰을 생성할 때 사용자의 Secret Access Key를 기반으로 만들고, 결국 AWS STS를 통해 서명된 키를 비교하여 인증하는 방식입니다.
이제 kubectl 명령어를 사용하기 위해 클라이언트 툴을 설치하고 노드 조회를 시도해 보겠습니다. 조회 결과, 명령은 실패하게 됩니다. 현재 testuser에게 AWS 계정 자체에 대한 관리자 권한(AdministratorAccess)은 부여했지만, EKS 클러스터 내부의 API에 접근할 수 있는 권한은 별도로 설정하지 않았기 때문입니다.
sh-5.2# curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.35.2/2026-02-27/bin/linux/arm64/kubectl
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 53952k 100 53952k 0 0 7595k 0 0:00:07 0:00:07 --:--:-- 9476k
sh-5.2# install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
sh-5.2# kubectl version --client=true
Client Version: v1.35.2-eks-f69f56f
Kustomize Version: v5.7.1
sh-5.2# kubectl get node -v6
I0410 15:40:39.557677 52 loader.go:405] Config loaded from file: /root/.kube/config
I0410 15:40:39.628923 52 envvar.go:172] "Feature gate default state" feature="InOrderInformersBatchProcess" enabled=true
I0410 15:40:39.630314 52 envvar.go:172] "Feature gate default state" feature="InformerResourceVersion" enabled=true
I0410 15:40:39.630703 52 envvar.go:172] "Feature gate default state" feature="WatchListClient" enabled=true
I0410 15:40:39.630900 52 envvar.go:172] "Feature gate default state" feature="ClientsAllowCBOR" enabled=false
I0410 15:40:39.631055 52 envvar.go:172] "Feature gate default state" feature="ClientsPreferCBOR" enabled=false
I0410 15:40:39.631281 52 envvar.go:172] "Feature gate default state" feature="InOrderInformers" enabled=true
이 상태에서는 rbac-tool 같은 진단 도구를 사용하더라도, 기본적인 API 접근 권한 자체가 없기 때문에 정보를 조회할 수 없다는 에러가 발생하게 됩니다.
# rbac-tool 설치 : https://github.com/alcideio/rbac-tool
sh-5.2# yum install tar gzip -y
sh-5.2# curl https://raw.githubusercontent.com/alcideio/rbac-tool/master/download.sh | bash
# Shows the subject for the current context with which one authenticates with the cluster
sh-5.2# ./bin/rbac-tool whoami
Error: Failed to create kubernetes client - the server has asked for the client to provide credentials
[2] 방안1 EKS API 을 통한 Access 설정 : testuser에 AmazonEKSAdminPolicy 정책 부여로 EKS 관리자 수준 권한 설정
이제 Access Entry를 생성하여 EKS 클러스터에 접근할 수 있는 통로를 만들어보겠습니다. 이 작업은 로컬 터미널이나 컨테이너 내부 어디서든 가능합니다. testuser는 이미 AWS 관리자 권한을 가지고 있으므로 AWS API를 호출하여 본인 또는 타인의 접근 항목을 생성할 권한이 있기 때문입니다.
export CLUSTER_NAME=myeks
export ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
aws eks create-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser
{
"accessEntry": {
"clusterName": "myeks",
"principalArn": "arn:aws:iam::003590317363:user/testuser",
"kubernetesGroups": [],
"accessEntryArn": "arn:aws:eks:ap-northeast-2:003590317363:access-entry/myeks/user/003590317363/testuser/e4cebc08-5fcb-4ca3-178c-fad8a45738ae",
"createdAt": "2026-04-10T15:44:16.831000+00:00",
"modifiedAt": "2026-04-10T15:44:16.831000+00:00",
"tags": {},
"username": "arn:aws:iam::003590317363:user/testuser",
"type": "STANDARD"
}
}
Access Entry를 생성하면 목록에 testuser가 나타나지만, 아직 그룹이나 정책이 없어 실제 권한은 없는 상태입니다.

그래서 여기서 권한을 추가해 줍니다. 신입 사원이 실수할 수도 있으니 ClusterAdmin이 아닌 Admin 권한만 부여하겠습니다.
# testuser에 AmazonEKSAdminPolicy 연동
sh-5.2# aws eks associate-access-policy --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy --access-scope type=cluster
{
"clusterName": "myeks",
"principalArn": "arn:aws:iam::003590317363:user/testuser",
"associatedAccessPolicy": {
"policyArn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy",
"accessScope": {
"type": "cluster",
"namespaces": []
},
"associatedAt": "2026-04-10T15:45:44.352000+00:00",
"modifiedAt": "2026-04-10T15:45:44.352000+00:00"
}
}
설정을 마치고 새로고침해 보면 AdminPolicy가 Access 정책으로 들어온 것을 확인할 수 있으며, 이제 인증과 인가 과정이 EKS API를 통해 완료됩니다.

참고로 AdminPolicy는 이름과 달리 노드 조회는 불가능합니다. 특정 네임스페이스 내에서 애플리케이션을 관리하는 개발 팀장급에게 적합한 권한이라고 보시면 됩니다. 실제로 노드 조회를 시도하면 실패하지만, 정책에 포함된 Deployment 조회 등은 정상적으로 작동합니다.

또한 kubectl auth can-i 명령어를 사용하면 특정 동작이 가능한지 체크할 수 있습니다. 예를 들어 파드 조회는 가능하다고 나오지만, 노드 조회가 불가능한 이유는 EKS 관리형 정책에 정의된 리소스와 verbs 권한이 그렇게 설정되어 있기 때문입니다. 결과적으로 워커 노드 입장에서 EKS API를 호출하고 RBAC을 거쳐 응답을 받는 과정까지 확인했습니다.
sh-5.2# kubectl get node
Error from server (Forbidden): nodes is forbidden: User "arn:aws:iam::003590317363:user/testuser" cannot list resource "nodes" in API group "" at the cluster scope
sh-5.2# kubectl get deploy -A
NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE
cert-manager cert-manager 2/2 2 2 6h7m
cert-manager cert-manager-cainjector 2/2 2 2 6h7m
cert-manager cert-manager-webhook 2/2 2 2 6h7m
external-dns external-dns 1/1 1 1 6h8m
kube-system coredns 2/2 2 2 6h8m
kube-system metrics-server 2/2 2 2 6h8m
sh-5.2# kubectl auth can-i delete pods --all-namespaces
yes
EKS에서 제공하는 관리형 정책이 아닌, 직접 만든 커스텀 RBAC을 사용해보자!
제가 되는 지점은 EKS API를 사용할 때 직접 만든 커스텀 RBAC이 필요한 경우입니다. 이럴 때는 어쩔 수 없이 직접 RBAC을 구성해 주어야 합니다.
[0] testuser의 Access Entry 제거
우선 기존에 설정했던 Access Entry를 삭제합니다
sh-5.2# aws eks delete-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser
삭제 후 콘솔을 새로고침하면 등록되어 있던 Access Entry가 사라진 것을 확인할 수 있습니다.
[1] K8s ClusterRole 및 ClusterRoleBinding 생성
이제 직접 정의한 커스텀 RBAC인 ClusterRole과 ClusterRoleBinding을 생성하는 단계입니다. 먼저 ClusterRole을 2개 생성하여 하나씩 적용해 보도록 하겠습니다.
❯ # ClusterRole 생성
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-viewer-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list", "get", "watch"]
--- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-admin-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["*"]
EOF
clusterrole.rbac.authorization.k8s.io/pod-viewer-role created
clusterrole.rbac.authorization.k8s.io/pod-admin-role created
파드 어드민 수준의 권한을 가진 pod-admin-role과 조회가 가능한 pod-viewer-role 등 2개의 ClusterRole을 생성했습니다. apiGroups가 비어 있는 것은 코어 API 그룹을 의미하며, 리소스와 verbs는 다음과 같이 설정되었습니다.
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list", "get", "watch"]
이후 각각의 ClusterRole을 바인딩하는 ClusterRoleBinding을 생성합니다. 여기서 중요한 점은 주체(Subject)를 특정 사용자가 아닌 그룹(Group)으로 지정했다는 것입니다.
❯ kubectl create clusterrolebinding viewer-role-binding --clusterrole=pod-viewer-role --group=pod-viewer
clusterrolebinding.rbac.authorization.k8s.io/viewer-role-binding created
❯ kubectl create clusterrolebinding admin-role-binding --clusterrole=pod-admin-role --group=pod-admin
clusterrolebinding.rbac.authorization.k8s.io/viewer-role-binding created
❯ kubectl get clusterrolebinding | grep -E 'admin-role|viewer-role'
admin-role-binding ClusterRole/pod-admin-role 2s
viewer-role-binding ClusterRole/pod-viewer-role 3s
설정 후 rbac-tool을 통해 확인해 보면, pod-viewer라는 Subject가 Group 타입으로 생성되어 pod-viewer-role과 정상적으로 바인딩된 것을 볼 수 있습니다.
❯ kubectl rbac-tool lookup pod-viewer
SUBJECT | SUBJECT TYPE | SCOPE | ROLE | BINDING
-------------+--------------+-------------+-----------------+----------------------
pod-viewer | Group | ClusterRole | pod-viewer-role | viewer-role-binding
또한 rolesum 명령어로 확인하면, 해당 그룹에 매핑된 ClusterRoleBinding(CRB)과 ClusterRole(CR)의 구체적인 리소스 권한(Get, List, Watch 등)을 체크할 수 있습니다. 이렇게 해서 수동으로 커스텀 RBAC 구성을 마쳤습니다.
❯ kubectl rolesum -k Group pod-viewer
Group: pod-viewer
Policies:
• [CRB] */viewer-role-binding ⟶ [CR] */pod-viewer-role
Resource Name Verbs G L W C U P D DC
pods [*] [-] ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖
[2] Access Entry 설정 : pod-viewer 그룹
이제 Access Entry를 생성합니다. 여기서 중요한 포인트는 Kubernetes에 직접 만든 그룹인 pod-viewer를 Access Entry의 그룹으로 지정하는 것입니다. AWS에서 제공하는 관리형 정책이 아닌, 커스텀 RBAC을 사용할 때는 이와 같은 방식이 필요합니다.
컨테이너 안에서 아래 명령어를 실행합니다.
sh-5.2# aws eks create-access-entry --cluster-name $CLUSTER_NAME --principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser --kubernetes-group pod-viewer
실행 결과(JSON):
{
"accessEntry": {
"clusterName": "myeks",
"principalArn": "arn:aws:iam::003590317363:user/testuser",
"kubernetesGroups": [
"pod-viewer"
],
"accessEntryArn": "arn:aws:eks:ap-northeast-2:003590317363:access-entry/myeks/user/003590317363/testuser/...",
"createdAt": "2026-04-10T16:08:23.671000+00:00",
"username": "arn:aws:iam::003590317363:user/testuser",
"type": "STANDARD"
}
}
이렇게 생성한 뒤 AWS 관리 콘솔에서 확인해 보면, 그룹 이름 항목에 pod-viewer가 표시됩니다. 이는 Kubernetes 클러스터 내부에 정의된 주체 그룹 정보가 정상적으로 매핑되었음을 의미합니다.

[3] kubectl 사용 확인
실제로 권한이 잘 적용되었는지 확인해 보겠습니다.
- 노드 조회: 권한이 없으므로 당연히 실패합니다.
- 파드 조회: pod-viewer 그룹에 속해 있으므로 정상적으로 조회됩니다.
- 파드 삭제: 현재는 '뷰어' 권한만 있으므로 삭제 시도는 거부됩니다.
h-5.2# kubectl get node -A
Error from server (Forbidden): nodes is forbidden: User "arn:aws:iam::003590317363:user/testuser" cannot list resource "nodes" in API group "" at the cluster scope
sh-5.2# kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
cert-manager cert-manager-b5c55cbd4-4ccjj 1/1 Running 0 6h25m
cert-manager cert-manager-b5c55cbd4-tvddl 1/1 Running 0 6h25m
cert-manager cert-manager-cainjector-6fc9d9ddc7-jjgnp 1/1 Running 0 6h25m
cert-manager cert-manager-cainjector-6fc9d9ddc7-w44dd 1/1 Running 0 6h25m
cert-manager cert-manager-webhook-cf55c69cf-4g848 1/1 Running 0 6h25m
cert-manager cert-manager-webhook-cf55c69cf-p24hk 1/1 Running 0 6h25m
external-dns external-dns-79bc6f9c7c-xj262 1/1 Running 0 6h25m
kube-system aws-node-hvjfs 2/2 Running 0 6h26m
kube-system aws-node-ms7dh 2/2 Running 0 6h26m
kube-system coredns-7b7dc46964-klvhl 1/1 Running 0 6h25m
kube-system coredns-7b7dc46964-pbb4x 1/1 Running 0 6h25m
kube-system eks-pod-identity-agent-hq49t 1/1 Running 0 6h25m
kube-system eks-pod-identity-agent-w7hf4 1/1 Running 0 6h25m
kube-system kube-proxy-4vvmt 1/1 Running 0 6h25m
kube-system kube-proxy-m9975 1/1 Running 0 6h25m
kube-system metrics-server-69cfcf7444-cs5bf 1/1 Running 0 6h25m
kube-system metrics-server-69cfcf7444-sqhhr 1/1 Running 0 6h25m
sh-5.2# kubectl auth can-i get pods --all-namespaces
yes
sh-5.2# kubectl auth can-i delete pods --all-namespaces
no
[4] Access Entry 설정 업데이트: pod-admin 그룹
이번에는 더 높은 권한을 주기 위해 Access Entry를 업데이트해 보겠습니다. 기존의 pod-viewer 그룹을 pod-admin으로 변경합니다.
# Access Entry의 쿠버네티스 그룹 업데이트
sh-5.2# aws eks update-access-entry --cluster-name $CLUSTER_NAME \
--principal-arn arn:aws:iam::$ACCOUNT_ID:user/testuser \
--kubernetes-group pod-admin | jq -r .accessEntry
{
"clusterName": "myeks",
"principalArn": "arn:aws:iam::003590317363:user/testuser",
"kubernetesGroups": [
"pod-admin"
],
"accessEntryArn": "arn:aws:eks:ap-northeast-2:003590317363:access-entry/myeks/user/003590317363/testuser/...",
"createdAt": "2026-04-10T16:08:23.671000+00:00",
"modifiedAt": "2026-04-10T16:10:43.818000+00:00",
"username": "arn:aws:iam::003590317363:user/testuser",
"type": "STANDARD"
}
업데이트 후 AWS 관리 콘솔에서 새로고침하면, 매핑된 그룹이 pod-admin으로 변경된 것을 확인할 수 있습니다. 이제 이 사용자는 이전에 불가능했던 파드 삭제 등의 관리 작업을 수행할 수 있게 됩니다.

업데이트 후 사용 권한을 다시 확인해 보면, pod-admin 그룹 권한 덕분에 이전과 달리 파드를 삭제할 수 있게 됩니다.
# 모든 네임스페이스의 파드 삭제 권한 확인 (성공)
sh-5.2# kubectl auth can-i delete pods --all-namespaces
yes
이와 같이 커스텀 RBAC 그룹을 Access Entry에 매핑하여 정교한 권한 제어가 가능합니다. 실습을 마친 후에는 사용한 리소스를 아래와 같이 정리하여 초기화합니다.
리소스 정리 및 초기화
- 로컬 컨테이너 삭제 작업에 사용했던 aws-cli 컨테이너를 강제로 삭제합니다.
❯ docker rm -f aws-cli - IAM 사용자 삭제 불필요해진 testuser 계정을 AWS 관리 콘솔(IAM)에서 삭제하거나 비활성화하여 보안을 유지합니다.