클라우드

ECS 태스크 역할과 실행 역할, 무엇이 다를까

ECS 태스크 정의에는 역할을 넣는 칸이 두 개 있다.
실행 역할은 이미지와 비밀 값, 로그를 다루는 ECS 에이전트가 쓰고, 태스크 역할은 컨테이너 안의 앱 코드가 쓴다.

이 글의 목차역할은 AWS에 요청할 때 쓰는 신원이다6
  1. 역할은 AWS에 요청할 때 쓰는 신원이다
  2. 컨테이너를 준비하는 쪽: 실행 역할
  3. 앱 코드가 쓰는 쪽: 태스크 역할
  4. 두 역할을 하나로 합치면
  5. 권한 오류가 났을 때 먼저 볼 곳
  6. 서비스마다 두 역할을 따로 만든다

컨테이너를 실행하고 관리해 주는 AWS 서비스인 ECS에서는 먼저 태스크 정의를 만든다.
어떤 이미지를 어떤 설정으로 실행할지 적는 명세이고, 이 명세대로 띄운 실행 단위를 태스크라고 한다.

태스크 정의에는 역할을 지정하는 칸이 두 개 있다.
태스크 역할과 태스크 실행 역할이다.

이름만 보면 둘 다 이 컨테이너의 권한처럼 들린다.
그래서 처음에는 필요한 권한을 한 역할에 몰아넣고 두 칸에 같은 역할을 고르거나, 한 칸을 비워 뒀다가 권한 오류를 만나곤 한다.

두 칸이 왜 따로 있는지는 역할이 무엇인지부터 알아야 보인다.

역할은 AWS에 요청할 때 쓰는 신원이다

앱이 S3에 파일을 올리거나 DynamoDB에서 데이터를 읽을 때는 AWS API에 요청을 보낸다.
이 요청에는 유효한 자격 증명으로 서명을 붙여야 AWS가 누가 보냈는지 알 수 있다.

IAM 역할은 이런 요청에 쓰는 신원의 한 종류다.
IAM 사용자와 달리 역할에는 비밀번호나 액세스 키 같은 장기 자격 증명이 붙어 있지 않다.
역할을 맡으면 정해진 시간 동안만 쓸 수 있는 임시 자격 증명을 받는다.

그 신원으로 무엇을 할 수 있는지는 역할에 붙인 정책이 정한다.
정책이 허용하지 않은 요청은 기본으로 거부되므로, 특정 S3 버킷을 읽는 정책만 붙인 역할로는 그 버킷만 읽을 수 있다.

역할 정책에 따른 요청 허용과 거부 역할을 맡아 받은 임시 자격 증명으로 서명한 요청은 정책이 허용한 작업과 자원에만 통과하고, 나머지는 기본으로 거부된다. 개념 도해 역할 정책에 따른 요청 허용과 거부 요청이 지나는 경로 ① 역할 맡기 장기 키 없음 ② 임시 자격 증명 정해진 시간만 유효 ③ 서명한 요청 S3 · DynamoDB API ④ 정책 확인 허용한 것만 통과 정책: 버킷 A 읽기만 허용 버킷 A 버킷 B 읽기 요청 허용 거부 허용 문장 없음 · 기본 거부 쓰기 요청 거부 허용 문장 없음 · 기본 거부 거부 허용 문장 없음 · 기본 거부 역할을 맡아 받은 임시 자격 증명으로 서명한 요청은 정책이 허용한 작업과 자원에만 통과하고, 나머지는 기본으로 거부된다. SOSHIN · DEV NOTES
허용 문장이 있는 칸 하나만 통과하고, 같은 버킷이라도 쓰기 요청은 거부된다.

태스크 하나 안에서도 AWS에 요청을 보내는 쪽은 둘이다.

컨테이너를 준비하는 쪽: 실행 역할

컨테이너가 뜨기 전에도 AWS로 가는 요청이 있다.
비공개 이미지 저장소인 ECR에서 이미지를 내려받고, Secrets Manager 같은 곳에 둔 비밀 값을 꺼내 환경 변수로 넣어 주는 일이다.

이 요청을 보내는 쪽은 태스크를 준비하고 관리하는 ECS 에이전트다.
이때 앱 코드는 아직 실행되기도 전이다.
EC2에서 돌리면 인스턴스 안의 컨테이너 에이전트가, 서버 관리를 AWS에 맡기는 Fargate에서는 Fargate 에이전트가 이 일을 한다.

실행 역할은 이 에이전트들이 AWS API를 부를 때 쓰는 역할이다.
컨테이너 로그를 CloudWatch Logs로 보내는 일도 여기에 들어간다.

로그는 컨테이너가 도는 내내 보내므로, 실행 역할을 ‘시작 전에만 쓰는 권한’⁠으로 외우면 틀린다.
언제 쓰느냐보다 누가 요청하느냐로 구별해야 정확하다.

실행 역할의 임시 자격 증명은 ECS가 에이전트에게 주기적으로 보내 준다.
태스크 안의 컨테이너는 이 자격 증명에 직접 접근하지 못한다.

요청 주체에 따른 두 역할의 사용 구간 실행 역할은 에이전트가 시작 전부터 로그를 보내는 동안까지 쓰고, 태스크 역할은 앱 코드가 AWS를 부를 때 쓴다. 개념 도해 요청 주체에 따른 두 역할의 사용 구간 태스크가 도는 동안의 요청 Fargate · awslogs 로그 기준 ① 시작 전 ② 컨테이너 시작 ③ 실행 중 ECS 에이전트 실행 역할 앱 코드 태스크 역할 이미지 내려받기 ECR 비밀 값 가져오기 Secrets Manager 로그 보내기 CloudWatch Logs · 실행 중 내내 앱 코드 실행 전 S3 · DynamoDB 요청 앱 코드가 부를 때마다 시간 역할별 자격 증명을 쓰는 쪽 ECS 에이전트 앱 코드 실행 역할 사용 접근 못 함 태스크 역할 쓰지 않음 사용 실행 역할은 에이전트가 시작 전부터 로그를 보내는 동안까지 쓰고, 태스크 역할은 앱 코드가 AWS를 부를 때 쓴다. SOSHIN · DEV NOTES
로그 보내기는 컨테이너가 시작된 뒤에도 이어지지만, 요청하는 쪽이 에이전트라서 실행 역할을 쓴다.

앱 코드가 쓰는 쪽: 태스크 역할

앱 코드가 직접 S3나 DynamoDB를 부를 때 쓰는 것은 태스크 역할이다.
이 역할의 권한은 컨테이너로 전달되고, 에이전트는 이 권한을 자기 일에 쓰지 않는다.

전달은 에이전트가 맡는다.
태스크에 역할을 붙이면 에이전트가 그 태스크만의 자격 증명을 만들고, 컨테이너의 환경 변수에 그 자격 증명을 받아 갈 주소를 넣어 준다.
최근 AWS SDK는 이 주소를 알아서 읽는다.

이렇게 받은 자격 증명은 기본적으로 여섯 시간 동안 유효하고, 그 뒤에는 에이전트가 자동으로 교체한다.
이미지나 환경 변수에 오래 쓰는 키를 넣어 둘 이유가 없어진다.

같은 컨테이너라도 요청한 주체가 다르다

앱의 파일 읽기 권한과 에이전트의 로그 전송 권한은 붙인 역할에 따라 동작이 달라진다.

앱 · 태스크 역할

로그 전송 권한

파일 읽기 실패

에이전트 · 실행 역할

파일 읽기 권한

로그 전송 실패

앱이 읽는 파일은 태스크 역할, 에이전트가 보내는 로그는 실행 역할의 권한을 사용한다.

서버 관리를 맡기는 Fargate에서 앱의 파일 읽기와 awslogs 방식의 로그 전송만 비교한다. 다른 시작 조건은 갖췄다고 가정한다.

두 역할을 하나로 합치면

합쳐도 동작은 한다.
에이전트도 앱도 필요한 권한을 한 역할에서 찾을 수 있으니, 처음에는 문제가 드러나지 않는다.

문제는 앱 코드가 에이전트의 권한까지 갖게 된다는 점이다.
실행 역할용으로 AWS가 제공하는 관리형 정책은 ECR 이미지 내려받기와 로그 쓰기 권한을 계정의 모든 자원을 대상으로 허용한다.

이 역할을 태스크 역할로도 쓰면 앱은 계정 안 어느 저장소의 이미지든 내려받을 수 있게 된다.
실행 역할에 비밀 값을 꺼내는 권한을 넓게 붙여 뒀다면 그 비밀 값들도 앱이 꺼낼 수 있다.

이 차이는 앱에 문제가 생겼을 때 드러난다.
코드에 취약점이 있거나 내려받은 라이브러리가 오염됐다면, 공격자는 그 프로세스가 가진 자격 증명으로 할 수 있는 일을 한다.
피해가 번지는 범위는 태스크 역할에 든 권한이 정한다.

역할 구성에 따른 앱 코드의 권한 범위 두 역할을 하나로 합치면 앱 코드가 에이전트의 권한까지 쓰게 되어, 앱이 뚫렸을 때 닿는 범위가 그만큼 넓어진다. 개념 도해 역할 구성에 따른 앱 코드의 권한 범위 두 칸에 같은 역할을 고른 경우 앱 코드 태스크 역할 칸 ECS 에이전트 실행 역할 칸 합친 역할 하나 두 주체가 같은 권한을 씀 버킷 A 읽기 앱에 필요한 권한 이미지 내려받기 계정의 모든 저장소 로그 쓰기 계정의 모든 로그 비밀 값 꺼내기 넓게 붙였다면 앱 코드가 쓸 수 있는 권한 역할을 나눔 태스크 역할만 버킷 A 읽기 역할을 합침 두 칸에 같은 역할 버킷 A 읽기 이미지 내려받기 로그 쓰기 비밀 값 · 붙였다면 두 역할을 하나로 합치면 앱 코드가 에이전트의 권한까지 쓰게 되어, 앱이 뚫렸을 때 닿는 범위가 그만큼 넓어진다. SOSHIN · DEV NOTES
앱 코드에 필요한 권한은 버킷 A 읽기 하나인데, 역할을 합치면 이미지 내려받기와 로그 쓰기까지, 넓게 붙였다면 비밀 값 꺼내기까지 쓸 수 있게 된다.

ECS 초기에는 구조상 이 문제를 피할 수 없었다.
태스크 역할이 나오기 전에는 역할을 클러스터의 EC2 인스턴스에만 붙일 수 있었다.

어떤 태스크가 필요로 하는 권한이든 인스턴스 역할 하나에 모일 수밖에 없었다.
그래서 태스크마다 자기에게 필요 없는 권한까지 쓸 수 있었다.

지금도 EC2 인스턴스 위에서 태스크를 돌리면 인스턴스가 클러스터에 등록할 때 쓰는 컨테이너 인스턴스 역할도 필요하다.
이 역할의 자격 증명은 인스턴스 메타데이터 서비스(IMDS)⁠라는 인스턴스 안의 주소로 제공된다.

AWS는 컨테이너를 보안 경계로 보지 않는다.
EC2에서는 컨테이너가 같은 인스턴스에 있는 다른 태스크의 자격 증명이나 인스턴스 역할의 권한에 닿을 수 있어서, 컨테이너가 IMDS에 접근하지 못하게 막으라고 권한다.

권한 오류가 났을 때 먼저 볼 곳

증상을 보면 어느 역할의 문제인지 짐작이 간다.
태스크가 실행 상태에 이르지 못하고 멈췄다면 이미지를 내려받거나 비밀 값을 가져오다 막혔을 가능성이 크다.
실행 역할 쪽이다.

태스크는 잘 떴는데 앱 로그에 권한 거부(AccessDenied) 오류가 찍힌다면 태스크 역할 쪽이다.
앱이 부르는 API와 자원이 태스크 역할의 정책에 들어 있는지 확인하면 된다.

공급자 순서에 따른 자격 증명 선택 앞 공급자에서 유효한 자격 증명을 찾으면 뒤의 컨테이너 역할 공급자는 조회되지 않는다. 개념 도해 공급자 순서에 따른 자격 증명 선택 고정 키가 남은 탐색 경로 세부 순서 · SDK별 상이 ① 환경 변수 고정 키 · 유효 ② SDK별 중간 공급자 미조회 ③ 컨테이너 자격 증명 미조회 자격 증명 선택 탐색 중단 앞 공급자 상태별 선택 결과 환경 변수 중간 공급자 컨테이너 역할 선택 결과 고정 키 있음 선택 · 중단 미조회 미조회 고정 키 앞 공급자 없음 없음 없음 선택 · 중단 태스크 역할 앞 공급자에서 유효한 자격 증명을 찾으면 뒤의 컨테이너 역할 공급자는 조회되지 않는다. SOSHIN · DEV NOTES
역할을 붙여 두어도, 먼저 발견되는 자격 증명이 있으면 그쪽이 쓰인다.

그런데 태스크 역할에 권한이 분명히 있는데도 거부될 때가 있다.
요청이 태스크 역할이 아닌 다른 자격 증명으로 나가고 있는 경우다.

AWS SDK는 자격 증명을 여러 곳에서 정해진 순서로 찾다가, 처음 찾은 것을 쓰고 거기서 멈춘다.
이 순서를 자격 증명 공급자 체인이라고 한다.

파이썬용 SDK인 Boto3의 순서에서는 환경 변수가 컨테이너 자격 증명보다 한참 앞에 있다.
예전에 쓰던 액세스 키가 환경 변수에 남아 있으면 태스크 역할보다 그 키가 먼저 쓰인다.

그래서 역할로 옮기는 일과 옛 키를 지우는 일은 별개다.
태스크 정의의 환경 변수와 이미지, 설정 파일을 하나씩 확인해서 남은 키를 지워야 전환이 끝난다.

서비스마다 두 역할을 따로 만든다

AWS도 역할을 공유하지 말고 따로 만들라고 권한다.
실행 역할에는 AWS 관리형 정책을 붙이고, 비밀 값을 쓴다면 그 태스크가 쓰는 비밀 값만 꺼낼 수 있게 권한을 더한다.

태스크 역할에는 앱이 실제로 부르는 API와 자원만 허용한다.
IAM 권한이 필요한 태스크 정의나 서비스가 여럿이면, 태스크 정의나 서비스마다 역할을 따로 만든다.

태스크 역할은 선택 항목이다.
앱이 AWS API를 하나도 부르지 않는다면 비워 둬도 된다.

소신을 상징하는 까마귀 목판화 일러스트
작성자소신 · Soshin

웹과 서버를 만들고, 개발하며 배운 것들을 글로 남깁니다.

좌우로 움직여 보세요