EKS 컨테이너 및 네트워크 차원에서의 보안 기본개념

2021-11-20

.

Data_Engineering_TIL(20211121)

[학습자료]

“클라우드 네이티브를 위한 쿠버네티스 실전 프로젝트” 책을 읽고 정리한 내용입니다.

** 동양북스, 아이자와 고지&사토 가즈히코 지음, 박상욱 옮김

참고자료 URL : https://github.com/dybooksIT/k8s-aws-book

“클러스터 내의 노드 차원에서 EKS 보안 기본개념”에 이어서 공부한 내용을 정리한 내용임

** URL : https://minman2115.github.io/DE_TIL308

[학습내용]

  1. 컨테이너 차원에서 보안

파드는 근본적으로 컨테이너이기 때문에 쿠버네티스 기능으로 커버할 수 있는 부분이 한계가 있다. 결론적으로 컨테이너 차원에의 보안은 라이프사이클에 맞춰 수행해야 한다. 컨테이너 이미지를 생성할때는 취약성을 점검하고, 컨테이너를 동작시킬때는 동작을 감시해줘야 한다.

1) 컨테이너 이미지의 취약점 점검하기

컨테이너는 application 실행에 필요한 각종 미들웨어나 프레임워크를 설치한 상태에서 이미지를 만들어두고 필요할때 동작시킨다. 도커 파일을 작성할때 명확하게 관리한다고 해도 컨테이너 내부의 소프트웨어 버전 등을 의식하며 관리하기는 힘들다. 그래서 컨테이너 이미지를 스캔하여 문제가 있는 소프트웨어 버전이 포함되지 않았는지 확인한다. application 배포 파이프라인에 포함시켜 컨테이너 이미지를 생성할때 자동으로 확인하게 함으로써 취약한 컨테이너가 배포되는 것을 방지할 수 있다.

2) 컨테이너 동작감시

컨테이너가 동작중인 경우, 동작감시로 실제 컨테이너에 문제되는 동작이 실행되는지 확인한다. 예를 들어 사용하지 않는 bash 명령이 실행되거나 이미 아는 피싱 사이트 등에 많은 트래픽이 발생하는지 등 수상한 동작이 발생할 경우 조치를 하는 것이다.

일반적으로는 이런 컨테이너 차원에서의 보안은 난이도가 높기 때문에 솔루션을 사용한다. 컨테이너 이미지 스캔은 trivy나 microscanner가 오픈소스로 제공되고 있다. 동작을 감시한다면 Falco 등을 활용할 수 있다. 만약에 둘다필요 한다면 sysdig나 aqua 같은 상용 제품이 좋다.

  1. 네트워크 차원에서 보안

네트워크 차원에서의 보안은 크게 두가지를 고려하면 된다.

1) 클러스터 외부에서 클러스터 내부로 들어오는 통신에 관한 보안

1-1) 외부에서 접근이 가능한 엔드포인트 IP 주소 제한

기본적으로 EKS 클러스터 엔드포인트는 인터넷에 공개된다. IAM으로 인증하는 장치가 있기 때문에 큰 문제는 없지만 어쨌든 잠재적으로는 취약요소가 될 수 있다. EKS에서는 엔드포인트 IP 주소를 제한하는 기능이 있으며 AWS에서 보안그룹과 유사한 기능이라고 생각하면 된다.

1

1-2) kubectl을 VPC 내부로 제한하는 private endpoint 적용

EKS에서는 private endpoint를 제공하기 때문에 완전히 VPC 내부에서만 접속할 수 있는 클러스터를 만들 수도 있다.

2

2) 클러스터 내부 통신에 관한 보안 - 네트워크 정책을 사용한 클러스터 내부 통신 제어

파드에는 VPC 내부의 IP 주소가 할당되어 클러스터 외부와 투명하게 통신할 수 있지만 어떤 노드에 어떤 IP 주소가 할당되는지는 제어할 수 없다. 또 기본적으로 모든 노드에는 공통 보안 그룹이 설정되어 있어서 클러스터 내부통신을 보안그룹으로 제어하기는 어렵다. 클러스터 내부 서비스 사이의 통신 제어는 Network Policy라는 클러스터 내부 구조를 이용해 설정한다. 네트워크 정책은 쿠버네티스 내의 보안그룹과 같은 개념으로 클러스터 내부 통신 제어가 가능하다. 예를 들어서 서비스 A, 서비스 B, 서비스 C가 있을 경우 서비스 A는 서비스 B에서만 접속을 허가하고 서비스 C에서의 접속은 허가하지 않는다라고 제어가 가능하다.

3

위에 아키텍처 그림을 실제 구현해보자

# 매니패스트 파일을 예로들면 아래와 같다.
[ec2-user@ip-10-10-1-125 k8s-aws-book]$ cat security/network-policy-all-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector:
    # 모든 파드에 적용
    matchLabels: {}

# 서비스 B에서 http 요청만 허가하는 네트워크 정책을 생성하는 예시다. .spec.podSelector.matchLabels에서 어떤 파드에 네트워크 정책을 적용할지 설정한다. 이 예시에서는 app: ServiceA에 적용하고 있다. .spec.ingress에는 전달받은 통신의 허가 조건을 설정한다. 조건에는 레이블을 설정할 수 있으므로 app: ServiceB라는 레이블이 부여된 파드에서만 접속을 허가한다. 같은 방법으로 서비스 B의 네트워크 정책을 생성하여 적용하면 위에 그림과 같이 구현할 수 있다.
[ec2-user@ip-10-10-1-125 k8s-aws-book]$ cat security/network-policy-allow-http-from-serviceB2ServiceA.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: network-policy-for-servicea
  # 적용대상 네임스페이스 지정
spec:
  podSelector:
    matchLabels:
      # 적용대상 파드 레이블 지정
      app: ServiceA
  policyTypes:
  - Ingress
  # 수신 규칙 설정
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: ServiceB
    ports:
    - protocol: TCP
      port: 80

[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl apply -f security/network-policy-all-deny.yaml
networkpolicy.networking.k8s.io/default-deny created

[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl apply -f security/network-policy-allow-http-from-serviceB2ServiceA.yaml
networkpolicy.networking.k8s.io/network-policy-for-servicea created

[ec2-user@ip-10-10-1-125 k8s-aws-book]$ cat security/network-policy-sample-serviceA.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: servicea
  labels:
    app: ServiceA
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ServiceA
  template:
    metadata:
      labels:
        app: ServiceA
    spec:
      containers:
      - image: nginx:latest
        name: servicea
        ports:
          - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: servicea
  labels:
    app: ServiceA
spec:
  type: ClusterIP
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 80
      targetPort: 80
  selector:
    app: ServiceA

[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl apply -f security/network-policy-sample-serviceA.yaml
deployment.apps/servicea created
service/servicea created

# 아래와 같이 calico.yaml을 다운로드 받을 수 있는데 security 폴더에 이미 해당 파일이 있으니 다운로드 받을 필요는 없다.
#[ec2-user@ip-10-10-1-125 k8s-aws-book]$ curl -O https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/master/config/v1.7/calico.yaml
#  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
#                                 Dload  Upload   Total   Spent    Left  Speed
#100  217k  100  217k    0     0   496k      0 --:--:-- --:--:-- --:--:--  496k
# calico.yaml 상세내용은 첨부파일 참고할 것

# 칼리코를 설치하는 중간에 에러가 발생할수도 있는데 이는 칼리코를 설치할때 서로 참조하는 리소스가 아직 동작하지 않아서 발생하는 문제이다. 따라서 1분정도 텀을주고 다시 설치명령을 실행하면 정상적으로 모두 설치될 것이다.
[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl apply -f security/calico.yaml
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/installations.operator.tigera.io created
customresourcedefinition.apiextensions.k8s.io/tigerastatuses.operator.tigera.io created
namespace/tigera-operator created
podsecuritypolicy.policy/tigera-operator created
serviceaccount/tigera-operator created
clusterrole.rbac.authorization.k8s.io/tigera-operator created
clusterrolebinding.rbac.authorization.k8s.io/tigera-operator created
deployment.apps/tigera-operator created
error: unable to recognize "calico.yaml": no matches for kind "Installation" in version "operator.tigera.io/v1"

[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl apply -f security/calico.yaml
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/installations.operator.tigera.io unchanged
customresourcedefinition.apiextensions.k8s.io/tigerastatuses.operator.tigera.io unchanged
namespace/tigera-operator unchanged
podsecuritypolicy.policy/tigera-operator configured
serviceaccount/tigera-operator unchanged
clusterrole.rbac.authorization.k8s.io/tigera-operator unchanged
clusterrolebinding.rbac.authorization.k8s.io/tigera-operator unchanged
deployment.apps/tigera-operator configured
installation.operator.tigera.io/default created

[ec2-user@ip-10-10-1-125 ~]$ kubectl get all
NAME                               READY   STATUS    RESTARTS   AGE
pod/backend-app-8478f8cd55-k44s7   1/1     Running   0          20h
pod/backend-app-8478f8cd55-z4zth   1/1     Running   0          20h
pod/servicea-55fc99f658-295m6      1/1     Running   0          7m19s
pod/servicea-55fc99f658-dt2kq      1/1     Running   0          7m19s
pod/serviceb                       1/1     Running   0          6m28s

NAME                          TYPE           CLUSTER-IP       EXTERNAL-IP                                                                   PORT(S)          AGE
service/backend-app-service   LoadBalancer   10.100.0.84      xxxxxxxxx-yyyyyyy.ap-northeast-2.elb.amazonaws.com   8080:30363/TCP   20h
service/kubernetes            ClusterIP      10.100.0.1       <none>                                                                        443/TCP          21h
service/servicea              ClusterIP      10.100.167.108   <none>                                                                        80/TCP           7m19s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/backend-app   2/2     2            2           20h
deployment.apps/servicea      2/2     2            2           7m19s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/backend-app-8478f8cd55   2         2         2       20h
replicaset.apps/servicea-55fc99f658      2         2         2       7m19s

[ec2-user@ip-10-10-1-125 ~]$ kubectl describe services servicea
Name:              servicea
Namespace:         default
Labels:            app=ServiceA
Annotations:       <none>
Selector:          app=ServiceA
Type:              ClusterIP
IP Families:       <none>
IP:                10.100.167.108
IPs:               <none>
Port:              http-port  80/TCP
TargetPort:        80/TCP
Endpoints:         192.168.0.9:80,192.168.1.11:80
Session Affinity:  None
Events:            <none>

# 위에 아이피주소(10.100.167.108) 복사

[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl run -it serviceb --image=busybox --labels="app=ServiceB" --rm -- sh
If you don't see a command prompt, try pressing enter.
/ # wget -q -O - http://10.100.167.108
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

/ # exit
Session ended, resume using 'kubectl attach serviceb -c serviceb -i -t' command when the pod is running
pod "serviceb" deleted

# 이번에는 서비스 C 파드를 동작시켜 서비스 A로 HTTP 요청을 보내면 응답이 없는 것을 알 수 있다. 우리가 예상한대로 서비스 B 이외에 요청은 거부되는 것을 확인할 수 있다.
[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl run -it servicec --image=busybox --labels="app=ServiceC" --rm -- sh
If you don't see a command prompt, try pressing enter.
/ # wget -q -O - http://10.100.167.108
# 잠시 기다려도 아무 응답이 없는데 이는 네트워크 정책으로 거부된 것이다. ctrl + c로 중지해주고 exit로 나가주면 된다.
^C
/ # exit
Session ended, resume using 'kubectl attach servicec -c servicec -i -t' command when the pod is running
pod "servicec" deleted

# 실습종료후 리소스 삭제
[ec2-user@ip-10-10-1-125 k8s-aws-book]$ ll
total 20
drwxrwxr-x  2 ec2-user ec2-user    98 Nov 20 04:53 autoscaling
drwxrwxr-x  7 ec2-user ec2-user   194 Nov 20 05:01 backend-app
drwxrwxr-x  5 ec2-user ec2-user   170 Nov 20 04:53 batch-app
drwxrwxr-x  4 ec2-user ec2-user    49 Nov 20 04:53 cicd
drwxrwxr-x  2 ec2-user ec2-user    85 Nov 20 04:53 column-deployment-update
drwxrwxr-x  2 ec2-user ec2-user    60 Nov 20 04:53 column-loadbalancer-https
drwxrwxr-x  2 ec2-user ec2-user    73 Nov 20 04:53 db-docker-compose
drwxrwxr-x  3 ec2-user ec2-user  4096 Nov 20 04:53 eks-env
drwxrwxr-x 10 ec2-user ec2-user   243 Nov 20 04:53 frontend-app
-rw-rw-r--  1 ec2-user ec2-user 11357 Nov 20 04:53 LICENSE
drwxrwxr-x  3 ec2-user ec2-user    37 Nov 20 04:53 readme
-rw-rw-r--  1 ec2-user ec2-user  3103 Nov 20 04:53 README.md
drwxrwxr-x  4 ec2-user ec2-user    75 Nov 20 05:01 sample-app-common
drwxrwxr-x  2 ec2-user ec2-user   181 Nov 20 12:36 security

[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl delete -f security
customresourcedefinition.apiextensions.k8s.io "bgpconfigurations.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "bgppeers.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "blockaffinities.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "clusterinformations.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "felixconfigurations.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "globalnetworkpolicies.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "globalnetworksets.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "hostendpoints.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "ipamblocks.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "ipamconfigs.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "ipamhandles.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "ippools.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "kubecontrollersconfigurations.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "networkpolicies.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "networksets.crd.projectcalico.org" deleted
customresourcedefinition.apiextensions.k8s.io "installations.operator.tigera.io" deleted
customresourcedefinition.apiextensions.k8s.io "tigerastatuses.operator.tigera.io" deleted
namespace "tigera-operator" deleted
podsecuritypolicy.policy "tigera-operator" deleted
serviceaccount "tigera-operator" deleted
clusterrole.rbac.authorization.k8s.io "tigera-operator" deleted
clusterrolebinding.rbac.authorization.k8s.io "tigera-operator" deleted
deployment.apps "tigera-operator" deleted
networkpolicy.networking.k8s.io "default-deny" deleted
networkpolicy.networking.k8s.io "network-policy-for-servicea" deleted
deployment.apps "servicea" deleted
service "servicea" deleted
Error from server (NotFound): error when deleting "security/calico.yml": the server could not find the requested resource (delete installations.operator.tigera.io default)
Error from server (NotFound): error when deleting "security/rbac.yaml": namespaces "rbac-test-ns" not found
Error from server (NotFound): error when deleting "security/rbac.yaml": clusterroles.rbac.authorization.k8s.io "rbac-test-role" not found
Error from server (NotFound): error when deleting "security/rbac.yaml": rolebindings.rbac.authorization.k8s.io "rbac-test-role-binding" not found

[ec2-user@ip-10-10-1-125 k8s-aws-book]$ kubectl get all
NAME                               READY   STATUS    RESTARTS   AGE
pod/backend-app-8478f8cd55-k44s7   1/1     Running   0          21h
pod/backend-app-8478f8cd55-z4zth   1/1     Running   0          21h

NAME                          TYPE           CLUSTER-IP    EXTERNAL-IP                                                                   PORT(S)          AGE
service/backend-app-service   LoadBalancer   10.100.0.84   xxxxxxxx-yyyyyyyyy.ap-northeast-2.elb.amazonaws.com   8080:30363/TCP   21h
service/kubernetes            ClusterIP      10.100.0.1    <none>                                                                        443/TCP          22h

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/backend-app   2/2     2            2           21h

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/backend-app-8478f8cd55   2         2         2       21h