GCP 워크로드 아이덴티티 적용 후기

Caesars·2022년 4월 25일
1

GCP 도입

목록 보기
2/8

AWS ECS에서 GCP 워크로드 아이덴티티 적용 실패에 관한 내용입니다. 검색해봐도 비슷한 내용을 찾지 못해 한동안 고생했는데 비슷한 환경에 처하신 분들께 도움이 되시길 바랍니다.


워크로드 아이덴티티 제휴란

워크로드 ID 페더레이션은 온프레미스, AWS 또는 Azure에서 실행되는 어플리케이션에서 서비스 계정 키를 사용하지 않고 GCP 리소스를 호출할 수 있도록 하는 새로운 인증 메커니즘입니다.

일반적으로 Google Cloud 외부에서 실행되는 애플리케이션이 Google Cloud 리소스에 액세스하려면 서비스 계정 키를 사용합니다. 저희도 서비스 계정을 발급 받고 해당 계정에 권한(IAM)을 준 뒤, 다운받은 키 파일을 외부 어플리케이션에 저장하고 사용했습니다.

문제는 서비스 계정 키만 있으면 누구나 GCP 리소스에 접근 가능하기에 제대로 관리하지 않을 경우 보안상 위험합니다. 때로는 키 파일이 git에 업로드 되는 경우도 있습니다. 해당 키의 만료시간을 짧게 두는 방법이 있지만 그 때마다 물리적인 파일을 전달해야 하므로 현실성이 떨어지는 방안입니다. 이러한 문제 때문에 워크로드 아이덴티티 제휴를 사용합니다.

GCP 가이드에서도 다른 대안이 없는 경우에만 서비스 계정 키 사용을 권장하고 있습니다. 그러므로 워크로드 아이덴티티 제휴를 시도해보았습니다.

세팅 방법

아래에서는 서비스 계정 설정 및 IAM 권한은 완료되었다고 가정하고 진행하겠습니다. AWS EC2에서 설정하는 영상이 있기에 이를 참고했습니다.

공급업체는 AWS를 선택하고 사용중인 AWS 계정 ID를 입력합니다.

attribute.aws_role 은 "arn:aws:iam::계정ID" 로 입력했습니다.

설정이 끝나면 워크로드가 설정된 키 파일을 다운받을 수 있습니다. 이대로 어플리케이션의 서비스 계정 키 파일을 워크로드 제휴 키 파일로 바꾸면 되겠다고 생각했습니다. 비교해보면 type 값과 몇몇 필드가 바뀐것을 알 수 있습니다.

서비스 계정 키 파일

{
  "type": "service_account",
  "project_id": "",
  "private_key_id": "",
  "private_key": "",
  "client_email": "",
  "client_id": "",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": ""
}

워크로드 제휴 키 파일

{
  "type": "external_account",
  "audience": "",
  "subject_token_type": "",
  "service_account_impersonation_url": "",
  "token_url": "",
  "credential_source": {
    "environment_id": "aws1",
    "region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
    "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
    "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
  }
}

문제점

파일을 교체하고 실행시키니 바로 에러가 났습니다.

Caused by: java.io.IOException: Error reading credentials from stream, 'type' field not specified.

	String fileType = (String) fileContents.get("type");
    if (fileType == null) {
      throw new IOException("Error reading credentials from stream, 'type' field not specified.");
    }
    if (USER_FILE_TYPE.equals(fileType)) {
      return UserCredentials.fromJson(fileContents, transportFactory);
    }
    if (SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) {
      return ServiceAccountCredentials.fromJson(fileContents, transportFactory);
    }

에러가 난 GoogleCredentials.class 파일을 보니 최신 버전이 아니라 external_account를 지원안하고 있었습니다.

	//build.gralde
	implementation 'com.google.auth:google-auth-library-oauth2-http:1.6.0'
	String fileType = (String) fileContents.get("type");
    if (fileType == null) {
      throw new IOException("Error reading credentials from stream, 'type' field not specified.");
    }
    if (USER_FILE_TYPE.equals(fileType)) {
      return UserCredentials.fromJson(fileContents, transportFactory);
    }
    if (SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) {
      return ServiceAccountCredentials.fromJson(fileContents, transportFactory);
    }
    //추가
    if (ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE.equals(fileType)) {
      return ExternalAccountCredentials.fromJson(fileContents, transportFactory);
    }
    if ("impersonated_service_account".equals(fileType)) {
      return ImpersonatedCredentials.fromJson(fileContents, transportFactory);
    }

최신 버전 라이브러리를 적용하니 해당 에러는 없어졌지만 timeout 에러가 발생 하였습니다.

상단 워크로드 키 파일 crednetial_source의 region_url은 AWS 내부적으로 권한 정보를 조회하는 주소입니다. EC2의 경우에는 위 내부 IP의 주소가 유효하지만 ECS에서는 그렇지 않습니다. 해당 주소로 요청을 보내도 응답이 없습니다.

비슷한 문제를 겪은 개발자의 사례를 찾아본 결과 region_url을 호출한 다음 환경변수로 설정해서 문제를 해결했습니다. ECS가 아니라 CodeBuild 에서도 같은 문제를 겪은 듯 한데 crednetial_source에서 region_url을 제외하고 별도로 호출한 듯 합니다.

하지만 위 방법도 ECS에서는 문제가 있었습니다. 우선 Dockerfile 을 수정해 미리 인증 정보를 받아와서 실행하니 정상적으로 동작했습니다.

RUN apt-get update && apt-get -y install jq
RUN echo '#!/bin/sh' > start.sh && \
    echo 'token_file=$(mktemp) && curl -sS "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" 
    > "$token_file" && AWS_ACCESS_KEY_ID=$(jq -r ".AccessKeyId" "$token_file") 
    && AWS_SECRET_ACCESS_KEY=$(jq -r ".SecretAccessKey" "$token_file") 
    && AWS_SESSION_TOKEN=$(jq -r ".Token" "$token_file") && rm "$token_file" 
    && export AWS_ACCESS_KEY_ID && export AWS_SECRET_ACCESS_KEY && export AWS_SESSION_TOKEN' >> start.sh 
    
RUN chmod 755 start.sh

하지만 몇시간 뒤부터 다시 에러가 나는데 AWS_SESSION_TOKEN 에는 만료 기간이 있습니다. EC2에서는 세션이 끊길시 자동으로 refresh가 가능하지만 위 ECS에서는 불가능하기에 기간이 만료된 후 부터는 에러가 발생합니다.


결론

아쉽지만 워크로드 페데레이션 설정은 제외하기로 했습니다. AWS 브릿지 만료 기간을 늘리거나 하는 방법을 찾아볼까 했지만 일이 너무 커지는 듯 하여 여기서 마치겠습니다.


참고

https://cloud.google.com/blog/products/identity-security/how-to-authenticate-service-accounts-to-help-keep-applications-secure?_ga=2.256538061.-757752448.1628733514
https://www.youtube.com/watch?v=wgzuLEdHcO0
https://blog.studysapuri.jp/entry/2021/06/08/080000
https://brownbears.tistory.com/600

profile
잊기전에 저장

0개의 댓글