[CKS] 16. kubelet Security

Kubelet은 파드 생명주기 관리와 상태 보고를 담당합니다. 따라서 kubelet 보안 설정을 강화하여 안전한 클러스터 운영 방법을 확인합니다.

[CKS] 16. kubelet Security
Photo by Anton Shuvalov / Unsplash

개요

해당 문서에서는 kubelet을 다시 알아봅니다.

이를 통해 kubelet 구성을 강화하는 다양한 방법에 대해서도 정리하였습니다.

kubelet

우선 kubelet은 마스터 제어 시스템과 정기적으로 통신하고, 스케줄러의 지시에 따라 컨테이너를 적재 또는 하역하고, 지속적인 상태 보고서를 전송합니다. 이는 컨테이너의 생성, 시작, 중지 등 파드(Pod)의 생명주기와 kube-apiserver로 상태 보고를 수행하는 역할로 마스터,워커 모든 노드에 존재하여 작업을 수행합니다.

kubeadm이 나온 이후로 패키지 관리자 aptyum으로 kubeadm을 설치하면, 의존성 때문에 kubelet 바이너리도 함께 설치됩니다.

이후 kubeadm init이나 kubeadm join을 실행하면, kubeadm이 이 문서에서 설명한 kubelet.service 파일과 kubelet-config.yaml 설정 파일을 알아서 최적의 내용으로 생성하고 구성해줍니다.

하지만 kubeadm, kubelet, kubectl은 수동으로 설치가 필요합니다.

# kubelet-config.yaml file snippet
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
clusterDomain: cluster.local
fileCheckFrequency: 0s
healthzPort: 10248
clusterDNS:
  - 10.96.0.10
httpCheckFrequency: 0s
syncFrequency: 0s

실행 중인 Kubelet 프로세스를 검사하고 구성 설정을 보려면 다음과 같이 수행할 수 있습니다.

# 다양한 옵션들을 확인할 수 있습니다.
ps -aux | grep kubelet

Kubelet을 검사하라는 요청을 받으면 설정 파일 위치를 식별하고 그 파일의 내용을 탐색해야합니다.

# /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
clusterDNS:
  - 10.96.0.10
clusterDomain: cluster.local
cpuManagerReconcilePeriod: 0s
evictionPressureTransitionPeriod: 0s
fileCheckFrequency: 0s
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 0s
imageMinimumGCAge: 0s
nodeStatusReportFrequency: 0s
nodeStatusUpdateFrequency: 0s
rotateCertificates: true
runtimeRequestTimeout: 0s
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 0s

Kubelet - 보안

Kubelet이 kube-apiserver의 인증된 요청에만 응답하도록 하는 것은 클러스터 보안에 매우 중요합니다. 기본적으로 Kubelet은 두 개의 서로 다른 포트를 사용합니다.

  1. 포트 10250:
    1. 전체 API 액세스를 제공합니다.
  2. 포트 10255:
    1. 메트릭 및 시스템 데이터에 대한 읽기 전용 API를 제공합니다.

또한 API에는 익명 액세스가 허용됩니다.

curl -sk http://localhost:10250/pods

노드 시스템 로그를 확인하려면 추가 엔드포인트(예: 10250/logs/syslog)에 액세스할 수도 있습니다. Kubelet API는 노드 상태 확인, 메트릭, 포트 포워딩, 컨테이너에서의 명령 실행 등 다양한 기능을 제공합니다.

10255번 포트에서 실행되는 서비스는 인증되지 않은 읽기 전용 접근 권한을 제공합니다.

이는 네트워크 접근 권한이 있는 누구나 민감한 데이터를 볼 수 있다는 보안 위험을 초래할 수 있습니다.

kubelet 보안 강화

보안을 강화하기 위해선 kubelet에 대한 모든 요청은 처리되기 전 인증되고 승인(인가)되어야합니다.

1. 익명 인증 비활성화

기본적으로 이를 수행하기 위해서 YAML 파일 내에서 설정을 구성할 수 있습니다.

# kubelet-config.yaml snippet
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
  anonymous:
    enabled: false

따라서 우선 익명 인증을 비활성화하는 것이 가장 좋습니다. 비활성화한 후에는 지원되는 인증 메커니즘을 활성화해야 합니다.

2. 인증서 기반 인증 등록

토큰과 인증서 인증 방법 중 여기선 인증서 관련 방법을 알아보겠습니다.

# kubelet-config.yaml snippet
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
  x509:
    clientCAFile: /path/to/ca.crt

사용자가 API 호출(예: curl 사용)을 수행할 때 Kube-apiserver는 Kubelet 관점에서 클라이언트로 간주되므로 클라이언트 인증서와 키를 포함해야 합니다.

curl -sk https://localhost:10250/pods/ --key kubelet-key.pem --cert kubelet-cert.pem

주의해야할 점은 인증서 기반 인증이나 토큰 기반 인증 모두 요청을 명시적으로 거부하지 않으면 Kubelet은 해당 요청을 익명으로 처리합니다. 인증 메커니즘이 올바르게 구성되었는지 항상 확인하세요.

3. 승인

요청을 인증한 후 Kubelet은 사용자가 액세스할 수 있는 작업 또는 API 리소스를 결정합니다.

기본적으로 권한 부여 모드는 AlwaysAllow로 설정되어 있어 모든 요청이 허용됩니다. Kubelet을 보호하려면 권한 부여 모드를 Webhook으로 구성하여 Kubelet이 요청이 허용되어야 하는지 결정하기 위해 API 서버와 통신하도록 합니다

# kubelet-config.yaml snippet
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authorization:
  mode: Webhook

webhook으로 설정하게 된다면 Kubelet은 SubjectAccessReview(주체 접근 검토)라는 특별한 요청을 만들어 API 서버에게 보냅니다. 이 요청에는 다음과 같은 정보가 담겨있습니다.

  • 주체(User): admin
  • 행위(Verb): create (리소스 종류: pods/exec)
  • 대상(Resource): my-pod

이후 API 서버는 Kubelet으로부터 받은 SubjectAccessReview 요청을 보고, 클러스터에 설정된 모든 Role, ClusterRole, RoleBinding, ClusterRoleBinding (RBAC 규칙)을 뒤져봅니다. admin 사용자에게 해당 작업을 허용하는 규칙이 있는지 확인하고, 그 결과를 Kubelet에게 "허용됨(Allowed: true)" 또는 "거부됨(Allowed: false)"으로 응답해 줍니다.

4. 읽기 전용 포트(10255) 관리

읽기 전용 포트(10255)는 인증 없이 민감한 메트릭을 노출할 수 있습니다.

명시적으로 필요하지 않은 경우 이 포트를 비활성화하는 것이 좋습니다. 서비스 파일이나 구성 파일에서 포트 값을 0으로 설정하여 비활성화할 수 있습니다.

# kubelet-config.yaml snippet
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
readOnlyPort: 0

Hands-on

ps -aux | grep kubelet
sudo systemctl restart kubelet
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: true
  webhook:
    cacheTTL: 0s
    enabled: true
  x509:
    clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
  mode: Webhook
cgroupDriver: systemd
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
cpuManagerReconcilePeriod: 0s
evictionPressureTransitionPeriod: 0s
fileCheckFrequency: 0s
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 0s
imageMinimumGCAge: 0s
kind: KubeletConfiguration
logging: {}
nodeStatusReportFrequency: 0s
nodeStatusUpdateFrequency: 0s
resolvConf: /run/systemd/resolve/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 0s
shutdownGracePeriod: 0s
shutdownGracePeriodCriticalPods: 0s
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 0s
syncFrequency: 0s
volumeStatsAggPeriod: 0s
readOnlyPort: 10255
  • 인증에 실패 “Unauthorized”
    • 401 Unauthorized
  • 인가에 실패 “Forbidden”
    • 403 Forbidden