|
|
지난 글에 이어서 AWS 서비스를 통해서 EKS 보안 구성을 한 내용들을 공유한다. 보안 구성 내용은 EKS 워크샵 내용을 참고하여 정리하였다. 보안 구성 내용은 다음과 같다.
- EKS Secret 시크릿 키 관리 : Sealed Secrets + AWS KMS
- EKS 이상 탐지 모니터링 : Amazon GuardDuty + 사고 대응
- EKS 애플리케이션 트래픽 보호 : AWS WAF
EKS Secret Key 관리
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 에서 암호화한 시크릿 키에 대해 복호화를 해준다.
각 파트를 통해 시크릿 키를 관리하는 과정은 다음과 같다.
https://auth0.com/blog/kubernetes-secrets-management/
- (왼쪽 위 컨트롤러) Sealed Secrets 컨트롤러는 클러스터에 설치되며, 고유한 공개 키/비밀 키 쌍을 생성한다. 이 키 쌍은 SealedSecret 리소스를 암호화하고 복호화하는 데 사용된다.
- (아래 과정) 사용자는
kubeseal
CLI 도구를 사용하여 Secret을 SealedSecret으로 변환(암호화)한다. 이 변환은 로컬에서 수행되며, 원본 Secret은 클러스터에 전송되지 않는다. 변환된 암호화된 시크릿 키를 통해 git 레파지토리에 업로드시 안전하게 키를 관리할 수 있다(store) - (오른쪽 위 과정) 시크릿 키를 통한 워크로드 배포시 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를 통해 암호화를 진행하자.
|
|
암호화 후 키를 확인하면 다음과 같이 패스워드가 달라진 것을 확인할 수 있다.
|
|
암호화된 시크릿 키를 배포하면 mysql 이 정상적으로 배포된 것을 확인할 수 있다.
|
|
Sealed Secrets 키 관리
앞 서 과정을 확인하니 Sealed Secrets 컨트롤러 내 키를 통해 시크릿 키를 암호화 / 복호화하는 것을 확인하였다. 말 그대로 이중으로 암호화를 진행한 것으로 이해하면 되는데 만약 클러스터를 옮긴다고 가정하거나 재해상태로 새로운 Sealed Secrets 컨트롤러를 생성하는 경우가 생긴다면 시크릿 키의 복호화가 안될 것이고 통일성이 깨져 관리가 힘들어질 것이 분명하다. 그렇다면 Sealed Secrets 컨트롤러 키 관리가 필요한데, AWS Systems Manager Parameter Store 를 통해서 키 관리를 위임할 수 있다.
Sealed Secrets 컨트롤러 키는 다음의 명령어를 통해 키를 파일로 저장시킬 수 있다.
|
|
해당 파일에서 tls.crt 와 tls.key 에 대해 키 관리가 필요하다. AWS Systems Manager Parameter Store 에서 키를 생성하면 되는데 AWS 콘솔 → Systems Manager → Parameter Store 에서 해당 키를 입력하고 저장시키면 된다.
저장한 키는 다음의 명령어로 확인할 수 있다.
|
|
Amazon GuardDuty
AWS 의 지능형 위협 탐지 서비스이다. 이 서비스는 AWS 계정, 워크로드, 그리고 AWS에서 호스팅되는 데이터를 보호하기 위해 고급 머신 러닝을 활용하여 악의적인 활동이나 비정상적인 행동을 탐지하는데 사용된다. 탐지는 다음의 활동에서 식별한다.
- 인프라(EKS)와 계정(IAM)을 위협하는 악성 소스로부터의 공격과 위협
- 계정 내부의 위협, 즉 AWS 리소스를 위협하는 잠재적으로 악의적인 또는 무단 행동
- AWS 리소스를 위협하는 알려진 악의적인 활동이나 알려진 비정상 행동
GuardDuty는 AWS 로그 데이터, 예를 들어 AWS CloudTrail, Amazon VPC Flow Logs, DNS 로그 등을 분석하여 이러한 위협을 탐지한다. 별도의 설치도 필요 없으며, 사용자는 수동으로 활성화하거나 관리할 필요 없이 GuardDuty를 사용하여 보안 통찰력을 얻을 수 있다.
https://www.youtube.com/watch?v=t3rVVilJWEk
활성화도 간단하다. AWS 콘솔에서 GuardDuty로 접속하여 EKS 보호 기능을 활성화시키면 끝이다.
활성화 이후 결과에서 탐지 결과를 확인할 수 있다.
다음은 워크샵에서 제공하는 이상 탐지 예제로 몇 가지 예제를 따라해보고 가드 듀티에 적용되는 지 확인해보겠다.
-
파드 내에서 이상 실행 명령어 감지
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 화면에서 이상 감지 결과와 정보를 확인할 수 있다.
해당 이벤트에 대해 알람 구성도 가능하다. AWS Guardduty 공식 문서를 참고하면 Cloudwatch 에 규칙 생성으로 이벤트를 받아 알람을 설정할 수 있다.
-
설정 → 결과 내보내기 옵션을 통해 Cloudwatch 에 이벤트 제공
-
Cloudwatch → 이벤트 → 규칙 생성
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 설정하고 규칙을 생성하자
-
구성이 완료되면 패턴 기반으로 이벤트가 4~8.9 발생시 SNS로 알람이 오는 것을 확인할 수 있다.
운영 레벨에서 사용함에 있어 비용적인 측면에서도 고려해야 한다. 23년 6월 기준 30일동안 프리티어로 일정 사용량에 대한 비용을 무료로 제공하고 있다. 문제는 그 다음인데 해당 기능을 위해서는 Cloudtrail, VPC flowlog, EKS runtime 모니터링에 대한 비용을 추가로 산정해야 하기 때문에 도입시 반드시 고려해야 한다.
https://aws.amazon.com/ko/guardduty/pricing/
사고 대응
Amazon GuardDuty를 통해 악의적인 활동 및 이상 동작을 모니터링하고 알람을 거는 방법까지 확인하였다. 다음은 알람의 다음 단계로, 실제 보안 사고 발생시 대응할 수 있는 방법들을 정리한다. 실제 사고가 발생하면 영향을 받는 컨테이너를 폐기하고 교체할지 또는 컨테이너를 격리하고 검사할지 결정해야 한다. 말이 결정이지.. 결국 재발 방지를 위해 컨테이너 격리 및 검사(사고 근본 원인 분석 및 포렌식 조사)가 필수적으로 진행되어야 한다. 사고 대응의 과정은 식별 → 격리 → 포렌식 분석 및 재배포 으로 과정별 대응 계획을 공유하겠다.
식별
가장 먼저 문제가 되는 파드나 노드를 식별하는 것이 중요하다. 식별 방식은 문제 파드나 서비스 계정, 취약 및 손상된 이미지를 기준으로 노드를 식별한다. 아래는 식별 방식별 명령어이다.
|
|
격리
문제 노드나 파드를 식별하였으면 해당 리소스에 대한 격리가 필요하다. 격리 과정은 다음과 같다.
-
네트워크 정책을 통해 식별 파드의 인그래스 & 이그래스 트래픽을 거부시킨다.
다음과 같이 문제있는 파드의 레이블(예제 레이블,
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 및 포트를 확인하는 작업이 필요하다.
-
IAM 보안 자격 증명 취소를 통해 추가 손상을 방지한다.
파드가 다른 AWS 리소스에 액세스할 수 있도록 허용하는 IAM 역할이 할당된 경우 추가 손상을 막기 위해 식별 노드에서 해당 IAM 역할을 제거한다. 이때도 다른 워크로드에 영향을 주지 않고 역할에서 IAM 정책을 안전하게 제거할 수 있는 지 평가가 필요하다.
-
노드 차단을 통해 더 이상 파드가 노드에 예약하지 않도록 설정한다.
kubernetes cordon
명령어를 통해 노드에 더 이상 파드를 예약하지 않도록 한다. 이는 다른 워크로드에 영향을 주지 않고 포렌식 연구를 하기 위함이다. -
종료 방지 기능을 활성화하여 노드 축소 이벤트로 부터 노드를 보호시킨다.
해킹 공격으로 이상 감지의 파드를 지워 문제 원인을 지우려고 시도할 수 있다. 이때 ASG 의 종료 방지 기능을 활용하면, 인스턴스 축소 이벤트로 부터 노드를 보호시킬 수 있다.
포렌식 분석 및 재배포
다음은 원인 분석을 위해 식별 노드에 대한 휘발성 아키텍츠 캡처가 필요하다.
-
노드에서 실행 중인 도커 프로세스와 메모리 및 포트 확인
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
-
작업자 노드에서 컨테이너가 변경되기 전에 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] # 컨테이너와 원본 이미지 간의 차이점 확인
-
포렌식 캡처를 위해 컨테이너를 일시중지하고, 인스턴스의 EBS 볼륨을 스냅샷하자.
노드 IP를 식별하여 해당 노드에 연결된 볼륨에 들어가 스냅샷을 생성하도록 하자.
-
손상된 포드 또는 워크로드 리소스 재배포
분석을 위한 데이터를 수집한 후에는 손상된 포드 또는 워크로드 리소스를 재배포 할 수 있다. 수정 사항을 롤아웃하고 새 교체 파드로 시작한 다음,취약 파드를 삭제하면 끝난다.
침해 대응 시뮬레이션
민첩한 사고 대응을 위해서는 사전의 침해 시뮬레이션이 필요하다. 시뮬레이션을 위한 유틸리티로 https://github.com/cyberark/kubesploit 가 있는데 아래 침해 스택에 따라 모듈을 구성하여 침해 방식을 테스트한다.
https://github.com/cyberark/kubesploit/blob/assets/mitre_pic_full.png
공격 스택이 별로 없는 것 같지만,, k8s 기반의 침해 시뮬레이션 툴 중에 가장 별이 많으며, 그나마 많은 침해 방식 기능을 제공한다. 또한, 깃허브 공식 문서에서는 침해 스택에 따른 대응 방식도 제공하니 나중에 침해 대응 시뮬레이션 진행시 참고하도록 하자.
AWS WAF을 통한 애플리케이션 트래픽 보호
AWS WAF 는 웹 애플리케이션을 일반적인 웹 공격으로부터 보호하는 보안 서비스이다. 이 서비스는 공격자가 취약점을 이용하여 애플리케이션에 접근하거나 데이터를 유출하는 것을 막는다. (참고 : ChatGPT)
AWS WAF는 다음과 같은 주요 기능을 제공한다:
- 사용자 정의 보안 규칙: AWS WAF는 사용자 정의 필터를 생성하여 웹 요청을 허용하거나 차단할 수 있다. 이 필터는 IP 주소, HTTP 헤더, HTTP 바디, URI 문자열, SQL 삽입 공격, 스크립트 교차 사이트 공격 (XSS) 등에 기반한다.
- 비용 효율적인 보안: AWS WAF는 수요에 따라 가격이 책정되므로, 사용자는 실제로 사용하는 만큼만 비용을 지불한다.
- 실시간 지표: AWS WAF는 AWS CloudWatch와 통합하여 웹 트래픽에 대한 실시간 지표를 제공한다. 이를 통해 사용자는 웹 트래픽의 트렌드를 식별하고 필요한 보안 조치를 즉시 취할 수 있다.
- 보안 자동화: 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
해당 서비스에 WAF를 연결하겠다. AWS 콘솔에서 WAF 접근하여 새로운 서비스를 추가하도록 하자. 아쉽지만 WAF 콘솔 진입시 지역이 글로벌로 바뀌어서 그런가 한글 지원이 안된다.
추가 리소스에서 위에서 생성한 ALB를 연결하자.
AWS WAF에서 자체적으로 트래픽 보호를 위한 규칙을 제공한다.. 하지만 아래 그림과 같이 사용에 비용이 발생하니 참고하자. 규칙 또한 다양하고 구성시 설명이 상세하게 나오니 참고하자. 규칙 키워드로 정리하면 IP 주소 차단, SQL Injection 공격 차단, Cross-Site Scripting (XSS) 공격 차단, 유저 봇 차단, 크기 제한 규칙, 지역 블랙리스트, HTTP 헤더, HTTP 메소드, 문자열 매칭, 반복 요청 등이 있고 세부적으로 커스터마이징이 가능하다.
WAF 관측
WAF을 구성하면 메트릭과 로깅 정보를 확인할 수 있다. 메트릭은 기본적으로 제공하지만, 로깅은 WAF 내 기능 탭 Logging and metrics 에서 로깅 기능을 활성화(로그 그룹 생성시 네이밍 aws-waf-logs-
로 시작해야 인식된다.)해야 한다. 활성화를 하면 로깅 관련 쿼리도 제공된다.
WAF 기능 확인
기본적으로 WAF 의 설정된 룰에 조건이 도달되면 해당 IP는 접근이 블록(차단)된다. 상황에 따라 다르지만, 기본적으로 WAF는 각 규칙이 엄격하게 구성되어 있어 원치않는 차단 IP가 나올 수 있다. 이를 대비하기 위해 옵션이 제공되는 데 블록 대신 Count로 모니터링할 수 있는 기능을 제공한다. 기능 탭 Rule에서 규칙을 클릭하고 설정 부분에서 해당 옵션을 활성화시키면 된다.
WAF에서 임계값에 대한 알람 구성 또한 가능하다. Cloudwatch 에서 알람 생성에서 WAF에서 확인한 메트릭 값에 대한 알람 설정을 손 쉽게 할 수 있다.
WAF는 CloudWatch Contributor Insights와 연계하여 특정 조건의 상위 N 기여자를 가져와 대시보드에서 시각화하는 기능을 제공한다. 이를 통해서 상위 차단 IP 주소를 시각화하여 확인할 수 있다. Cloudwatch → contributor Ingihts → 규칙 생성으로 WAF에서 생성한 로그를 지정하고 로그 개수에 대한 필터링을 집계 설정하여 대시보드 구성하면 다음과 같이 확인할 수 있다.
트래픽 테스트를 할 수 없어서 워크샵 결과 화면을 대체한다. 앞 서 생성한 규칙 키에서 설정한 값에 대한 로그 값을 합계하여 Count 값을 확인할 수 있다.