본문 바로가기
Orchestration/Kubernetes

AWS EKS Security Group 최소 규칙

by wlsdn3004 2023. 4. 11.
728x90
반응형

 

기업에서는 보안 강화를 위해 최소한의 규칙으로 운영함으로써 잠재적인 보안 위협을 줄이기 위해 노력한다.

이를 위해서는 꼭 필요한 인바운드/아웃바운드 규칙을 설정해야 하며, 이를 통해 높은 보안 수준을 유지할 수 있다.

이번 포스팅에서는 내가 경험했던 AWS EKS Security Group 최소 규칙으로 보안 그룹을 설정하는 과정을 기록해보고자 한다.

 

EKS 클러스터를 생성하면 Amazon EKS에서 eks-cluster-sg-{my-cluster-uniqueID}라는 보안 그룹을 만드는데, 이 보안 그룹에는 아래와 같이 기본 규칙이 설정되어 있어 모든 IP 주소에서 클러스터로의 모든 트래픽을 허용한다.

규칙 유형 프로토콜 포트 소스 대상 주소
인바운드 모두 모두 자체  
아웃바운드 모두 모두   0.0.0.0/0 (IPv4) or ::/0 (IPv6)

 

EKS 클러스터를 생성하면 3가지 보안그룹이 생성된다.

  • 클러스터 보안 그룹
    Control Plane만 사용하거나, 클러스터 노드 전체 사용하는 보안 그룹
  • 추가 보안 그룹
    Control Plane만 사용하는 보안 그룹
  • 워커노드 보안 그룹
    Worker Node만 사용하는 보안 그룹

 

AWS에서는 클러스터 간 통신 제한을 위해서는 아래와 같은 규칙을 클러스터 보안 그룹에 추가해야 한다고 설명한다.

규칙 유형 프로토콜 포트 대상 주소
아웃바운드 TCP 443 클러스터 보안 그룹
아웃바운드 TCP 10250 클러스터 보안 그룹
아웃바운드(DNS) TCP 및 UDP 53 클러스터 보안 그룹
  • 10250 : Kubelet 통신을 위한 포트
  • 443 : API Server HTTPS 트래픽 처리를 위한 포트
  • 53 : DNS 트래픽 처리를 위한 포트

 

아래와 같은 Private 환경으로 보안 그룹을 사용하는 구성일 때 최소 정책은 다음과 같다.

위 구성은 클러스터 보안 그룹은 사용하지 않고 Control Plane의 추가 보안 그룹과, 워커노드 보안 그룹만 사용한다.

 

EKS를 구성하여 위와 같은 보안 그룹 규칙을 사용하면 문제없이 동작할 것이다. 하지만 add-on이나 솔루션을 추가하게 되면 이에 맞는 보안 그룹 규칙이 추가로 필요할 수 있다. 

 

보안 그룹 규칙 추가 설정


각자 환경에서 추가로 설치한 서비스들 중 API Server와 상호 작용하며 동작하는 서비스를 확인해 보자.

아래 스크립트는 apiservices 목록 중 port 정보를 갖고 있는 apiservices의 이름과 port를 출력하는 스크립트이다.

$ for name in $(kubectl get apiservices.apiregistration.k8s.io  --no-headers  | awk {'print $1'})
do
  kubectl get apiservices $name -oyaml | grep -i port 1> /dev/null
  if [ $? == 0 ];
  then
   echo "Name: ${name}"; kubectl get apiservices $name -oyaml | grep -i port ; echo "==========================="
  fi
done

Name: v1alpha1.tap.linkerd.io
    port: 443
===========================
Name: v1beta1.metrics.k8s.io
    port: 443
===========================

쿠버네티스 메트릭 수집기인 metrics-server와 linkerd 솔루션의 시각화 도구인 linkerd-viz 서비스가 확인된다.

 

해당 서비스가 어떤 서비스와 통신하는지 확인해 보자.

## linkerd-viz
$ kubectl get apiservices.apiregistration.k8s.io v1alpha1.tap.linkerd.io -oyaml | grep -A3 service
  service:
    name: tap
    namespace: linkerd-viz
    port: 443

## linkerd-viz endpoint
$ kubectl get ep -n linkerd-viz tap
NAME   ENDPOINTS                             AGE
tap    192.216.38.81:8088,192.216.38.81:8089   11d

---

## metrics-server 
$ kubectl get apiservices.apiregistration.k8s.io v1beta1.metrics.k8s.io -oyaml | grep -A3 service
  service:
    name: metrics-server
    namespace: kube-system
    port: 443

## metrics-server endpoint
$ kubectl get ep -n kube-system metrics-server
NAME             ENDPOINTS           AGE
metrics-server   192.216.7.111:4443   116d

linkerd-viz 네임스페이스의 tap이라는 서비스와, kube-system 네임스페이스의 metrics-server 서비스가 확인된다.

즉, API Server는 tap, metrics-server 서비스의 endpoint로 연결되어 있는 Pod와 통신한다는 의미이다.

 

위 정보를 토대로 아래와 같이 규칙을 추가해야 한다.

[Worker Node inbound 규칙]

규칙 유형 프로토콜 포트 소스
인바운드 TCP 4443, 8089 Control Plane Security Group

 

[Control Plane outbound 규칙]

규칙 유형 프로토콜 포트 대상 주소
아웃바운드 TCP 4443, 8089 Worker Node Security Group
[참고]
EKS의 SecurityGroupPolicy를 사용하는 Pod는 해당 Pod로 향하는 트래픽은 Worker Node 보안 그룹에 영향받지 않고, SecurityGroupPolicy에 설정되어 있는 보안 그룹에 영향을 받는다. 즉, Worker Node 보안 그룹이 아닌 Pod에서 사용하는 SecurityGroupPolicy 보안 그룹에 규칙을 추가해줘야 한다.
  • EKS SecurityGroupPolicy는 추 후 포스팅에 다룰 예정이다.

 

실제로 해당되는 Pod가 존재하는 워커노드로 접근하여 패킷을 확인해 보자.

  • EKS API Server의 ip : 192.216.0.8
  • linkerd-viz tap pod ip : 192.216.38.81
  • metric-server : 192.216.7.111

 

linkerd-viz tap 패킷 덤프

## linkerd-viz tap pod
$ kubectl get po -o wide -n linkerd-viz | grep tap
$ ssh < worker node ip >

## 8089로 향하는 EKS API Server 패킷 확인
$ tcpdump -n host 192.216.0.8 | grep 8089
04:18:28.494976 IP 192.216.0.8.35182 > 192.216.38.81.8089: Flags [P.], seq 1457681171:1457681210, ack 3277733096, win 1358, options [nop,nop,TS val 20143793 ecr 3993719488], length 39
04:18:28.495030 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], ack 39, win 1672, options [nop,nop,TS val 3993734270 ecr 20143793], length 0
04:18:28.495038 IP 192.216.0.8.35182 > 192.216.38.81.8089: Flags [P.], seq 39:78, ack 1, win 1358, options [nop,nop,TS val 20143794 ecr 3993719488], length 39
04:18:28.495039 IP 192.216.0.8.35182 > 192.216.38.81.8089: Flags [P.], seq 78:156, ack 1, win 1358, options [nop,nop,TS val 20143794 ecr 3993719488], length 78
04:18:28.495071 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], ack 78, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 0
04:18:28.495083 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], ack 156, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 0
04:18:28.495128 IP 192.216.0.8.35182 > 192.216.38.81.8089: Flags [P.], seq 156:195, ack 1, win 1358, options [nop,nop,TS val 20143794 ecr 3993734270], length 39
04:18:28.495144 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], ack 195, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 0
04:18:28.495492 IP 192.216.0.8.35182 > 192.216.38.81.8089: Flags [P.], seq 195:234, ack 1, win 1358, options [nop,nop,TS val 20143794 ecr 3993734270], length 39
04:18:28.495495 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [P.], seq 1:111, ack 195, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 110
04:18:28.495526 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], seq 111:1559, ack 234, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 1448
04:18:28.495532 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], seq 1559:3007, ack 234, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 1448
04:18:28.495534 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [P.], seq 3007:4229, ack 234, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 1222
04:18:28.495573 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], seq 4229:5677, ack 234, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 1448
04:18:28.495577 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], seq 5677:7125, ack 234, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 1448
04:18:28.495579 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], seq 7125:8573, ack 234, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 1448
04:18:28.495580 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [.], seq 8573:10021, ack 234, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 1448
04:18:28.495582 IP 192.216.38.81.8089 > 192.216.0.8.35182: Flags [P.], seq 10021:11469, ack 234, win 1672, options [nop,nop,TS val 3993734270 ecr 20143794], length 1448

 

metric-server 패킷 덤프

## metric-server pod
$ kubectl get po -o wide | grep metrics-server
$ ssh < worker node ip >

## 4443으로 향하는 EKS API Server 패킷 확인
$ tcpdump -n host 192.216.0.8 | grep -i pharos
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
06:56:43.856856 IP 192.216.0.8.38074 > 192.216.7.111.pharos: Flags [.], ack 3245893736, win 11574, options [nop,nop,TS val 3324601036 ecr 434137040], length 0
06:56:43.856890 IP 192.216.7.111.pharos > 192.216.0.8.38074: Flags [.], ack 1, win 1672, options [nop,nop,TS val 434152172 ecr 3324585865], length 0
06:56:46.031366 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [P.], seq 381425109:381425311, ack 4044946985, win 2433, options [nop,nop,TS val 3324603211 ecr 434147474], length 202
06:56:46.031927 IP 192.216.7.111.pharos > 192.216.0.8.33036: Flags [P.], seq 1:88, ack 202, win 775, options [nop,nop,TS val 434154347 ecr 3324603211], length 87
06:56:46.031998 IP 192.216.7.111.pharos > 192.216.0.8.33036: Flags [P.], seq 88:411, ack 202, win 775, options [nop,nop,TS val 434154347 ecr 3324603211], length 323
06:56:46.032011 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [.], ack 88, win 2433, options [nop,nop,TS val 3324603212 ecr 434154347], length 0
06:56:46.032071 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [.], ack 411, win 2455, options [nop,nop,TS val 3324603212 ecr 434154347], length 0
06:56:46.620409 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [P.], seq 202:401, ack 411, win 2455, options [nop,nop,TS val 3324603800 ecr 434154347], length 199
06:56:46.623205 IP 192.216.7.111.pharos > 192.216.0.8.33036: Flags [P.], seq 411:476, ack 401, win 784, options [nop,nop,TS val 434154938 ecr 3324603800], length 65
06:56:46.623246 IP 192.216.7.111.pharos > 192.216.0.8.33036: Flags [P.], seq 476:596, ack 401, win 784, options [nop,nop,TS val 434154938 ecr 3324603800], length 120
06:56:46.623272 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [.], ack 476, win 2455, options [nop,nop,TS val 3324603803 ecr 434154938], length 0
06:56:46.623300 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [.], ack 596, win 2455, options [nop,nop,TS val 3324603803 ecr 434154938], length 0

 

위 metric-server 패킷 덤프에서 포트를 pharos로 필터 한 이유는 pharos 가 4443을 의미하기 때문이다.

해당 Name이 의미하는 포트 확인은 shell의 /etc/services 에서 확인할 수 있다.

  • /etc/services 파일은 리눅스 시스템에서 사용하는 표준 포트 번호와 대응하는 서비스 이름을 매핑한 파일이다.
## pharos 포트 확인
$ cat /etc/services | grep pharos
pharos          4443/tcp                # Pharos
pharos          4443/udp                # Pharos

## 4443 포트로 향하는 EKS API Server 확인
$ tcpdump -n host 192.216.0.8 and dst port 4443
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
06:58:24.247507 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [P.], seq 381436088:381436242, ack 4045054927, win 2585, options [nop,nop,TS val 3324701427 ecr 434251723], length 154
06:58:24.250515 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [.], ack 89, win 2585, options [nop,nop,TS val 3324701430 ecr 434252565], length 0
06:58:24.250550 IP 192.216.0.8.33036 > 192.216.7.111.pharos: Flags [.], ack 209, win 2585, options [nop,nop,TS val 3324701430 ecr 434252566], length 0

 

EKS 클러스터 Logging에서 API 서버 로깅을 활성화 한 뒤 CloudWatch의 Logs Insights에서도 확인할 수 있다.

EKS Cluster Logging
Log Insights

위 메세지는 API Server가 연결 요청을 보내고 연결 시도부터 응답받은 시간까지 걸린 시간을 보여준다.

timeout 응답을 받아 발생하기도 하니 응답받은 시간이 너무 길다면 확인해야 한다. 

 

아래 그림은 timeout으로 발생한 로그이다.

EKS kube-api log

 

마치며

기업에서 보안 강화를 위해 보안 그룹 최소 정책을 사용하려 할 때는 작업을 진행하기 전에는 반드시 현재 사용 중인 Pod들이 어떻게 통신하는지, 어떠한 통신이 필요한지를 정확히 파악해야 한다.
적절하지 않은 보안 그룹 규칙을 설정하면 보안 상의 취약점이 생길 수 있고, 기능 동작에 문제가 생길 수도 있으므로 이러한 취약점과 문제를 방지하기 위해 각별한 주의가 필요하다.

 

반응형

댓글