Kubernetes SIG Auth 및 SIG Node를 대표하여, Kubernetes v1.36에서 세분화된(fine-grained) kubelet API 권한 부여 기능이 정식 출시(General Availability, GA)되었음을 알려드립니다!
KubeletFineGrainedAuthz 기능 게이트는 Kubernetes v1.32에서 선택 사양인 알파(alpha) 기능으로 도입되었으며, v1.33에서 베타(beta) 단계로 승급되어 기본적으로 활성화되었습니다. 이제 v1.36에서는 이 기능이 정식 버전이 되었으며 기능 게이트는 '활성화(enabled)' 상태로 고정됩니다. 이 기능을 통해 kubelet의 HTTPS API에 대해 보다 정밀한 최소 권한(least-privilege) 제어가 가능해졌으며, 일반적인 모니터링 및 관찰성(observability) 용도로 과도하게 넓은 권한이었던 nodes/proxy 권한을 부여할 필요가 없어졌습니다.
배경: nodes/proxy 문제
kubelet은 포드(pod) 목록, 노드 메트릭, 컨테이너 로그, 그리고 결정적으로 실행 중인 컨테이너 내부에서 명령을 실행할 수 있는 기능 등 다양한 민감도의 데이터를 제공하는 여러 API를 HTTPS 엔드포인트로 노출합니다.
이 기능이 도입되기 전, kubelet 권한 부여는 조대(coarse-grained) 모델을 사용했습니다. 웹훅(webhook) 권한 부여가 활성화되었을 때, 거의 모든 kubelet API 경로는 단일 nodes/proxy 하위 리소스(subresource)로 매핑되었습니다. 즉, kubelet에서 메트릭이나 상태 정보를 읽어야 하는 모든 워크로드는 nodes/proxy 권한이 필요했는데, 이 권한은 해당 노드에서 실행 중인 모든 컨테이너에 대해 임의의 명령을 실행할 수 있는 권한과 동일했습니다.
이것이 왜 문제인가요?
모니터링 에이전트, 로그 수집기 또는 상태 확인 도구에 nodes/proxy 권한을 부여하는 것은 최소 권한 원칙을 위반합니다. 만약 이러한 워크로드 중 하나라도 침해당한다면, 공격자는 해당 노드의 모든 컨테이너에서 명령을 실행할 수 있는 능력을 얻게 됩니다. nodes/proxy 권한은 사실상 노드 수준의 슈퍼유저(superuser) 권한이며, 이를 광범위하게 부여하는 것은 보안 사고 발생 시 피해 범위(blast radius)를 비약적으로 증가시킵니다.
이 문제는 커뮤니티 내에서 수년간 인지되어 왔으며(kubernetes/kubernetes#83465 참조), 이번 개선 사항인 KEP-2862를 추진하게 된 핵심 동기가 되었습니다.
nodes/proxy GET WebSocket RCE 위험성
상황은 처음 보는 것보다 훨씬 더 심각합니다. 보안 연구원들은 2026년 초에 모니터링 도구에 일상적으로 부여되는 최소한의 읽기 전용 권한인 nodes/proxy GET만으로도 접근 가능한 노드의 모든 포드에서 명령을 실행(RCE)하는 데 악용될 수 있음을 입증했습니다.
근본적인 원인은 WebSocket 연결 방식과 kubelet이 HTTP 메서드를 RBAC 동사(verb)에 매핑하는 방식 사이의 불일치에 있습니다. WebSocket 프로토콜(RFC 6455)은 초기 연결 핸드셰이크를 위해 HTTP GET 요청을 요구합니다. kubelet은 이 GET을 RBAC get 동사로 매핑하고, 이후에 이어지는 쓰기 작업에 대해 CREATE 권한이 있는지는 별도로 확인하지 않은 채 요청을 승인합니다. 공격자는 websocat과 같은 WebSocket 클라이언트를 사용하여 10250 포트의 kubelet /exec 엔드포인트에 직접 접속하여 임의의 명령을 실행할 수 있습니다.
websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol v4.channel.k8s.io \
"wss://$NODE_IP:10250/exec/default/nginx/nginx?output=1&error=1&command=id"
uid=0(root) gid=0(root) groups=0(root)
세분화된 kubelet 권한 부여: 작동 원리
KubeletFineGrainedAuthz를 통해 kubelet은 이제 기존의 nodes/proxy 하위 리소스로 넘어가기 전에 추가적인 정밀 권한 체크를 수행합니다. 자주 사용되는 여러 kubelet API 경로가 전용 하위 리소스로 매핑되었습니다.
kubelet API |
리소스 (Resource) | 하위 리소스 (Subresource) |
|---|---|---|
/stats/* |
nodes | stats |
/metrics/* |
nodes | metrics |
/logs/* |
nodes | log |
/pods |
nodes | pods, proxy |
/runningPods/ |
nodes | pods, proxy |
/healthz |
nodes | healthz, proxy |
/configz |
nodes | configz, proxy |
/spec/* |
nodes | spec |
/checkpoint/* |
nodes | checkpoint |
| 기타 모든 것 | nodes | proxy |
이제 독자적인 하위 리소스가 생긴 엔드포인트(/pods, /runningPods/, /healthz, /configz)에 대해, kubelet은 먼저 해당 하위 리소스에 대한 SubjectAccessReview를 보냅니다. 해당 확인이 성공하면 요청이 승인됩니다. 만약 실패하면 하위 호환성을 위해 조대 권한인 nodes/proxy 하위 리소스로 재시도합니다.
이러한 이중 확인 방식은 부드러운 전환 경로를 보장합니다. nodes/proxy 권한을 가진 기존 워크로드는 계속 작동하며, 새로운 배포는 첫날부터 최소 권한 원칙을 적용할 수 있습니다.
실제 적용 예시
kubelet에서 /metrics를 긁어와야 하는 Prometheus 노드 익스포터(node exporter)나 모니터링용 DaemonSet을 가정해 봅시다. 이전에는 다음과 같은 RBAC ClusterRole이 필요했습니다.
# 기존 방식: 과도하게 넓은 권한
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-agent
rules:
- apiGroups: [""]
resources: ["nodes/proxy"]
verbs: ["get"]
이는 모니터링 에이전트에 필요 이상의 접근 권한을 부여합니다. 세분화된 권한 부여를 사용하면 이제 다음과 같이 정밀하게 범위를 좁힐 수 있습니다.
# 새로운 방식: 최소 권한 적용
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-agent
rules:
- apiGroups: [""]
resources: ["nodes/metrics", "nodes/stats"]
verbs: ["get"]
이제 모니터링 에이전트는 컨테이너에서 명령을 실행할 권한 없이도 kubelet에서 메트릭과 통계 정보를 읽을 수 있습니다.
system:kubelet-api-admin ClusterRole 업데이트
RBAC 권한 부여가 활성화되면, 기본 제공되는 system:kubelet-api-admin ClusterRole이 모든 새로운 세분화된 하위 리소스에 대한 권한을 포함하도록 자동으로 업데이트됩니다. 이를 통해 API 서버의 kubelet 클라이언트를 포함하여 이미 이 역할을 사용 중인 클러스터 관리자는 수동 설정 변경 없이도 전체 액세스 권한을 유지할 수 있습니다.
해당 역할은 이제 다음 권한들을 포함합니다:
nodes/proxynodes/statsnodes/metricsnodes/lognodes/specnodes/checkpointnodes/configznodes/healthznodes/pods
업그레이드 시 고려 사항
kubelet이 이중 권한 확인(세분화된 권한 확인 후 nodes/proxy로 대체)을 수행하므로, v1.36으로의 업그레이드는 대부분의 클러스터에서 원활하게 이루어집니다.
- 기존 워크로드:
nodes/proxy권한을 가진 워크로드는 변경 없이 계속 작동합니다.nodes/proxy로의 폴백(fallback) 기능이 하위 호환성을 보장합니다. - API 서버:
system:kubelet-api-admin을 통해 항상nodes/proxy권한을 가지므로,kube-apiserver와kubelet간의 통신은 기능 게이트 상태에 관계없이 영향을 받지 않습니다. - 버전 혼합 클러스터: 유연하게 처리됩니다.
kubelet은 세분화된 권한 부여를 지원하지만 API 서버가 지원하지 않거나 그 반대인 경우에도nodes/proxy권한이 폴백 역할을 합니다.
기능 활성화 여부 확인
kubelet 메트릭 엔드포인트를 확인하여 특정 노드에서 기능이 활성화되었는지 확인할 수 있습니다. 10250 포트의 메트릭 엔드포인트는 권한 부여가 필요하므로, 먼저 요청을 보내는 포드나 ServiceAccount에 적절한 RBAC 바인딩을 생성해야 합니다.
1단계: ServiceAccount 및 ClusterRole 생성
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubelet-metrics-checker
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubelet-metrics-reader
rules:
- apiGroups: [""]
resources: ["nodes/metrics"]
verbs: ["get"]
2단계: ClusterRole을 ServiceAccount에 바인딩
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubelet-metrics-checker
subjects:
- kind: ServiceAccount
name: kubelet-metrics-checker
namespace: default
roleRef:
kind: ClusterRole
name: kubelet-metrics-reader
apiGroup: rbac.authorization.k8s.io
두 매니페스트를 적용합니다:
kubectl apply -f serviceaccount.yaml
kubectl apply -f clusterrole.yaml
kubectl apply -f clusterrolebinding.yaml
3단계: ServiceAccount를 사용하는 포드를 실행하고 기능 플래그 확인
kubectl run kubelet-check \
--image=curlimages/curl \
--serviceaccount=kubelet-metrics-checker \
--restart=Never \
--rm -it \
-- sh
그런 다음 포드 내부에서 노드 IP를 가져와 메트릭 엔드포인트를 쿼리합니다.
# 토큰 가져오기
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# kubelet 메트릭을 쿼리하고 기능 게이트 필터링
curl -sk \
--header "Authorization: Bearer $TOKEN" \
https://$NODE_IP:10250/metrics \
| grep kubernetes_feature_enabled \
| grep KubeletFineGrainedAuthz
기능이 활성화되어 있다면 다음과 같은 출력이 나타납니다:
kubernetes_feature_enabled{name="KubeletFineGrainedAuthz",stage="GA"} 1
참고:
$NODE_IP를 확인하려는 노드의 IP 주소로 바꾸세요. 노드 IP는kubectl get nodes -o wide로 확인할 수 있습니다.
알파에서 GA까지의 여정
| 릴리스 | 단계 | 세부 내용 |
|---|---|---|
| v1.32 | Alpha | 기능 게이트 KubeletFineGrainedAuthz 도입, 기본 비활성화 |
| v1.33 | Beta | 기본 활성화; /pods, /runningPods/, /healthz, /configz에 대한 세분화된 체크 도입 |
| v1.36 | GA | 기능 게이트가 활성화로 고정됨; 세분화된 kubelet 권한 부여가 항상 활성화됨 |
향후 전망
세분화된 kubelet 권한 부여가 GA 단계에 도달함에 따라, Kubernetes 커뮤니티는 모니터링 및 관찰성 워크로드에 nodes/proxy 대신 특정 하위 리소스를 사용할 것을 권장하고 결국 강제하기 시작할 것입니다. 이러한 전환의 시급함은 nodes/proxy GET이 WebSocket 프로토콜을 통한 기록되지 않는 원격 코드 실행(RCE)에 악용될 수 있다는 연구 결과로 인해 더욱 강조됩니다. 이 위험은 널리 배포된 수십 개의 Helm 차트의 기본 RBAC 설정에 존재합니다. 시간이 지남에 따라 다음과 같은 변화를 기대할 수 있습니다.
- 생태계 채택: Prometheus, Datadog 에이전트 및 기타
DaemonSets와 같은 모니터링 도구는 기본 RBAC 설정을nodes/proxy대신nodes/metrics,nodes/stats,nodes/pods를 사용하도록 업데이트할 수 있습니다. 이는 해당 워크로드에 대한 WebSocket RCE 공격 표면을 직접적으로 제거합니다. - 정책 강제: Admission 컨트롤러 및 정책 엔진은 세분화된 대안이 존재함에도
nodes/proxy권한을 부여하는 RBAC 바인딩을 감지하거나 거부하여, 조직이 규모에 맞게 최소 권한 액세스를 채택하도록 도울 수 있습니다. - 지원 중단 경로: 채택률이 높아짐에 따라
nodes/proxy는 모니터링 용도로의 사용이 점차 중단(deprecated)될 수 있으며, 이는 Kubernetes 클러스터의 공격 표면을 더욱 줄여줄 것입니다.
참여하기
이 개선 사항은 SIG Auth 및 SIG Node에 의해 추진되었습니다. Kubernetes의 보안 및 권한 부여 기능에 기여하고 싶다면 저희와 함께해 주세요.
- SIG Auth
- SIG Node
- Slack:
#sig-auth및#sig-node - KEP-2862: Fine-Grained Kubelet API Authorization
이 기능에 대한 여러분의 의견과 경험을 기다리겠습니다!