[FLO-Tech] AWS EKS에서의 IAM 역할 분리


안녕하세요. 드림어스컴퍼니 Bernard 입니다.

FLO에서는 여러 팀이 공용으로 사용하는 쿠버네티스 클러스터를 AWS EKS (Elastic Kubernetes Service) 로 구축했습니다. 공용 클러스터를 관리하면서 팀별로 독립적인 권한을 유지하려고 했고, 이 과정에서 AWS IAM (Identity and Access Management) 권한 관리 이슈를 접하게 되었습니다. 팀별 또는 서비스별로 AWS IAM 역할을 분리하기 위해 도입한 방안들과 그 방안들의 동작 방식에 대해서 공유하려고 합니다.





들어가며

사내에서 여러 팀들이 쿠버네티스를 활용하고자 할 때, 클러스터를 구축하는 방안은 다음과 같이 구분할 수 있습니다.



저희는 공용 클러스터를 사용하는 것이 사내 개발 방침에 적합하다고 판단했습니다. 하나의 클러스터를 여러 팀이 사용할 때 서로의 간섭을 줄이기 위해서는 자원과 권한의 분리가 잘 이루어져야 합니다. 쿠버네티스에서는 팀별로 독립된 환경을 제공하는 Namespace와 컨테이너를 실행하는 계정인 ServiceAccount 를 활용하여 해결할 수 있습니다.



AWS EKS로 구축한 경우에는 AWS IAM 역할의 분리에 대해서도 고려해야 합니다. EKS의 작업 노드인 EC2 인스턴스에는 작업 노드로서 동작하기 위한 권한이 필요하므로, IAM 인스턴스 프로파일을 사용하여 IAM 역할을 부여합니다. EC2 인스턴스에 부여된 이 IAM 역할은 해당 인스턴스에서 구동되는 모든 컨테이너가 사용할 수 있습니다. 하나의 작업 노드에 팀 구분 없이 컨테이너가 실행되므로, 모든 팀들이 이 IAM 역할을 공유하게 됩니다. 이것은 팀별로 권한 분리가 제대로 이루어지지 않음을 뜻합니다.

이러한 환경에서, 팀별로 IAM 역할을 분리하기 위해 다음과 같은 방안을 공유하고자 합니다.


1. IAM 인스턴스 프로파일이 동작하는 방식에 대해 소개하고, IAM 인스턴스 프로파일을 Namespace에 따라 선택적으로 이용할 수 있는 방안에 대해 소개하겠습니다.


2. IAM 인스턴스 프로파일을 사용하지 않는다면, 컨테이너에서 IAM 사용자 접근 키를 직접 관리해야하는 번거로움이 생길 수 있습니다. 이런 번거로움을 피할 수 있도록 쿠버네티스 ServiceAccount와 AWS IAM 역할을 연동하는 방안에 대해 소개하겠습니다.





EC2 인스턴스 메타데이터 서비스 (IMDS)

EC2 인스턴스에 IAM 역할을 부여하기 위해 IAM 인스턴스 프로파일을 설정하면, 사용자가 직접 credential을 설정하지 않고도 AWS CLI나 SDK를 이용할 수 있습니다. 이때 EC2 인스턴스 메타데이터 서비스(IMDS)가 credential을 관리합니다. AWS SDK에서 IMDS를 이용하여 credential을 획득하고 AWS API를 호출하는 도식은 아래와 같습니다.



1. IMDS는 EC2 내에서 `http://169.254.169.254/`로 접근할 수 있습니다. EC2 인스턴스의 정보나 사용자 데이터를 확인할 수 있으며, 인스턴스 프로파일을 설정하는 경우 AWS credential도 획득할 수 있습니다.


2. IMDS에서 획득한 credential로 AWS API를 호출할 수 있습니다.



EC2 내에서 실행되는 컨테이너들도 같은 방식으로 IMDS에 접근하여 credential을 획득할 수 있고, 인스턴스에 부여된 권한도 이용할 수 있습니다. 컨테이너에서 이 권한을 사용하지 않도록 하기 위해서는 컨테이너에서의 IMDS 접근을 차단할 필요가 있습니다. 아래 명령어는 `iptables`를 이용하여 작업 노드에서 실행되는 모든 컨테이너가 IMDS에 접근하지 못하도록 합니다.



# https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/restrict-ec2-credential-access.html
yum install -y iptables-services
iptables --insert FORWARD 1 --in-interface eni+ --destination 169.254.169.254/32 --jump DROP
iptables-save | tee /etc/sysconfig/iptables
systemctl enable --now iptables




Project Calico 통한 네트워크 정책 관리

앞에서는 모든 컨테이터에서 IMDS로 접근하지 못하도록 설정했습니다. 하지만 컨테이너에서 IMDS 접근이 필요한 경우도 있습니다. AWS CloudWatch Container Insights라는 모니터링 도구를 활용하는 경우 쿠버네티스에 CloudWatch 에이전트를 구동하게 되는데, 에이전트는 IMDS에 접근해서 모니터링에 필요한 정보를 가져옵니다. 그리고 Cluster Autoscaler나 External DNS 등의 시스템을 구성하는 경우, 각각의 IAM 역할을 별도로 관리하기보다 EC2 인스턴스 프로파일에서 같이 관리하는 것이 편리하기도 합니다. 이러한 상황에서는 IMDS로의 접근을 모두 막는 보다, 필요한 조건에 따라 조절하는 것이 좋습니다.


그렇게 하기 위해, 쿠버네티스 네트워크 정책 엔진으로 Project Calico를 활용하여 Namespace별로 IMDS로의 접근을 제어하는 방법에 대해 알아보겠습니다.


쿠버네티스는 네트워크의 동작 방식에 대한 모델을 정의해두고, 환경에 맞는 구현체를 사용할 있게 되어있습니다. EKS 의 경우 네트워크 구현체로서 amazon-vpc-cni-k8s사용하여, AWS VPC 내에서 컨테이너의 IP를 할당받고 네트워크 인터페이스를 활용할 있도록 하고 있습니다. Project Calico는 컨테이너 네트워크 솔루션으로서 쿠버네티스 네트워크 모델의 구현체로서 사용되기도 하지만, 네트워크 정책 엔진 모듈만 따로 이용할 수도 있습니다. EKS에는 별도의 네트워크 정책 엔진이 없기때문에, ProjectCalico의 네트워크 정책 엔진을 설치하여 사용하도록 안내 되어 있습니다.


Project Calico를 설치하면, 각 작업 노드마다 calico/node 컨테이너가 실행됩니다. 이 컨테이너에서는 생성된 네트워크 정책에 따라 `iptables`를 활용해 네트워크를 제어합니다.

Project Calico를 이용하면 클러스터 전역에 대한 네트워크 정책을 생성할 수 있습니다. 기본적으로 밖으로 나가는 트래픽을 허용하되 IMDS로의 트래픽은 거부하도록 설정할 수 있습니다. 또한 앞에서 언급했던 서비스들이 실행되는 Namespace에서는 IMDS 접근을 허용할 수 있습니다. 다음은 이와 같은 내용을 반영한 네트워크 정책입니다.




 
# 아래 내용은 kubectl로 생성합니다. calicoctl을 사용할 때와 다르게 형식 검증이 수행되지 않으며, apiVersion이 다릅니다.
# 참고: https://docs.projectcalico.org/reference/resources/globalnetworkpolicy
---
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: egress-deny-instance-metadata-service
spec:
  order: 0.5
  types:
    - Egress
  egress:
    - action: Allow
      source:
        selector: projectcalico.org/namespace in {'kube-system', 'amazon-cloudwatch', 'external-dns'}
      destination:
        nets:
          - 169.254.169.254/32
    - action: Deny
      destination:
        nets:
          - 169.254.169.254/32
---
apiVersion: crd.projectcalico.org/v1
kind: GlobalNetworkPolicy
metadata:
  name: egress-allow
spec:
  order: 1
  types:
    - Egress
  egress:
    - action: Allow



쿠버네티스 ServiceAccount와 AWS IAM 역할 연동

이번에는 쿠버네티스 ServiceAccount와 AWS IAM 역할을 연동하는 방법에 대해서 알아보겠습니다. 이 방안을 활용하면, IAM 사용자 접근 키를 직접 관리하지 않고도 팀별 혹은 서비스별로 IAM 역할을 분리하여 사용할 수 있습니다.


이 AWS 문서는 EKS에서 연동하는 방법에 대해 안내하고 있습니다. 설명의 내용을 요약하면 다음과 같습니다.


1. EKS에 생성되어있는 OpenID Connect 발행자를 AWS IAM에 등록합니다.


2. 팀에서 사용할 IAM 역할을 생성합니다. 그 역할을 사용할 수 있는 신뢰 관계로서 위의 OpenID Connect 발행자를 설정하고, 역할을 사용할 ServiceAccount를 지정합니다.


3. ServiceAccount에 위의 IAM 역할을 annotation으로 지정합니다.


4. 해당 ServiceAccount로 Pod을 실행하면, AWS CLI나 SDK에서 위의 IAM 역할을 사용할 수 있습니다.


이 연동의 동작 방식에 대해 두 가지 관점에서 알아보겠습니다. 먼저 쿠버네티스와 AWS IAM 사이의 인증 연동 방식에 대해서 알아보겠습니다. 인증 연동은 Open ID Connect(OIDC)를 통해 이루어지는데, 과정은 아래 도식과 같습니다.





1. 쿠버네티스는 연결된 OIDC를 통해 Pod에 토큰을 발급합니다. 이 토큰은 OIDC의 ID Token으로서 JWT로 되어있습니다. 이 토큰에는 토큰을 발급한 OIDC 정보와, 토큰의 사용자인 ServiceAccount 정보가 포함되어 있으며, AWS STS에서만 사용할 수 있도록 설정되어 있습니다.


2. Pod에서 실행된 AWS CLI나 SDK는 해당 토큰과 함께 ServiceAccount에 지정된 IAM 역할을 AWS STS에 전달합니다.


3. AWS STS는 토큰을 발급한 OIDC에 요청하여 토큰을 검증합니다. 또한, 토큰에 포함된 ServiceAccount 정보와 사용할 IAM 역할에 지정된 조건을 비교하여 검증합니다.


4. AWS STS는 검증 완료 후, AWS API를 이용할 수 있는 credential을 반환합니다.



이번에는 쿠버네티스 내에서 OIDC 토큰을 Pod에 발급하는 과정에 대해서 알아보겠습니다. 이 과정은

pod-identity-webhook 을 통해 이루어지며, EKS에는 기본으로 설치되어있습니다. pod-identity-webhook은 쿠버네티스에 MutatingWebhook을 설정하는데, MutatingWebhook은 쿠버네티스 리소스가 생성되거나 변경될 때, 해당 리소스를 조작하기 위해 호출되는 웹훅입니다. 이 웹훅을 통해 토큰이 발급되는 방식은 다음과 같습니다.




1. Pod 생성 요청이 있을 때, MutatingWebhook 설정에 의해 pod-identity-webhook이 실행됩니다.


2. pod-identity-webhook은 Pod에 할당된 ServiceAccount에 IAM 역할이 지정되어 있으면 다음과 같이 Pod을 조작합니다.


먼저, Pod에 ServiceAccountTokenVolumeProjection 을 설정합니다.



이렇게 발급받을 토큰과, ServiceAccount에 지정된 IAM 역할이 컨테이너의 환경 변수로 할당되도록 Pod을 설정합니다.


1. pod-identity-webhook에 의해 조작된 Pod이 생성될 때, 쿠버네티스는 Pod의 ServiceAccountTokenVolumeProjection 설정에 따라 OIDC를 통해 Pod에 토큰을 발급합니다.


2. Pod에서 동작하는 AWS CLI나 SDK에서는 지정된 환경 변수로부터 IAM 역할과 토큰을 확인하고, 이 값을 AWS STS에 전달하여 AWS credential을 가져옵니다.







정리하며

지금까지 소개한 내용을 바탕으로 EKS에서 팀별 혹은 서비스별로 IAM 역할을 분리하는 방식에 대해서 정리해보겠습니다.

Pod에서는 EC2 인스턴스 메타데이터 서비스(IMDS)를 통해 EC2 인스턴스에 할당된 IAM 역할을 의도치 않게 사용할 수 있습니다. 이를 제어하기 위해 Project Calico의 네트워크 정책 엔진을 이용하여, 특정 Namespace에서만 IAM 인스턴스 프로파일을 사용할 수 있도록 설정했습니다. 그리고 OpenID Connect를 기반으로 쿠버네티스와 AWS IAM 사이의 인증을 연동하여, 별도의 사용자 접근 키를 관리하지 않고 쿠버네티스 ServiceAccount별로 IAM 역할을 사용할 수 있도록 했습니다.

이 글이 쿠버네티스 클러스터에서 IAM 권한을 분리하여 관리하는데 도움이 되고, 더 나아가, 여러 사용자가 있는 두 시스템의 인증을 연동하는 방식으로서 참고가 되기를 바랍니다!



© DREAMUS COMPANY ALL RIGHTS RESERVED.