1
2
AWS EKS Workshop Study (=AEWS)는 EKS Workshop 실습 스터디입니다.
CloudNet@ Gasida(가시다)님이 진행하시며,공개된 AWS EKS Workshop을 기반으로 진행하고 있습니다.

지난 글에 이어서 AWS 서비스를 통해서 EKS 보안 구성을 한 내용들을 공유한다. 보안 구성 내용은 EKS 워크샵 내용을 참고하여 정리하였다. 보안 구성 내용은 다음과 같다.

  • EKS Secret 시크릿 키 관리 : Sealed Secrets + AWS KMS
  • EKS 이상 탐지 모니터링 : Amazon GuardDuty + 사고 대응
  • EKS 애플리케이션 트래픽 보호 : AWS WAF

EKS Secret Key 관리

참고 : EKS 워크샵, AWS 블로그

Kubernetes Secret은  워크로드의 암호, OAuth 토큰 및 ssh 키 등과 같은 민감한 정보의 배포를 관리하는 리소스이다. Secret은 파드에 볼륨으로 마운트되거나 컨테이너 이미지를 가져오는 데 사용되는 자격 증명으로 사용된다. 그러나 Secret은 기본적으로 Base64 인코딩되어 저장되지만, 이는 실제로 암호화를 제공하는 것은 아니다. 이말은 Git 레파지토리에 시크릿을 업로드하게 되면 그대로 민감 데이터가 노출된다는 뜻으로, 시크릿 키에 대한 추가 관리를 통해 레파지토리에 시크릿 키가 업로드 되도 문제가 없도록 구성해야 한다. 이를 해결하기 위해 본 장에서는 Sealed Secrets + AWS KMS 를 이용하여 쿠버네티스 워크로드의 시크릿 키를 관리하여 레파지토리에서도 시크릿 키를 관리할 수 있도록 보안 구성을 진행할 것이다.

Sealed Secrets 원리

Sealed Secrets는 Kubernetes에서 Secret을 안전하게 관리하기 위한 도구로 시크릿 키에 대해 암호화를 해주는 도구이다. 암호화 원리 이해를 위해 Sealed Secrets 구성을 먼저 확인하면 다음과 같다.

Sealed Secrets 두 가지 파트에서 시크릿 키에 대해 암호화 / 복호화를 하여 암호화된 키 관리를 구성한다.

  • A client-side utility (kubeseal ) : CLI 도구로 시크릿 키에 대해 암호화를 진행해준다. 암호화에 대한 공개 키 / 비밀 키는 Cluster-side controller에서 진행한다.
  • A cluster-side controller / operator : 쿠버네티스 클러스터에서 설치되며, 고유한 공개 키/비밀 키 쌍을 생성하여 키 관리를 진행한다. Kubeseal 에서 암호화한 시크릿 키에 대해 복호화를 해준다.

각 파트를 통해 시크릿 키를 관리하는 과정은 다음과 같다.

<a href="https://auth0.com/blog/kubernetes-secrets-management/">https://auth0.com/blog/kubernetes-secrets-management/</a>

https://auth0.com/blog/kubernetes-secrets-management/

  1. (왼쪽 위 컨트롤러) Sealed Secrets 컨트롤러는 클러스터에 설치되며, 고유한 공개 키/비밀 키 쌍을 생성한다. 이 키 쌍은 SealedSecret 리소스를 암호화하고 복호화하는 데 사용된다.
  2. (아래 과정) 사용자는 kubeseal CLI 도구를 사용하여 Secret을 SealedSecret으로 변환(암호화)한다. 이 변환은 로컬에서 수행되며, 원본 Secret은 클러스터에 전송되지 않는다. 변환된 암호화된 시크릿 키를 통해 git 레파지토리에 업로드시 안전하게 키를 관리할 수 있다(store)
  3. (오른쪽 위 과정) 시크릿 키를 통한 워크로드 배포시 Sealed Secrets 컨트롤러는 SealedSecret 리소스를 감지하고 해당 Secret을 복호화하여 클러스터에 배포한다.

Sealed Secrets 설치

Sealed Secrets 를 사용하기 위해서는 Client-Side와 Cluster-Side 의 툴을 각각 설치해야 한다.

  • Client-Side 설치 (kubeseal CLI) - 바이너리 확인

    1
    2
    3
    4
    
    # 리눅스 
    wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.21.0/kubeseal-0.21.0-linux-amd64.tar.gz
    tar -xvzf kubeseal-0.21.0-linux-amd64.tar.gz
    sudo install -m 755 kubeseal /usr/local/bin/kubeseal 
    
  • Cluster-Side 설치 (Sealed Secrets Controllers)

    1
    
    kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.21.0/controller.yaml
    

    컨트롤러를 설치하면 컨트롤러 로그를 통해 인증서와 공개 키, 비밀 키를 확인할 수 있다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    # 인증서 확인 
    kubectl logs deployments/sealed-secrets-controller -n kube-system
    --
    controller version: 0.21.0
    2023/06/03 02:57:08 Starting sealed-secrets controller version: 0.21.0
    Searching for existing private keys
    New key written to kube-system/sealed-secrets-keynllnv
    Certificate is 
    -----BEGIN CERTIFICATE-----
    MIIEzDCCArSgAwIBAgIQCbNur2lY5wvwqAZYH7KKsTANBgkqhkiG9w0BAQsFADAA
    MB4XDTIzMDYwMzAyNTcxMloXDTMzMDUzMTAyNTcxMlowADCCAiIwDQYJKoZIhvcN
    AQEBBQADggIPADCCAgoCggIBALqtMiIVQUHCT7EEcTGzRPNOK3CH8hwhOX8gxDcA
    3uN0sLYGRhWyHh+tTDf6BeFvT/K44OU1MjpcyityArXkXizXT5G6Xehl5YY5lHJI
    f/3V71kxzSo/iwj4nn900NbMZ8hhFmd4tm23GjLjx02U6D0frC7qFXfZLy4f+qAO
    
    # 키 확인 
    kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml
    ---
    apiVersion: v1
    items:
    - apiVersion: v1
      data:
        tls.crt: ... # 키 
        tls.key: ... # 키
      kind: Secret
      metadata:
        creationTimestamp: "2023-06-03T02:57:12Z"
        generateName: sealed-secrets-key
        labels:
          sealedsecrets.bitnami.com/sealed-secrets-key: active
        name: sealed-secrets-keynllnv
        namespace: kube-system
        resourceVersion: "36062"
        uid: 178a33bf-14b4-4cde-8687-76e08e5f2b2f
      type: kubernetes.io/tls
    kind: List
    metadata:
      resourceVersion: ""
    

Sealed Secrets 를 통한 키 관리

시크릿 키 관리를 위한 예제로 mysql를 배포하고 sealed secret 을 통해 mysql 접근 패스워드를 관리해보겠다.

  • mysql 예제 배포

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    # mysql-depeloyment.yaml 
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mysql
      labels:
        app: mysql
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mysql
      template:
        metadata:
          labels:
            app: mysql
        spec:
          containers:
          - name: mysql
            image: mysql:8.0.26
            env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-root
                  key: password
            ports:
            - containerPort: 3306
    
    1
    2
    3
    4
    5
    6
    7
    8
    
    # mysql-secret.yaml
    apiVersion: v1
    kind: Secret
    metadata:
      name: mysql-root
    type: Opaque
    data:
      password: cm9vdA==
    
    • 시크릿 키를 디코딩하면 패스워드는 root 이다

mysql 접근 시크릿에 대하여 sealed secrets를 통해 암호화를 진행하자.

1
2
3
4
# 로컬에 컨트롤러 인증서 저장하기 
kubeseal --fetch-cert > mycert.pem 
# 저장한 컨트롤러 인증서를 통한 암호화 
cat mysql-secret.yaml | kubeseal --cert mycert.pem -o yaml > sealed-secret.yaml

암호화 후 키를 확인하면 다음과 같이 패스워드가 달라진 것을 확인할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
cat sealed-secret.yaml 
--- 
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: mysql-root
  namespace: default
spec:
  encryptedData:
    password: AgBGUSjNgqCLMGKI/y/IwlhgNVzF/wrUFxx00eS/33Q4ZniweRu9Wfwlu9Pq16uBmECmVBPlN7US/gX0wV/hqxYrfWULTaByDnNsrRhKaRFGg0mD5DHCxFLqzTZKzUMZwYZovPxREcMt/d1yx3tWViVsqnz5VnNZYONeyhaZvU059Tj1EWyvfoow19YZM9V3iiyxraX2VB1luTPSbt9JZ355hfWxgDXUHTsheKOVW8GUC1fBi15pBFJXwovoqa6cQ1YcYOKSslJORf+WcDB836ikgykJiHoFpgcLVPS/3uaR73RBAN93gt52hHRpwqFj7aMczqm4aruz6tB7ugpSpArjYDkBHaO5NuaYoeeIGApqsjMQUc/ASuZGXf8rPBZiQeDS5QFLZR3II7gVyyYYQXwMmOy7BCmzBSbzZhKKq9wmD7/uEVBmmH9yJFcCC+FyfzlUyycsANCAWJO12z0MzdVSNr7Lbhb2GUStq6VDlQLndCn1MDAwKbYtkxdwGB5H4PcSiM1+TNkwpwOY7O59XwcWNAVtfPFMLK62lgTCFm0RjR7M/oJX32yWFLfiAifnYVJuNK8ic33J2fFyQvZ3TxWVsZzryOI3+vsR+cUbMh3DCXfMduJ/VtPHUe3zsbbYVOA/Fs/+MpP9GkqkndvQx6kyLhGnIUOOU4iIR2loAPXIjhq1ZUn5XPY7jbrO27yr0clQxM2B
  template:
    metadata:
      creationTimestamp: null
      name: mysql-root
      namespace: default
    type: Opaque

암호화된 시크릿 키를 배포하면 mysql 이 정상적으로 배포된 것을 확인할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
kubectl apply -f sealed-secret.yaml 
---
sealedsecret.bitnami.com/mysql-root created

kubectl get pods -A 
---
NAMESPACE     NAME                                        READY   STATUS    RESTARTS   AGE
default       mysql-9fd5797cc-l8szh                       1/1     Running   0          4m23s
default       sealed-secrets-855f5fbf78-d2j4b             1/1     Running   0          23m
kube-system   aws-node-j6nq2                              1/1     Running   0          3h18m
kube-system   aws-node-lg25d                              1/1     Running   0          3h18m
kube-system   coredns-6777fcd775-68cql                    1/1     Running   0          3h16m
kube-system   coredns-6777fcd775-jzxr2                    1/1     Running   0          3h16m
kube-system   ebs-csi-controller-67dccdf78f-65hr5         6/6     Running   0          3h15m
kube-system   ebs-csi-controller-67dccdf78f-hjgjm         6/6     Running   0          3h15m
kube-system   ebs-csi-node-4jzmh                          3/3     Running   0          3h15m
kube-system   ebs-csi-node-68n58                          3/3     Running   0          3h15m
kube-system   kube-proxy-8bgrm                            1/1     Running   0          3h17m
kube-system   kube-proxy-wvnfs                            1/1     Running   0          3h17m
kube-system   sealed-secrets-controller-b97869575-d7prq   1/1     Running   0          14m

kubectl exec mysql-9fd5797cc-l8szh  -it /bin/sh
-- 
mysql -u root -p 
# root 입력 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.26 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

Sealed Secrets 키 관리

앞 서 과정을 확인하니 Sealed Secrets 컨트롤러 내 키를 통해 시크릿 키를 암호화 / 복호화하는 것을 확인하였다. 말 그대로 이중으로 암호화를 진행한 것으로 이해하면 되는데 만약 클러스터를 옮긴다고 가정하거나 재해상태로 새로운 Sealed Secrets 컨트롤러를 생성하는 경우가 생긴다면 시크릿 키의 복호화가 안될 것이고 통일성이 깨져 관리가 힘들어질 것이 분명하다. 그렇다면 Sealed Secrets 컨트롤러 키 관리가 필요한데, AWS Systems Manager Parameter Store 를 통해서 키 관리를 위임할 수 있다.

Sealed Secrets 컨트롤러 키는 다음의 명령어를 통해 키를 파일로 저장시킬 수 있다.

1
kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > master-sealing-key.yaml

해당 파일에서 tls.crt 와 tls.key 에 대해 키 관리가 필요하다. AWS Systems Manager Parameter Store 에서 키를 생성하면 되는데 AWS 콘솔 → Systems Manager → Parameter Store 에서 해당 키를 입력하고 저장시키면 된다.

parameter1.png

저장한 키는 다음의 명령어로 확인할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
aws ssm get-parameter --name "master-sealing-key-tls-crt" --with-decryption
---
{
    "Parameter": {
        "Name": "master-sealing-key-tls-crt",
        "Type": "String",
        "Value": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUV6RENDQXJTZ0F3SUJBZ0lRQ2JOdXIybFk1d3Z3cUFaWUg3S0tzVEFOQmdrcWhraUc5dzBCQVFzRkFEQUEKTUI0WERUSXpNRFl3TXpBeU5UY3hNbG9YRFRNek1EVXpNVEF5TlRjeE1sb3dBRENDQWlJd0RRWUpLb1pJaHZjTgpBUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTHF0TWlJVlFVSENUN0VFY1RHelJQTk9LM0NIOGh3aE9YOGd4RGNBCjN1TjBzTFlHUmhXeUhoK3RURGY2QmVGdlQvSzQ0T1UxTWpwY3lpdHlBclhrWGl6WFQ1RzZYZWhsNVlZNWxISkkKZi8zVjcxa3h6U28vaXdqNG5uOTAwTmJNWjhoaEZtZDR0bTIzR2pMangwMlU2RDBmckM3cUZYZlpMeTRmK3FBTwpEaFpoK2dJOWFvMXJSUWZCeDlIaW1vS1FRRy9GTGlxN0NsME5PaC9VQ1o0Q0RIVDd4VTJsN1JkMytsZDJhdTVMCktMR0N2bjZtOUpnVndwNUU0cGY5dUhoVzlVbU5ESXp6dUZFMEpXSUpwVnpIbForZThPYTNXc1p2aFlzUVlKa2EKcHRpelVwbWZBYTVXdFgyTUsyOFNVbVRaRnlqQTFYaFlRRHN5dGt3NTUzdXBjcW14V3hCZjVBUzlhNGs3Ujk5eAplcVhNK3R1a1dpWUpJSkxnaG9oRW12c3BGZmNBMUFlOVIxVnhKVEJKaVBBN2xmTFVLY1prN1hqMmgwR0t5L09UClVrY0ZCSFFvQlh2c0pseCtIVjROQnEwb2o4TVNkeGtTeWtpcnpKeWtHcFdoOE9HVXNLK2dwd0pCRkVsa0tTc2YKVzdhNlV1VU5kbUwrYzl5Tnp2T2hCUlZkRG1XMUU4ZWN5V3NrSW9MSy92WnRsRFk4ZzgwNmhaVTdZSHI1WWhVYwpWTjJjWXVnM0lwRVVNQkVQU0ZCVk9sd2pUYWw5TVRrK3lXR3VPVUhFeUZ0VzlKQ0IwQmVvTjZUM21uS280cUpyCjNaRmIxTGJrSk56TE1aWUlDbmdYeFhrbC9ic3kyR0tIc0FGL04walA2aDRNMmRTOTRJa2NQOFhhNWVTcGd5RXYKZU9IUEFnTUJBQUdqUWpCQU1BNEdBMVVkRHdFQi93UUVBd0lBQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwRwpBMVVkRGdRV0JCVDUzM0t6VENNYkVzYm1CV1JBK3cwWmpIc1VoVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBClZUNnQzV1IzTEFCUkJVeUNFZXhoRzRLWEFLeTdhVGhqODJHdlo0Q2thMFFNTEp4OUFaMjZZZFREMlZDRUNILzgKb3hUYlNiZzVtYTVUd0p0b25LTnpzMUkydWFpRFlDRG5aWHhjRUhnSmpEaFFDOUJwRUdPZmY1ek4xSTlMcFJMSgpXSjBKSk9vVW5WbjA2cHhqSTJvdVVRZy9oNVhidVBsZWJjVGFJN0Q0RWNZWWwzYnF5enpWVTFVRFFBdWpDTlB0CnZRcDZQa2YvTUUrcE9BNnpPU3BDckExYW92WlpZdTZUQWpzanZpcXluYVdnUWF5TFZ1OHJpSzZibzBOdGh4TzkKTWpmQXlyWGFUdUxNK0RlOVBIMW1teHB5SUZ3dmREcjZYWlRkTEN2SVpNdGprQXM2SXhqcDlheHVUS1lQVHVqcgpCNmxOa0hUNW9aSFNDUmVxVGFQZ3ZzcTdYb3dRNGtNU0traUp4OEhrNTJkVDVuQ0sybGltdVlESmFoWWZrSm1WCjV1NkQ4MkJoa3BKUSswQkJxN1pWTkZ2VCs1ME5NRG5BbVhWamdQd0VKVTFqdDNtS0Z6eWw1bWswSTZaZGpqd0kKVkdybUZLZWs2c1UzVERjbFJSdHJiUGlVZ3ZWUk5Fa0UxSGZzUHBFR0h4NzNxb0MycjRwUTVrOEYzd2JqQkU3VQo0MW02VVFsLzZ4a1RvV3M0RU85dUtFazdaVVBqTUEwK3hkYnZDWGdDRE45QXN3ZlQ2bzhMWGE5eUVyL3NNcng4Ck9ReEJsY1ZRRVRUc0hUd0djcEZlZ2hBUzNacll6U2w5ZGhCWHgweWZQUnZJWnRzdzMyVy9GS1M4djMvcmtQZDIKWGk1QjdldzJ2U0NSL0t2VHFiUG52UDU4cENyVVhlZUJhVmxIYlNqMUE2Zz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
        "Version": 1,
        "LastModifiedDate": "2023-06-03T12:48:22.354000+09:00",
        "ARN": "arn:aws:ssm:ap-northeast-2:955963799952:parameter/master-sealing-key-tls-crt",
        "DataType": "text"
    }
}

Amazon GuardDuty

AWS 의 지능형 위협 탐지 서비스이다. 이 서비스는 AWS 계정, 워크로드, 그리고 AWS에서 호스팅되는 데이터를 보호하기 위해 고급 머신 러닝을 활용하여 악의적인 활동이나 비정상적인 행동을 탐지하는데 사용된다. 탐지는 다음의 활동에서 식별한다.

  1. 인프라(EKS)와 계정(IAM)을 위협하는 악성 소스로부터의 공격과 위협
  2. 계정 내부의 위협, 즉 AWS 리소스를 위협하는 잠재적으로 악의적인 또는 무단 행동
  3. AWS 리소스를 위협하는 알려진 악의적인 활동이나 알려진 비정상 행동

GuardDuty는 AWS 로그 데이터, 예를 들어 AWS CloudTrail, Amazon VPC Flow Logs, DNS 로그 등을 분석하여 이러한 위협을 탐지한다. 별도의 설치도 필요 없으며, 사용자는 수동으로 활성화하거나 관리할 필요 없이 GuardDuty를 사용하여 보안 통찰력을 얻을 수 있다.

<a href="https://www.youtube.com/watch?v=t3rVVilJWEk">https://www.youtube.com/watch?v=t3rVVilJWEk</a>

https://www.youtube.com/watch?v=t3rVVilJWEk

활성화도 간단하다. AWS 콘솔에서 GuardDuty로 접속하여 EKS 보호 기능을 활성화시키면 끝이다.

guard2.png

guard3.png

활성화 이후 결과에서 탐지 결과를 확인할 수 있다.

guard4.png

다음은 워크샵에서 제공하는 이상 탐지 예제로 몇 가지 예제를 따라해보고 가드 듀티에 적용되는 지 확인해보겠다.

  • 파드 내에서 이상 실행 명령어 감지

    1
    
    kubectl -n kube-system exec $(kubectl -n kube-system get pods -o name -l app=efs-csi-node | head -n1) -c efs-plugin -- pwd
    
  • 익명 사용자에 대한 접근 감지 (호출, 정책, 파드 생성)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    
    EKS_CLUSTER_NAME=<클러스터 이름 입력>
    AWS_DEFAULT_REGION=ap-northeast-2 
    
    # AnonymousAccessGranted
    kubectl create role pod-create --verb=get,list,watch,create,delete,patch --resource=pods -n default 
    kubectl create rolebinding pod-access --role=pod-create --user=system:anonymous
    
    # Discovery: SuccessfulAnonymousAccess
    API_URL=$(aws eks describe-cluster --name $EKS_CLUSTER_NAME --query "cluster.endpoint" --region $AWS_DEFAULT_REGION --output text)
    curl -k $API_URL/api/v1/pods
    -- 
    {
      "kind": "Status",
      "apiVersion": "v1",
      "metadata": {},
      "status": "Failure",
      "message": "pods is forbidden: User \"system:anonymous\" cannot list resource \"pods\" in API group \"\" at the cluster scope",
      "reason": "Forbidden",
      "details": {
        "kind": "pods"
      },
      "code": 403
    }
    
    # Impact:Kubernetes/SuccessfulAnonymousAccess 
    API_URL=$(aws eks describe-cluster --name $EKS_CLUSTER_NAME --query "cluster.endpoint" --region $AWS_DEFAULT_REGION --output text)
    curl -k -v $API_URL/api/v1/namespaces/default/pods -X POST -H 'Content-Type: application/yaml' -d '---
    apiVersion: v1
    kind: Pod
    metadata:
      name: nginx
      namespace: default
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
    '
    --
    ...
    < 
    {
      "kind": "Status",
      "apiVersion": "v1",
      "metadata": {},
      "status": "Failure",
      "message": "pods \"nginx\" is forbidden: PodSecurityPolicy: unable to admit pod: []",
      "reason": "Forbidden",
      "details": {
        "name": "nginx",
        "kind": "pods"
      },
      "code": 403
    * Connection #0 to host 76A102F3C34E6587C0300AFD756C2316.gr7.ap-northeast-2.eks.amazonaws.com left intact 
    
  • 서비스 사용자 계정에 cluster-admin가 부여된 경우 감지

    1
    
    kubectl create rolebinding sa-default-admin --clusterrole=cluster-admin --serviceaccount=default:default --namespace=default 
    
  • 내부 대시보드가 로드밸런서로 변경되어 외부로 서비스가 노출될 때 감지

    1
    2
    3
    4
    5
    6
    
    
    # 예제 대시보드 배포 
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.1/aio/deploy/recommended.yaml
    
    # 서비스 타입 로드밸런서로 변경
    kubectl patch svc kubernetes-dashboard -n kubernetes-dashboard -p='{"spec": {"type": "LoadBalancer"}}'
    
  • 루트 수준 액세스 권한이 있는 컨테이너가 실행 감지

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ubuntu-privileged
    spec:
      selector:
        matchLabels:
          app: ubuntu-privileged
      replicas: 1
      template:
        metadata:
          labels:
            app: ubuntu-privileged
        spec:
          containers:
          - name: ubuntu-privileged
            image: ubuntu
            ports:
            - containerPort: 22
            securityContext:
              privileged: true
    
  • 호스트 경로 볼륨 마운트 감지

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ubuntu-privileged
    spec:
      selector:
        matchLabels:
          app: ubuntu-privileged
      replicas: 1
      template:
        metadata:
          labels:
            app: ubuntu-privileged
        spec:
          containers:
          - name: ubuntu-privileged
            image: ubuntu
            ports:
            - containerPort: 22
            securityContext:
              privileged: true
            volumeMounts:
            - mountPath: /test-pd
              name: test-volume
          volumes:
          - name: test-volume
            hostPath: # 호스트 볼륨 마운트 
              path: /etc
    

예제를 배포하면 다음과 같이 Guardduty 화면에서 이상 감지 결과와 정보를 확인할 수 있다.

guardduty5.png

guard7.png

해당 이벤트에 대해 알람 구성도 가능하다. AWS Guardduty 공식 문서를 참고하면 Cloudwatch 에 규칙 생성으로 이벤트를 받아 알람을 설정할 수 있다.

  • 설정 → 결과 내보내기 옵션을 통해 Cloudwatch 에 이벤트 제공

    guard10.png

  • Cloudwatch → 이벤트 → 규칙 생성

    guard11.png

    guard12.png

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    
    {
      "source": [
        "aws.guardduty"
      ],
      "detail-type": [
        "GuardDuty Finding"
      ],
      "detail": {
        "severity": [
          4,
          4.0,
          4.1,
          4.2,
          4.3,
          4.4,
          4.5,
          4.6,
          4.7,
          4.8,
          4.9,
          5,
          5.0,
          5.1,
          5.2,
          5.3,
          5.4,
          5.5,
          5.6,
          5.7,
          5.8,
          5.9,
          6,
          6.0,
          6.1,
          6.2,
          6.3,
          6.4,
          6.5,
          6.6,
          6.7,
          6.8,
          6.9,
          7,
          7.0,
          7.1,
          7.2,
          7.3,
          7.4,
          7.5,
          7.6,
          7.7,
          7.8,
          7.9,
          8,
          8.0,
          8.1,
          8.2,
          8.3,
          8.4,
          8.5,
          8.6,
          8.7,
          8.8,
          8.9
        ]
      }
    }
    
    • 해당 코드는 severity 필드가 4.0에서 8.9 사이인 GuardDuty Finding 이벤트를 필터링하도록 설정되어 있다.
  • 구독 SNS 설정하고 규칙을 생성하자

    duty12.png

  • 구성이 완료되면 패턴 기반으로 이벤트가 4~8.9 발생시 SNS로 알람이 오는 것을 확인할 수 있다.

    guard14.png

운영 레벨에서 사용함에 있어 비용적인 측면에서도 고려해야 한다. 23년 6월 기준 30일동안 프리티어로 일정 사용량에 대한 비용을 무료로 제공하고 있다. 문제는 그 다음인데 해당 기능을 위해서는 Cloudtrail, VPC flowlog, EKS runtime 모니터링에 대한 비용을 추가로 산정해야 하기 때문에 도입시 반드시 고려해야 한다.

durt-fee1.png

<a href="https://aws.amazon.com/ko/guardduty/pricing/">https://aws.amazon.com/ko/guardduty/pricing/</a>

https://aws.amazon.com/ko/guardduty/pricing/

사고 대응

참고 : EKS 모범 사례 - 보안 - 사고 대응

Amazon GuardDuty를 통해 악의적인 활동 및 이상 동작을 모니터링하고 알람을 거는 방법까지 확인하였다. 다음은 알람의 다음 단계로, 실제 보안 사고 발생시 대응할 수 있는 방법들을 정리한다. 실제 사고가 발생하면 영향을 받는 컨테이너를 폐기하고 교체할지 또는 컨테이너를 격리하고 검사할지 결정해야 한다. 말이 결정이지.. 결국 재발 방지를 위해 컨테이너 격리 및 검사(사고 근본 원인 분석 및 포렌식 조사)가 필수적으로 진행되어야 한다. 사고 대응의 과정은 식별 → 격리 → 포렌식 분석 및 재배포 으로 과정별 대응 계획을 공유하겠다.

식별

가장 먼저 문제가 되는 파드나 노드를 식별하는 것이 중요하다. 식별 방식은 문제 파드나 서비스 계정, 취약 및 손상된 이미지를 기준으로 노드를 식별한다. 아래는 식별 방식별 명령어이다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 잘못된 파드 이름과 네임스페이스를 알고 있을 경우, 파드가 실행되는 노드를 식별하는 명령어 
kubectl get pods <name> --namespace <namespace> -o=jsonpath='{.spec.nodeName}{"\n"}'

# 파드가 워크로드(예. 디플로이먼트)로 동작하는 경우 모든 포드와 실행 중인 노드를 나열하는 명령어 
selector=$(kubectl get deployments <name> \
 --namespace <namespace> -o json | jq -j \
'.spec.selector.matchLabels | to_entries | .[] | "\(.key)=\(.value)"')

kubectl get pods --namespace <namespace> --selector=$selector \
-o json | jq -r '.items[] | "\(.metadata.name) \(.spec.nodeName)"'

# 서비스 계정을 사용하여 문제가 있는 파드나 노드를 식별하는 명령어
kubectl get pods -o json --namespace <namespace> | \
    jq -r '.items[] |
    select(.spec.serviceAccount == "<service account name>") |
    "\(.metadata.name) \(.spec.nodeName)"'

# 취약하거나 손상된 이미지 및 작업자가 있는 파드나 노드를 식별 명령어
IMAGE=<Name of the malicious/compromised image>

kubectl get pods -o json --all-namespaces | \
    jq -r --arg image "$IMAGE" '.items[] | 
    select(.spec.containers[] | .image == $image) | 
    "\(.metadata.name) \(.metadata.namespace) \(.spec.nodeName)"'

격리

문제 노드나 파드를 식별하였으면 해당 리소스에 대한 격리가 필요하다. 격리 과정은 다음과 같다.

  1. 네트워크 정책을 통해 식별 파드의 인그래스 & 이그래스 트래픽을 거부시킨다.

    다음과 같이 문제있는 파드의 레이블(예제 레이블, app:web ) 을 찾아 모든 트래픽을 거부시키는 네트워크 정책을 생성하여 공격을 중지시킬 수 있다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: default-deny
    spec:
      podSelector:
        matchLabels: 
          app: web
      policyTypes:
      - Ingress
      - Egress
    

    만약 노드 호스트가 뚫린 경우, AWS 보안 그룹을 통해 식별 노드의 네트워크 인그래스를 막아 다른 호스트로 부터 격리할 수 있다. 다만, 보안 그룹을 변경할 때 실행 중인 모든 컨테이너가 영향을 받으니 사전 테스트를 통해 식별 IP 및 포트를 확인하는 작업이 필요하다.

  2. IAM 보안 자격 증명 취소를 통해 추가 손상을 방지한다.

    파드가 다른 AWS 리소스에 액세스할 수 있도록 허용하는 IAM 역할이 할당된 경우 추가 손상을 막기 위해 식별 노드에서 해당 IAM 역할을 제거한다. 이때도 다른 워크로드에 영향을 주지 않고 역할에서 IAM 정책을 안전하게 제거할 수 있는 지 평가가 필요하다.

  3. 노드 차단을 통해 더 이상 파드가 노드에 예약하지 않도록 설정한다.

    kubernetes cordon명령어를 통해 노드에 더 이상 파드를 예약하지 않도록 한다. 이는 다른 워크로드에 영향을 주지 않고 포렌식 연구를 하기 위함이다.

  4. 종료 방지 기능을 활성화하여 노드 축소 이벤트로 부터 노드를 보호시킨다.

    해킹 공격으로 이상 감지의 파드를 지워 문제 원인을 지우려고 시도할 수 있다. 이때 ASG 의 종료 방지 기능을 활용하면, 인스턴스 축소 이벤트로 부터 노드를 보호시킬 수 있다.

포렌식 분석 및 재배포

다음은 원인 분석을 위해 식별 노드에 대한 휘발성 아키텍츠 캡처가 필요하다.

  1. 노드에서 실행 중인 도커 프로세스와 메모리 및 포트 확인

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    
    # 파드별 프로세스 확인 
    sudo pstree
    ---
    systemd─┬─2*[agetty]
            ├─amazon-ssm-agen─┬─ssm-agent-worke───8*[{ssm-agent-worke}]
            │                 └─8*[{amazon-ssm-agen}]
            ├─auditd───{auditd}
            ├─chronyd
            ├─containerd───11*[{containerd}]
            ├─containerd-shim─┬─aws-vpc-cni─┬─aws-k8s-agent───8*[{aws-k8s-agent}]
            │                 │             └─4*[{aws-vpc-cni}]
            │                 ├─pause
            │                 └─11*[{containerd-shim}]
    ...
    
    # 파드별 프로세스 및 메모리 확인
    ps afxuwww 
    ---
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    ...
    root      5699  0.0  0.3 721420 15284 ?        Ssl  05:32   0:01  \_ /livenessprobe --csi-address=/csi/csi.sock
    root      5409  0.1  0.2 712480 10924 ?        Sl   05:32   0:11 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id eb55d159bad848ea2179132ee8d8d26dcb768aba4098331045b5943fa3981120 -address /run/containerd/containerd.sock
    ec2-user  5430  0.0  0.0    972     4 ?        Ss   05:32   0:00  \_ /pause
    ec2-user  5502  0.0  1.0 765196 39908 ?        Ssl  05:32   0:01  \_ /bin/aws-ebs-csi-driver controller --endpoint=unix:///var/lib/csi/sockets/pluginproxy/csi.sock --k8s-tag-cluster-id=hanhorang --logging-format=text --user-agent-extra=eks --v=2
    ec2-user  5646  0.0  1.0 755660 42648 ?        Ssl  05:32   0:08  \_ /csi-provisioner --csi-address=/var/lib/csi/sockets/pluginproxy/csi.sock --v=2 --feature-gates=Topology=true --extra-create-metadata --leader-election=true --default-fstype=ext4
    ec2-user  5788  0.0  0.9 751060 35884 ?        Ssl  05:32   0:03  \_ /csi-attacher --csi-address=/var/lib/csi/sockets/pluginproxy/csi.sock --v=2 --leader-election=true
    ec2-user  5852  0.1  0.8 750784 32136 ?        Ssl  05:32   0:17  \_ /csi-snapshotter --csi-address=/var/lib/csi/sockets/pluginproxy/csi.sock --leader-election=true --extra-create-metadata
    ec2-user  5927  0.0  0.8 751100 34652 ?        Ssl  05:32   0:00  \_ /csi-resizer --csi-address=/var/lib/csi/sockets/pluginproxy/csi.sock --v=2 --handle-volume-inuse-error=false
    ec2-user  5961  0.0  0.4 721676 16452 ?        Ssl  05:32   0:02  \_ /livenessprobe --csi-address=/csi/csi.sock
    root     13499  0.0  0.2 712224 10252 ?        Sl   05:57   0:01 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id cb6ebee42e28416dce159859edd5c0c2ca894bdae5fab6af4e51f8b26c21dc28 -address /run/containerd/containerd.sock
    65535    13521  0.0  0.0    972     4 ?        Ss   05:57   0:00  \_ /pause
    docker   13651  0.0  0.8 741808 34400 ?        Ssl  05:58   0:03  \_ /dashboard --insecure-bind-address=0.0.0.0 --bind-address=0.0.0.0 --auto-generate-certificates --namespace=kubernetes-dashboard
    root     14138  0.0  0.2 712480 10652 ?        Sl   05:59   0:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 8e865c7d2f0dc0a8ca9061aea5f89df362d07d749b8f984884d900ca77845011 -address /run/containerd/containerd.sock
    65535    14160  0.0  0.0    972     4 ?        Ss   05:59   0:00  \_ /pause
    root      9717  0.0  0.2 712480 10028 ?        Sl   07:01   0:00 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id 76439116dd121ba3c644c38ff9e0b09c01810dfff8c5adbeb41812abe9871ce7 -address /run/containerd/containerd.sock
    65535     9738  0.0  0.0    972     4 ?        Ss   07:01   0:00  \_ /pause
    root      9808  0.0  0.1   7880  4512 ?        Ss   07:01   0:00  \_ nginx: master process nginx -g daemon off;
    100       9820  0.0  0.0   8336  2200 ?        S    07:01   0:00      \_ nginx: worker process
    100       9821  0.0  0.0   8336  1548 ?        S    07:01   0:00      \_ nginx: worker process
    
    # 포트 확인 
    sudo netstat -tulnp
    ---
    Active Internet connections (only servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
    tcp        0      0 127.0.0.1:10248         0.0.0.0:*               LISTEN      2940/kubelet        
    tcp        0      0 127.0.0.1:61679         0.0.0.0:*               LISTEN      3431/./aws-k8s-agen 
    tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      1821/rpcbind        
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      2419/sshd           
    tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      2262/master         
    tcp        0      0 127.0.0.1:46847         0.0.0.0:*               LISTEN      2826/containerd     
    tcp        0      0 127.0.0.1:50051         0.0.0.0:*               LISTEN      3431/./aws-k8s-agen 
    tcp6       0      0 :::10249                :::*                    LISTEN      4213/kube-proxy     
    tcp6       0      0 :::10250                :::*                    LISTEN      2940/kubelet        
    tcp6       0      0 :::61678                :::*                    LISTEN      3431/./aws-k8s-agen 
    tcp6       0      0 :::111                  :::*                    LISTEN      1821/rpcbind        
    tcp6       0      0 :::10256                :::*                    LISTEN      4213/kube-proxy     
    tcp6       0      0 :::22                   :::*                    LISTEN      2419/sshd           
    udp        0      0 0.0.0.0:68              0.0.0.0:*                           2064/dhclient       
    udp        0      0 0.0.0.0:111             0.0.0.0:*                           1821/rpcbind        
    udp        0      0 127.0.0.1:323           0.0.0.0:*                           1848/chronyd        
    udp        0      0 0.0.0.0:720             0.0.0.0:*                           1821/rpcbind        
    udp6       0      0 :::111                  :::*                                1821/rpcbind        
    udp6       0      0 ::1:323                 :::*                                1848/chronyd        
    udp6       0      0 fe80::e2:4aff:fe3e::546 :::*                                2099/dhclient       
    udp6       0      0 :::720                  :::*                                1821/rpcbind
    
  2. 작업자 노드에서 컨테이너가 변경되기 전에 docker 명령을 실행하여 도커에 대한 설정 수집

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    
    # docker 
    ## 실행 컨테이너 확인 
    docker container top $CONTAINER 
    ## 로그 확인 
    docker container logs $CONTAINER
    ## 포트 확인
    docker container port $CONTAINER 
    ## 파일 및 디렉토리 변경 사항 캡처 
    docker container diff $CONTAINER
    
    # containerd 1.24 이상 
    sudo nerdctl ps # 컨테이너 리스트 확인
    sudo nerdctl logs [container-id] # 컨테이너 로그 확인
    sudo nerdctl diff [container-id] # 컨테이너와 원본 이미지 간의 차이점 확인 
    
  3. 포렌식 캡처를 위해 컨테이너를 일시중지하고, 인스턴스의 EBS 볼륨을 스냅샷하자.

    노드 IP를 식별하여 해당 노드에 연결된 볼륨에 들어가 스냅샷을 생성하도록 하자.

    snap1.png

  4. 손상된 포드 또는 워크로드 리소스 재배포

    분석을 위한 데이터를 수집한 후에는 손상된 포드 또는 워크로드 리소스를 재배포 할 수 있다. 수정 사항을 롤아웃하고 새 교체 파드로 시작한 다음,취약 파드를 삭제하면 끝난다.

침해 대응 시뮬레이션

민첩한 사고 대응을 위해서는 사전의 침해 시뮬레이션이 필요하다. 시뮬레이션을 위한 유틸리티로 https://github.com/cyberark/kubesploit 가 있는데 아래 침해 스택에 따라 모듈을 구성하여 침해 방식을 테스트한다.

<a href="https://github.com/cyberark/kubesploit/blob/assets/mitre_pic_full.png">https://github.com/cyberark/kubesploit/blob/assets/mitre_pic_full.png</a>

https://github.com/cyberark/kubesploit/blob/assets/mitre_pic_full.png

공격 스택이 별로 없는 것 같지만,, k8s 기반의 침해 시뮬레이션 툴 중에 가장 별이 많으며, 그나마 많은 침해 방식 기능을 제공한다. 또한, 깃허브 공식 문서에서는 침해 스택에 따른 대응 방식도 제공하니 나중에 침해 대응 시뮬레이션 진행시 참고하도록 하자.

AWS WAF을 통한 애플리케이션 트래픽 보호

참고 :One Observability Workshop

AWS WAF 는 웹 애플리케이션을 일반적인 웹 공격으로부터 보호하는 보안 서비스이다. 이 서비스는 공격자가 취약점을 이용하여 애플리케이션에 접근하거나 데이터를 유출하는 것을 막는다. (참고 : ChatGPT)

waf1.png

AWS WAF는 다음과 같은 주요 기능을 제공한다:

  1. 사용자 정의 보안 규칙: AWS WAF는 사용자 정의 필터를 생성하여 웹 요청을 허용하거나 차단할 수 있다. 이 필터는 IP 주소, HTTP 헤더, HTTP 바디, URI 문자열, SQL 삽입 공격, 스크립트 교차 사이트 공격 (XSS) 등에 기반한다.
  2. 비용 효율적인 보안: AWS WAF는 수요에 따라 가격이 책정되므로, 사용자는 실제로 사용하는 만큼만 비용을 지불한다.
  3. 실시간 지표: AWS WAF는 AWS CloudWatch와 통합하여 웹 트래픽에 대한 실시간 지표를 제공한다. 이를 통해 사용자는 웹 트래픽의 트렌드를 식별하고 필요한 보안 조치를 즉시 취할 수 있다.
  4. 보안 자동화: AWS WAF는 API를 통해 보안을 자동화할 수 있다. 이는 룰의 업데이트를 자동화하고, 새로운 애플리케이션에 보안 설정을 자동으로 적용하는 데 사용할 수 있다.

AWS WAF는 Amazon CloudFront, AWS Application Load Balancer, Amazon API Gateway 등 다른 AWS 서비스와 함께 사용될 수 있어, 사용자의 AWS 웹 애플리케이션을 보호하는 데 유용하다. EKS 환경에서도 애플리케이션을 ALB로 구성하며 여기에 WAF를 연결하여 유입 트래픽에 대한 보안 구성을 설정할 수 있다.

WAF 기능 확인을 위한 선행 작업으로 EKS 클러스터에서 ALB 컨트롤러와 예제 배포가 필요하다. 다음의 명령어를 통해 진행하도록 하자.

WAF 구성

  • ALB 컨트롤러 설치

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    # ALB controller 정책 설치
    curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
    aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
    
    # 정책 arn 생성 확인 
    ACCOUNT_ID=000000000000
    aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --query 'Policy.Arn'
    ---
    "arn:aws:iam::000000000000:policy/AWSLoadBalancerControllerIAMPolicy" 
    
    # OIDC 서비스 어카운트 생성
    CLUSTER_NAME=hanhorang 
    eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller \
    --attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve
    
    # OIDC 서비스 어카운트 확인 
    kubectl edit sa/aws-load-balancer-controller  -n kube-system
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      annotations: # annoation 에 role-arn이 추가된 것을 확인! 
        eks.amazonaws.com/role-arn: arn:aws:iam::000000000000:role/eksctl-hanhorang-addon-iamserviceaccount-kub-Role1-1MPK8ATNJVXQH
      creationTimestamp: "2023-05-08T05:32:22Z"
      labels:
        app.kubernetes.io/managed-by: eksctl
      name: aws-load-balancer-controller
      namespace: kube-system
      resourceVersion: "1236"
      uid: 5fe3e69b-8cfc-426c-a16a-509e1f7c4a86
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    # ALB 설치 
    helm repo add eks https://aws.github.io/eks-charts
    helm repo update
    helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
      --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
    
    # ALB 파드 확인 
    kubectl get pods -A
    ---
    NAMESPACE     NAME                                            READY   STATUS    RESTARTS   AGE
    kube-system   aws-load-balancer-controller-7857849d69-qc9nr   1/1     Running   0          24m
    kube-system   aws-load-balancer-controller-7857849d69-qjvkk   1/1     Running   0          24m
    
    # ALB 로그 확인
    kubectl logs pods/aws-load-balancer-controller-7857849d69-qc9nr -n kube-system
    ---
    {"level":"info","ts":"2023-05-08T05:59:45Z","msg":"version","GitVersion":"v2.5.1","GitCommit":"06abaed66e17a411ba064f34e6018b889780ac66","BuildDate":"2023-04-17T22:36:53+0000"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.metrics","msg":"Metrics server is starting to listen","addr":":8080"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"setup","msg":"adding health check for controller"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/mutate-v1-pod"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/mutate-v1-service"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/validate-elbv2-k8s-aws-v1beta1-ingressclassparams"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/mutate-elbv2-k8s-aws-v1beta1-targetgroupbinding"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/validate-elbv2-k8s-aws-v1beta1-targetgroupbinding"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/validate-networking-v1-ingress"}
    {"level":"info","ts":"2023-05-08T05:59:45Z","logger":"setup","msg":"starting podInfo repo"}
    {"level":"info","ts":"2023-05-08T05:59:47Z","logger":"controller-runtime.webhook.webhooks","msg":"Starting webhook server"}
    {"level":"info","ts":"2023-05-08T05:59:47Z","msg":"Starting server","kind":"health probe","addr":"[::]:61779"}
    ...
    
  • Ingress 예제 배포

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # game-2048 배포 
    curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml
    cat ingress1.yaml | yh
    kubectl apply -f ingress1.yaml
    
    kubectl get ingress -A 
    --- 
    NAMESPACE   NAME           CLASS   HOSTS   ADDRESS                                                                        PORTS   AGE
    game-2048   ingress-2048   alb     *       k8s-game2048-ingress2-91307666f8-2009146712.ap-northeast-2.elb.amazonaws.com   80      24s
    

    waf2.png

해당 서비스에 WAF를 연결하겠다. AWS 콘솔에서 WAF 접근하여 새로운 서비스를 추가하도록 하자. 아쉽지만 WAF 콘솔 진입시 지역이 글로벌로 바뀌어서 그런가 한글 지원이 안된다.

waf3.png

추가 리소스에서 위에서 생성한 ALB를 연결하자.

waf4.png

AWS WAF에서 자체적으로 트래픽 보호를 위한 규칙을 제공한다.. 하지만 아래 그림과 같이 사용에 비용이 발생하니 참고하자. 규칙 또한 다양하고 구성시 설명이 상세하게 나오니 참고하자. 규칙 키워드로 정리하면 IP 주소 차단, SQL Injection 공격 차단, Cross-Site Scripting (XSS) 공격 차단, 유저 봇 차단, 크기 제한 규칙, 지역 블랙리스트, HTTP 헤더, HTTP 메소드, 문자열 매칭, 반복 요청 등이 있고 세부적으로 커스터마이징이 가능하다.

waf6.png

WAF 관측

WAF을 구성하면 메트릭과 로깅 정보를 확인할 수 있다. 메트릭은 기본적으로 제공하지만, 로깅은 WAF 내 기능 탭 Logging and metrics 에서 로깅 기능을 활성화(로그 그룹 생성시 네이밍 aws-waf-logs- 로 시작해야 인식된다.)해야 한다. 활성화를 하면 로깅 관련 쿼리도 제공된다.

waf8.png

waf11.png

WAF 기능 확인

기본적으로 WAF 의 설정된 룰에 조건이 도달되면 해당 IP는 접근이 블록(차단)된다. 상황에 따라 다르지만, 기본적으로 WAF는 각 규칙이 엄격하게 구성되어 있어 원치않는 차단 IP가 나올 수 있다. 이를 대비하기 위해 옵션이 제공되는 데 블록 대신 Count로 모니터링할 수 있는 기능을 제공한다. 기능 탭 Rule에서 규칙을 클릭하고 설정 부분에서 해당 옵션을 활성화시키면 된다.

waf12.png

WAF에서 임계값에 대한 알람 구성 또한 가능하다. Cloudwatch 에서 알람 생성에서 WAF에서 확인한 메트릭 값에 대한 알람 설정을 손 쉽게 할 수 있다.

waf-alarm.png

WAF는 CloudWatch Contributor Insights와 연계하여 특정 조건의 상위 N 기여자를 가져와 대시보드에서 시각화하는 기능을 제공한다. 이를 통해서 상위 차단 IP 주소를 시각화하여 확인할 수 있다. Cloudwatch → contributor Ingihts → 규칙 생성으로 WAF에서 생성한 로그를 지정하고 로그 개수에 대한 필터링을 집계 설정하여 대시보드 구성하면 다음과 같이 확인할 수 있다.

waf15.png

트래픽 테스트를 할 수 없어서 워크샵 결과 화면을 대체한다. 앞 서 생성한 규칙 키에서 설정한 값에 대한 로그 값을 합계하여 Count 값을 확인할 수 있다.

contributor-insights-blocked-requests.jpeg