Announcing the EKS Cluster Games - Wiz Blog

Capture The Flag (CTF) 이벤트로 The EKS Cluster Games이 열렸다.

이 게임은 사용자가 해커(?)가 되어 Secrets 같은 것을 탈취하고 얻어낸 Flag를 답으로 제출하면 된다. 만점은 50점으로 5문제가 끝이다!

IMG_2347

와…

1,2번 문제는 1시간 반정도 걸렸고

3번에만 2시간 걸렸다. (4,5번도 꽤나,..)

사실 여기에 힌트 명령어 몇 개가 있다. 쉘 맨 우측 상단에!

Untitled

## k8s 인증 아이콘: k8s identity acquired!
root@wiz-eks-challenge:~# kubectl whoami
system:serviceaccount:challenge1:service-account-challenge1

## 초록 IAM 키: AWS identity acquired!
root@wiz-eks-challenge:~# aws sts get-caller-identity 

Unable to locate credentials. You can configure credentials by running "aws configure".

Untitled 1

평소에는 주로 kubectl get만 썼던거 같다

crane

2번 문제부터는 crane을 사용할 수 있는데, crane이란 컨테이너 이미지를 관리하기 위한 명령어이다. 처음 사용하다 보니 살짝 헤맸고 결국에 사용한 명령어는 crane auth get, crane auth login, crane pull 세 가지였다.

base64

이 게임을 진행하다 보면 답을 얻기 위해 base64 디코딩도 할 줄 알아야 한다. 나는 잘 사용해 보지 않아서 더 헤맨 것도 있다. 하지만 문제 푸는 플로우만 생각하면 디코딩 명령어 쯤이야 챗집이가 잘 알려준다 ㅎㅎ

echo "인코딩된 인증 정보" | base64 --decode

IMDS (Instance Metadata Service)

보안 관련 게임이다 보니, 쿠버네티스도 알아야 하고 AWS IAM에 관해서도 잘알아야 할 것 같다.

이번에 처음 알게 된 키워드가 바로 Instance Metadata Service (IMDS) 이다!

IMDSInstance Metadata Service의 약자로, AWS EC2 인스턴스에서만 사용할 수 있는 웹 서비스라고 한다. IMDS를 통해 EC2 인스턴스는 자신에 대한 메타데이터를 조회할 수 있으며, 아래와 같은 정보를 가져올 수 있다. (POD에서도 사용 가능)

$ curl http://169.254.169.254/latest/meta-data/           
	ami-id
	ami-launch-index
	ami-manifest-path
	autoscaling/
	block-device-mapping/
	events/
	hostname
	iam/
	identity-credentials/
	instance-action
	instance-id
	instance-life-cycle
	instance-type
	local-hostname
	local-ipv4
	mac
	metrics/
	network/
	placement/
	profile
	public-hostname
	public-ipv4
	reservation-id
	security-groups
	services/
	system

참고로 169.254.169.254는 AWS에서 EC2 인스턴스의 메타데이터를 제공하는 특수한 IP 주소이다. 이 주소는 모든 EC2 인스턴스에서 사용할 수 있으며, 인스턴스에서만 접근할 수 있다

IMDS API로 IAM Role을 조회하거나 임시자격증명을 발급받을 수 있는데 즉, 이 인스턴스에 적용된 권한을 탈취해서 사용할 수 있다.

이 메타데이터로 권한 탈취가 가능하니 인스턴스 설정에서 IMDS 사용옵션을 비활성화하거나 IMDSv2로 변경하는 것을 고려해야 한다. 왜냐하면 IMDSv2는 세션 지향 요청을 사용하며 IMDS에 액세스하기 위해 사용될 수 있는 여러 유형의 취약성을 완화하기 때문이다.

자세하게 두 차이를 보면 IMDSv1은 HTTP GET 요청을 통해 액세스하지만, IMDSv2는 PUT 요청으로 세션 토큰을 생성하고 이 토큰을 이용해 GET 요청으로 메타데이터에 액세스할 수 있다.

하지만 가장 근본적으로 중요한건 BP인 필요한 최소권한만 부여해야 한다는 것이다!

참고 링크 ) 인스턴스 메타데이터 서비스 버전 2 작동 방식 - Amazon Elastic Compute Cloud

docker image layer

또 dockerfile로 이미지를 만들게 되면 레이어가 쌓인다는 것 쯤은 알고 있었다. 하지만 이 레이어가 어느 파일에서 확인 가능한지 어디서 확인할 수 있는지는 생각해 보지 않았다.

이 EKS Challenge를 진행하면서 이미지 레이어를 확인해 보았다. 나는 파일을 저장해서 직접 보았지만 inspect 관련 명령어가 있다고 한다. 이미지 레이어 정보는 sha256:로 시작하는 파일에 저장되어 있다. 나는 vi 또는 cat 명령어로 내용만 확인했다

root@wiz-eks-challenge:~/challenge2# cat sha256\:add093cd268deb7817aee1887b620628211a04e8733d22ab5c910f3b6cc91867 | jq .
{
  "architecture": "amd64",
  "config": {
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "/bin/sleep",
      "3133337"
    ],
    "ArgsEscaped": true,
    "OnBuild": null
  },
  "created": "2023-11-01T13:32:18.920734382Z",
  "history": [
    {
      "created": "2023-07-18T23:19:33.538571854Z",
      "created_by": "/bin/sh -c #(nop) ADD file:7e9002edaafd4e4579b65c8f0aaabde1aeb7fd3f8d95579f7fd3443cef785fd1 in / "
    },
    {
      "created": "2023-07-18T23:19:33.655005962Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"sh\"]",
      "empty_layer": true
    },
    {
      "created": "2023-11-01T13:32:18.920734382Z",
      "created_by": "RUN sh -c echo 'flag 내용' > /flag.txt # buildkit",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2023-11-01T13:32:18.920734382Z",
      "created_by": "CMD [\"/bin/sleep\" \"3133337\"]",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:3d24ee258efc3bfe4066a1a9fb83febf6dc0b1548dfe896161533668281c9f4f",
      "sha256:a70cef1cb742e242b33cc21f949af6dc7e59b6ea3ce595c61c179c3be0e5d432"
    ]
  }
}

history 블록의 empty_layer는 도커 이미지의 레이어 중에서 파일 시스템의 변경사항이 없는 레이어로, RUN, COPY, ADD와 같은 명령이 아닌 CMD, ENTRYPOINT, ENV, EXPOSE, VOLUME 등의 명령에 의해 생성된다. 파일 시스템의 변경사항이 없어 이미지 크기에는 영향이 없지만 이미지의 메타데이터는 변경한다.

이번 계기로 처음 도커 이미지의 메타데이터를 확인해 본 것 같다.

token

Untitled 2

그림 출처 ) https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cluster-auth.html

쿠버네티스 클러스터에 대한 인증을 진행할 때 위와 같은 플로우를 가지게 된다. 인증은 aws eks get-token 명령어 또는 AWS IAM Authenticator을 설치해서 진행할 수 있다 (aws eks get-token 명령어로 얻은 토큰은 aws-iam-authenticator server를 통해 AWS IAM에 인증받을 수 있다)

참고 링크 ) https://github.com/kubernetes-sigs/aws-iam-authenticator

클라이언트가 API 서버에 요청을 보내면서 함께 토큰을 전달한다. 토큰의 유효성을 AWS STS를 통해 검증받고 RBAC에 따라 작업을 수행하거나 거부될 수 있다. 인증 토큰을 사용하면 EKS에서는 sts:GetCallerIdentity API 엔드포인트를 호출한다고 한다.

aws eks get-token --cluster-name

토큰은 위 명령어로 생성할 수 있다. 임시 보안 자격 증명이라 expirationTimestamp가 같이 발급된다.

Untitled 3

그림 출처 ) https://ssup2.github.io/theory_analysis/AWS_EKS_인증/

그림에 대한 설명은 아래와 같다

  1. 클라이언트에서 aws eks get-token 명령을 수행한다.
  2. AWS STS의 Presigned URL을 생성하고 인코딩하여 토큰을 생성한다.

    내가 사용한 부분은 토큰 값인데 k8s-aws-v1 이라는 프리픽스를 떼서 디코딩해 보면 Presigned URL을 확인할 수 있다고 한다. 이는 Action, Version, X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-SignedHeaders, X-Amz-Signature이 파라미터 값으로 들어가 있는데 Action=GetCallerIdentity 말고는 모르겠다!

    참고로 GetCallerIdentity API를 호출하려면 AccessKey/SecretKey가 필요하지만 Presigned URL에서 idetity 자격증명이 되어서 시크릿 정보 없이도 호출이 가능하다고 한다!

    왜냐하면 해당 URL을 x-k8s-aws-id: cluster명으로 쿼리해 보면 유저정보를 알 수 있기 때문!

  3. 아무튼 삼번에서 Presigned URL 얻고
  4. 토큰 발급받으면
  5. 이 토큰으로 클러스터에 요청한다
  6. 클러스터는 AWS IAM Authenticator에게 요청하고
  7. 최종적으로 AWS STS GetCallerIdentity API에게 Identity를 요청한다
  8. 그럼 IAM User/Role를 확인하고
  9. aws-auth configmap에 EKS 클러스터 User/Group을 등록한다

IAM을 활용해 사용자를 인증하고 Kubernetes의 RBAC에 정의된 권한에 따라 해당 명령을 인가하게 된다. (IRSA, IAM Roles for Service Accounts)

참고 링크 ) Amazon EKS 마이그레이션 요점정리 - 강인호 솔루션즈 아키텍트, AWS :: AWS Summit Korea 2022

이 플로우에 대한 이해도가 낮아서 문제를 식별하고 풀어나가는 데에 좀 시간이 오래 걸렸다… 사실 지인 힌트를… 받기는 했다 ^^* (이제 알면 됐지ㅎㅎ!)

AssumeRoleWithWebIdentity

assume role 내용이 가장 어렵지 않을까 한다

assume role은 작업을 수행하는 동안에만 특정 role을 잠시 동안 사용하여 최소 권한 원칙을 따른다. AssumeRoleWithWebIdentity(웹 ID 토큰 인증)을 호출해서 STS (Security Token Service)에서 발급받은 토큰을 통해 EKS Idp가 IAM Role을 위임(assume)받을 수 있게 된다. (IRSA, IAM Roles for Service Accounts)

참고 링크 ) EKS에서 쿠버네티스 포드의 IAM 권한 제어하기: Pod Identity Webhook

kubectl create token debug-sa --audience sts.amazonaws.com
aws sts assume-role-with-web-identity --role-arn arn:aws:iam::688655246681:role/challengeEksS3Role --role-session-name eks-session --web-identity-token $TOKEN

debug-sa가 가진 토큰으로 sts.amazonaws.com에서 자격증명을 인증받아 토큰과 role arn을 지정해서 권한을 위임받을 수 있게 된다.

OIDC Provider(EKS Idp)를 통해 IAM의 임시 토큰을 발급받을 수 있도록 audiencests.amazonaws.com을 추가해 주는 것이다. 아 그리고 토큰을 통해 IAM Role 권한을 획득하기 위해 OIDC Provider(EKS Idp)와 IAM Role 간의 신뢰관계가 설정되어 있어야 한다

Untitled 4

그림 출처 ) https://aws.amazon.com/ko/blogs/containers/diving-into-iam-roles-for-service-accounts/

위 플로우는 AssumeRoleWithWebIdentity를 통해 인증받아 IRSA를 적용하는 것이다.

딱 지금이랑 알맞! POD에서 S3를 조회하기 위해 IAM에 인증받고 IAM과 OIDC Provider 간의 신뢰관계가 형성되어 있어 SA는 IAM Role을 위임받고 S3에 접근할 수 있게 된다!

결론…..(?)

EKS Challenge 문제는 몇시간에 걸쳐 도움도 받고 어쩌고 해서 문제를 풀어냈는데, 정작 이를 정리하는 데에는 몇날며칠이 걸린듯하다… 이는 내가 AWS IAM에 대한 이해도가 떨어져서라고 생각한다 ^.ㅠ

이번 계기로 IRSA에 대해 한발자국 다가간 느낌이다

IRSA도 결국 최소 권한 원칙을 베이스로 갖고 있기 때문에 다시 한번 최소 권한 원칙에 대해 되새김하는 시간이었다.

공부하자 아자!

Leave a comment