IT/AWS

[AWS] EKS내부 pod가 Node의 IAM Role 권한 가져오지 못하는 이슈 (feat.IMDSv2)

반응형

이슈

EKS cluster에서 생성한 pod가 Node의 IAM Role 권한을 가져오지 못하고 있었습니다. 이슈를 트래킹 해보니 우선 첫번째로 pod, node모두에서 메타데이터를 호출하는 ipv4 주소 curl -v 169.254.169.254 를 호출하지도 못하고 있었습니다. 두번째로는 aws sts get-caller-identity 로 권한 조회도 되지 않았습니다.

원인1 - EC2가 IMDSv2로만 설정이 되어있었다

variable "metadata_options" {
  description = "Customize the metadata options for the instance"
  type        = map(string)
  default = {
    http_endpoint               = "enabled"
    http_tokens                 = "required"
    http_put_response_hop_limit = 2
  }
}
  • 위에 http_tokens파라미터 의 value가 “required” 로 되어있으면 IMDSv2만 사용하겠다는 설정입니다.

IMDS란?

instance metadata service 의 약어로 EC2가 metadata를 가져오는 방법을 말합니다. EC2의 metadata에는 EC2 type, instance id, iam role 등등 EC2에 대한 모든 metadata정보를 갖고 있습니다.

IMDSv1(버전1) 과 IMDSv2(버전2) 가 있는데, EC2 instance가 지속적으로 metadata를 들고 있으면 보안상이슈가 될 수 있으므로 세션 방식을 사용하는 IMDSv2 방식이 생겨 났습니다. 이 IMDSv2 방식은 일시적인 token 값을 받아와서 해당 token 값으로 metadata를 조회할 수 있게 만든 방식입니다.

IMDSv2 로만 설정되면 무슨일이?

EC2는 metadata값을 169.254.169.254 주소를 사용하여 가져오게 됩니다. 그런데 IMDSv2로만 사용하게되면 169.254.169.254 를 통해 metadata호출이 안됩니다. curl -v 169.254.169.254 를 해보면 401 에러 코드를 확인할 수 있습니다. 이 metadata안에는 EC2에 설정되어있는 IAM Role의 정보도 포함되어있기 때문에 어떤 Role이 할당되어있는지 확인을 할 수가 없게됩니다. 위에서 설명 드린것처럼 IMDSv2는 token 값을 가져와서 해당 token값으로 metadata를 조회할 수 있게 하는 방법인데, token값을 가져올때 아래와 같이 169.254.169.254 주소를 사용하게 됩니다. 하지만 IMDSv2만 사용하게 되어있어서 해당 token값도 못가져오게 되는것이죠.

TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`

그럼 어떻게 해야하나요?

그래서 IMDSv1, IMDSv2 모두 사용할 수 있게 그러니까 둘중하나를 선택적(optional)으로 사용할 수 있게 EC2에 설정해줘야 합니다. 위의 terraform EKS 모듈을 사용해서 이미 IMDSv2로만 사용하게 되어있다면 아래와 같이 aws cli명령어를 통해서 설정할 수 있습니다.

aws ec2 modify-instance-metadata-options \
--instance-id i-00c7bbdc9a9c0ad36 \
--http-tokens optional \
--http-endpoint enabled

이미 생성되어있는건 위에서 처럼 설정한다 치고 terraform EKS모듈을 계속 사용해야한다면 애초에 모듈의 metadata default 값을 바꾸면 되겠죠 http_tokens="required"http_tokens="optinal" 로요.

variable "metadata_options" {
  description = "Customize the metadata options for the instance"
  type        = map(string)
  default = {
    http_endpoint               = "enabled"
    http_tokens                 = "optional"
    http_put_response_hop_limit = 2
  }
}

자 그럼 EC2가 metadata를 호출할 수 있게 되었습니다. 그럼 pod에 들어가서 aws sts get-caller-identity 를 했을때 node가 할당된 iam role이 조회되나 볼까요. 예상대로 라면 아래와 같이 출력되어야 합니다. 그런데 조회가 안되었어요 왜그럴까요?? 해당 cli를 날렸을때 어떤 return 값도 오지 않았습니다. 말그대로 hang상태로 계속 유지되었어요.

{
    "UserId": "AAAAAAAAaRWDPEQWX5XCL",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/test"
}

아마도… 네트워크 이슈인가?

timeout시간이 어찌나 긴지 아무리 기다려도 timeout return값도 오지 않았습니다. 어쨋든 네트워크 이슈로 판단하고 subnet과 routing, vpcendpint, security group 그리고 혹시나 내부 어플라이언스 방화벽(팔로알토)장비를 거치건 아닌지 모두 체크를 해봤습니다. 결론은 외부 인터넷을 나가도록 routing 설정을 하면 aws sts get-caller-identity 에 대한 출력값이 원하는대로 보여졌습니다. 하지만 외부 인터넷으로 나가지 않도록 하면 다시 호출되지 않았습니다. 보안 정책상 외부로 트래픽이 나가지 않아야 하기 때문에 sts vpcendpint를 생성하고 이 endpint를 타야 합니다. sts vpcendpoint를 자세히 살펴봐도 문제가 없었고 여기에 붙어있는 security group의 문제도 아니었습니다. 그럼 무엇이 문제였냐면.. 두둥..

원인2 - AWS_DEFAULT_REGION 설정이 되어있지 않았다

원인은 EC2 환경변수에 AWS_DEFAULT_REGION 설정이 서울리전 ap-northeast-2로 설정되어있지 않아서 발생한 것이었습니다. EC2는 AWS_DEFAULT_REGION 설정이되어 있지 않으면 default가 us-east-1의 endpoint를 타게 되어있습니다. 즉, aws sts get-caller-identity 명령어를 날릴때sts.ap-northeast-1.amazonaws.com 를 호출한다는 것이죠. 그래서 aws sts get-caller-identity --region=ap-northeast-2 라고 region을 명시하면 원하는 출력값을 확인할 수 있습니다. 그런데 매번 region 파라미터를 주기가 번거로우니 EC2의 환경변수 값을 아래와 같이 설정하면 모든게 해결 됩니다.

export AWS_DEFAULT_REGION=ap-northeast-2

그럼 다시 pod에서 node의 IAM Role을 잘가져오나 확인해볼까?

이제 원인1, 원인2 모두 해결하였으니 확인해보겠습니다. kubectl 명령어로 pod에 접근해서 aws sts get-caller-identity 를 날려보겠습니다. 어라? 똑같이 hang이 걸립니다. node에서 설정한 환경변수 export AWS_DEFAULT_REGION=ap-northeast-2 설정은 pod가 가져오지 못하더랍니다..

따라서 pod에서 aws cli를 사용하거나 aws sdk를 사용한다면 docker image에 환경변수로 export AWS_DEFAULT_REGION=ap-northeast-2 를 설정하던가.. 아니면 반드시 region을 명시해야 합니다!! 아래와 같이 말이죠..

AmazonEC2 ec2 = AmazonEC2ClientBuilder.standard()
                    .withRegion(Regions.US_WEST_2)
                    .build();

참고로

참고로 EKS의 pod한테 IAM Role을 부여하고 싶을때 위와 같이 node의 role을 가져올 수 있도록 할 수 도 있지만 OIDC를 생성하여 pod에 serviceaccount를 할당할 수 있습니다. 사실 이게 더 보안적으로 안전하죠. 아마도.. terrform EKS 모듈 개발자(우크라이나 형님)분이 보안 이슈를 없애기위해 애초에 IMDSv2로만 사용할 수 있게해서 metadata를 조회하지 못하게 일부로 막지 않았나 싶네요. 무조건 pod에 serviceaccount를 할당해서 사용하도록 권장하려구 말이죠…

반응형