LabHub
배우기 러닝패스 코스

KCSA — 쿠버네티스 보안 어소시에이트 · 구성요소 보안 — 스케줄러, kube-proxy, 런타임, CNI, 스토리지, 클라이언트 · 이론

스케줄러, kube-proxy, 컨테이너 런타임 — 조용한 구성요소의 자격 증명

LabHub 에서 이어서 보기

한 줄 요약

kube-scheduler 와 kube-proxy 는 저마다 API 서버에 대한 클라이언트 인증서를 쥐고 있고, 자기 HTTPS 포트를 열어 둡니다. 컨테이너 런타임은 유닉스 소켓 하나로 노드의 모든 컨테이너를 만들고 지웁니다. 세 구성요소는 감사 로그에 잘 잡히지 않는 자리에 있으므로, 무엇을 쥐고 있고 어디를 열어 두는지를 알고 그 문을 좁혀야 합니다.

왜 이게 필요했나

KCSA 의 클러스터 구성요소 보안 도메인은 API 서버·etcd·kubelet 만 묻지 않습니다. 공격자는 가장 잘 지켜지는 문을 두드리지 않습니다. 스케줄러의 kubeconfig, kube-proxy 의 메트릭 포트, 런타임 소켓처럼 관리자가 잘 들여다보지 않는 자격 증명과 포트를 찾습니다. [API 서버 우회 위험](https://kubernetes.io/docs/concepts/security/api-server-bypass-risks/) 문서가 바로 이 관점에서 쓰였습니다 — API 서버를 거치지 않고 클러스터를 바꿀 수 있는 길은 어드미션 컨트롤도 감사 로그도 지나지 않습니다.

어떻게 동작하나

kube-scheduler — 바인딩 권한을 쥔 컨트롤 플레인 프로세스

[kube-scheduler 참조](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/) 문서에 따르면 스케줄러는 스케줄링 큐의 파드마다 제약과 가용 자원에 맞는 노드를 고르고 순위를 매겨 바인딩하는 컨트롤 플레인 프로세스입니다. 이 바인딩 권한이 곧 위협 모델입니다. 스케줄러의 자격 증명(kubeadm 에서는 /etc/kubernetes/scheduler.conf 의 클라이언트 인증서)을 얻은 공격자는 파드를 원하는 노드에 올릴 수 있고, 민감한 워크로드와 같은 노드에 자기 파드를 붙일 수 있습니다.

스케줄러는 자기 HTTPS 포트도 엽니다. [포트와 프로토콜](https://kubernetes.io/docs/reference/networking/ports-and-protocols/) 표에서 10259 입니다. 이 포트의 인증·인가는 두 플래그가 정합니다. --authentication-kubeconfig 는 TokenReview 를 만들 수 있는 kubeconfig 를 가리키는데, 비워 두면 모든 토큰 요청이 익명으로 취급됩니다. --authorization-kubeconfig 는 SubjectAccessReview 를 만들 수 있는 kubeconfig 를 가리키며, 비워 두면 인가를 건너뛰지 않는 요청은 전부 거부됩니다. 즉 두 플래그를 API 서버에 위임하도록 두어야 메트릭·상태 엔드포인트가 아무나 읽는 문이 되지 않습니다. --leader-elect 는 기본이 true 라 복제된 스케줄러 중 하나만 동작합니다.

스케줄러를 우회하는 길도 알아야 합니다. [노드 비우기](https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/) 문서는 파드의 nodeName 을 직접 지정하면 스케줄러를 거치지 않고 그 노드에 묶인다고 적습니다. 파드를 만들 권한이 있는 사용자는 스케줄러의 정책과 무관하게 노드를 고를 수 있으므로, 민감한 노드 격리는 스케줄러 설정이 아니라 어드미션(PodNodeSelector 등)과 taint 로 강제해야 합니다.

kube-proxy — 노드마다 도는 네트워크 프록시

[kube-proxy 참조](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/) 문서는 kube-proxy 가 각 노드에서 돌며 API 의 Service 정의를 노드에 반영해 TCP·UDP·SCTP 를 백엔드로 전달한다고 설명합니다. 리눅스의 프록시 모드는 iptables(기본), ipvs, nftables 입니다. kube-proxy 는 Service 를 구현하는 것이지 NetworkPolicy 를 집행하는 것이 아닙니다. 정책 집행은 다음 읽기에서 다룰 CNI 의 몫입니다.

보안에서 볼 것은 두 가지입니다. 첫째, 자격 증명입니다. [PKI 인증서와 요구사항](https://kubernetes.io/docs/setup/best-practices/certificates/) 문서는 kube-proxy 가 API 서버에 인증하기 위한 클라이언트 인증서가 노드마다 있다고 적습니다. 노드에 놓인 이 kubeconfig 를 읽을 수 있는 프로세스는 kube-proxy 의 권한으로 API 서버에 말할 수 있습니다. 둘째, 열어 둔 포트입니다. --healthz-bind-address 의 기본값은 0.0.0.0:10256 이라 모든 인터페이스에서 상태를 받지만, --metrics-bind-address 의 기본값은 127.0.0.1:10249로컬에서만 메트릭을 냅니다. 모니터링을 위해 이것을 0.0.0.0:10249 로 넓히면 노드 네트워크의 누구나 kube-proxy 의 내부 상태를 읽습니다. 기본값이 왜 로컬인지 알고 넓혀야 합니다.

컨테이너 런타임 — 소켓 하나가 노드 전체다

[CRI](https://kubernetes.io/docs/concepts/architecture/cri/) 문서에 따르면 kubelet 은 gRPC 클라이언트로 런타임에 연결하며, 엔드포인트는 유닉스 소켓입니다(containerd 는 /var/run/containerd/containerd.sock, CRI-O 는 /var/run/crio/crio.sock). [API 서버 우회 위험](https://kubernetes.io/docs/concepts/security/api-server-bypass-risks/) 문서의 "컨테이너 런타임 소켓" 절이 위협을 적습니다 — 이 소켓에 접근하는 공격자는 새 컨테이너를 띄우거나 돌고 있는 컨테이너와 상호작용할 수 있고, 그 노드의 컨테이너가 Secret 을 가지고 있다면 다른 노드나 컨트롤 플레인으로 권한을 넓힐 수 있습니다.

문서가 제시하는 완화책은 파일 시스템 수준입니다. 소켓에 대한 파일 접근을 root 로 제한하고, kubelet 을 커널 네임스페이스로 다른 구성요소와 격리하며, 런타임 소켓을 포함하는 hostPath 마운트를 직접이든 상위 디렉터리로든 금지하고, hostPath 는 읽기 전용으로 두어 디렉터리 제한을 우회하지 못하게 하며, 노드에 대한 사용자 접근 자체를 제한합니다. 같은 문서의 "static Pod" 절도 런타임과 이어집니다 — kubelet 이 매니페스트 디렉터리에서 직접 띄우는 static Pod 는 API 서버가 관리하지 않으므로, 그 디렉터리에 쓸 수 있는 공격자는 어드미션을 거치지 않고 hostPath 를 쓰는 파드를 노드에 올릴 수 있습니다. static Pod 가 어드미션에 실패하면 kubelet 은 API 서버에 등록하지 않지만 파드는 노드에서 그대로 돕니다.

런타임 격리는 반대 방향의 도구입니다. [RuntimeClass](https://kubernetes.io/docs/concepts/containers/runtime-class/) 문서는 파드마다 다른 런타임 구성을 골라 성능과 보안 사이의 균형을 잡을 수 있다고 설명합니다. 높은 수준의 정보 보안 보증이 필요한 워크로드는 하드웨어 가상화를 쓰는 런타임으로 돌려 추가 격리를 얻되 오버헤드를 감수하는 식입니다. 노드의 CRI 구현에 핸들러를 설정하고 RuntimeClass 오브젝트를 만들면, 파드 스펙의 runtimeClassName 으로 고릅니다.

현장에서 만나는 모습

메트릭 수집을 위해 kube-proxy 를 0.0.0.0 으로 열었다. Prometheus 가 노드 밖에서 10249 를 긁어야 한다며 metricsBindAddress 를 넓힌 뒤, 노드 네트워크의 어느 파드든 hostNetwork 없이도 노드 IP 의 10249 에 닿게 된 경우입니다. 넓히려면 NetworkPolicy 나 방화벽으로 수집기만 허용하는 규칙이 함께 가야 합니다.

CI 러너가 런타임 소켓을 마운트한다. 이미지를 빌드하겠다며 /var/run/containerd/containerd.sock 을 hostPath 로 넣은 파드는 노드의 모든 컨테이너를 다룰 수 있는 파드입니다. 문서가 정확히 이 마운트를 금지하라고 하는 이유이고, Pod Security Standards 의 baseline 이 hostPath 를 막는 이유이기도 합니다.

다음 이론에서 볼 것

다음 읽기에서는 경계에 있는 세 구성요소를 봅니다 — NetworkPolicy 를 실제로 집행하는 CNI, hostPath 와 CSI 자격 증명이 얽힌 스토리지, 그리고 kubeconfig 와 exec 플러그인이 노출하는 클라이언트 쪽 위험입니다.