본문 바로가기
👾 Server/☁️AWS

AWS SDK for Java 1.x가 AWS credentials를 가져오는 5가지 방법

by kukim 2023. 4. 23.

AWS SDK for Java 1.x 를 사용하고 있는 Java/Spring Boot 서비스에서 AWS credentials을 가져오지 못하는 문제를 만나게 되었습니다. 해당 문제를 분석하며 AWS SDK for Java 1.x 에서 AWS credentials를 가져오는 방법을 정리해 보았습니다.

 

DefaultAWSCredentialsProviderChain

AWS SDK for Java 1.x에서는 DefaultAWSCredentialsProviderChain를 사용하여 편리하게 AWS credentials를 가져올 수 있습니다.

// DefaultAWSCredentialsProviderChain 사용 예
private void getCredentials() {
    credentialsProvider = new DefaultAWSCredentialsProviderChain();
    
    try {
        AWSCredentials credentials = credentialsProvider.getCredentials();
    } catch (Exception e) {
        throw new AmazonClientException("Cannot load the credentials from the environment.", e);
    }
}
// DefaultAWSCredentialsProviderChain 구현체
public class DefaultAWSCredentialsProviderChain extends AWSCredentialsProviderChain {

    private static final DefaultAWSCredentialsProviderChain INSTANCE
        = new DefaultAWSCredentialsProviderChain();

    public DefaultAWSCredentialsProviderChain() { // 5가지 방법의 체이닝
        super(new EnvironmentVariableCredentialsProvider(),
              new SystemPropertiesCredentialsProvider(),
              WebIdentityTokenCredentialsProvider.create(),
              new ProfileCredentialsProvider(),
              new EC2ContainerCredentialsProviderWrapper());
    }

    public static DefaultAWSCredentialsProviderChain getInstance() {
        return INSTANCE;
    }
}

DefaultAWSCredentialsProviderChain 구현은 AWS SDK for Java 1.x가 제공하는 5가지 방법을 지정된 순서로 체이닝 돌면서 Credentials을 얻습니다. 5가지 모두 실패하게 된다면 예외가 발생합니다.

 

DefaultAWSCredentialsProviderChain가 체이닝 도는 방법은 AWSCredentialsProviderChain.getCredentials() 부모 클래스를 사용합니다.

public class AWSCredentialsProviderChain implements AWSCredentialsProvider {

// 생략

    @Override
    public AWSCredentials getCredentials() {
        if (reuseLastProvider && lastUsedProvider != null) {
            return lastUsedProvider.getCredentials();
        }

        List<String> exceptionMessages = null;
        for (AWSCredentialsProvider provider : credentialsProviders) {
            try {
                AWSCredentials credentials = provider.getCredentials();

                if (credentials.getAWSAccessKeyId() != null &&
                    credentials.getAWSSecretKey() != null) {
                    log.debug("Loading credentials from " + provider.toString());

                    lastUsedProvider = provider;
                    return credentials;
                }
            } catch (Exception e) {
                // Ignore any exceptions and move onto the next provider
                String message = provider + ": " + e.getMessage();
                log.debug("Unable to load credentials from " + message);
                if (exceptionMessages == null) {
                    exceptionMessages = new LinkedList<String>();
                }
                exceptionMessages.add(message);
            }
        }
        throw new SdkClientException("Unable to load AWS credentials from any provider in the chain: "
                                     + exceptionMessages);
    }
 }

만약 5가지 방법을 모두 체크하지 않고 특정 Provider만 사용하려면 DefaultAWSCredentialsProviderChain이 아니라 지정해 주면 됩니다. e.g. EnvironmentVariableCredentialsProvider

// 특정 Provider 지정 e.g. EnvironmentVariableCredentialsProvider
private void getCredentials() {
    credentialsProvider = new EnvironmentVariableCredentialsProvider();
    
    try {
        AWSCredentials credentials = credentialsProvider.getCredentials();
    } catch (Exception e) {
        throw new AmazonClientException("Cannot load the credentials from the environment.", e);
    }
}

5가지 방법은 다음과 같습니다.

1. EnvironmentVariableCredentialsProvider

환경변수를 사용해서 credentials을 얻습니다. 사용하는 환경변수는 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 또는 AWS_ACCESS_KEY, AWS_SECRET_KEY입니다. EC2를 사용한다면 해당 환경변수를 가지고 있어야 하고, k8s 환경에서라면 pod에 해당 값을 주입해줘야 합니다. 하지만 해당 값들을 환경변수로 가지고 있기엔 보안 문제가 발생할 수도 있습니다.

 

2. SystemPropertiesCredentialsProvider

Java properties system을 사용합니다. properties 값을 aws.accessKeyId, aws.secretKey를 설정해 줍니다.

 

3. WebIdentityTokenCredentialsProvider.create

웹 ID 토큰 증명을 사용합니다. 흔히 AWS Security Token Service(AWS STS)로 EC2에 임시자격증명을 주거나 해당 환경변수를 주입하여 사용하게 됩니다. 사용되는 환경변수(AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE)

작동 방식은 해당 환경변수를 가지고 AssumeRole과 같이 AWS STS API를 프로그래밍 방식으로 호출하고 결과 자격 증명 및 세션 토큰을 추출하게 됩니다.

 

4. ProfileCredentialsProvider

AWS CLI가 사용하는 Credential profiles file(~/. aws/credentials)을 사용하여 credential을 얻습니다. 

 

5. EC2ContainerCredentialsProviderWrapper

Java 서비스가 EC2, ECS에 따라 조금씩 다르겠지만 크게 두 가지 방법을 사용합니다.

AWS_CONTAINER_CREDENTIALS_RELATIVE_URI 환경변수가 지정된 경우와 EC2의 IMDS(인스턴스 메타 데이터 서비스)를 활용합니다.

 

EC2 IMDS 사용

AWS EC2의 메타데이터 시스템(IMDS)은 EC2 인스턴스에서 실행되는 소프트웨어에서 인스턴스에 대한 정보를 제공합니다. 이 정보는 인스턴스 메타데이터와 유사한 형식으로 제공됩니다. v1은 HTTP 요청을 이용하여 인증되지 않은 사용자도 인스턴스 메타데이터에 액세스 할 수 있지만, 이는 보안 위협이 될 수 있습니다. 따라서 AWS는 IMDSv2라는 새로운 버전을 출시하여 보안을 강화하고 인증된 사용자만이 액세스할 수 있도록 하였습니다. SDK v1은 IMDSv2를 지원합니다. 이를 이용하면, 사용자는 인증이 필요한 모든 요청에 대해 액세스 토큰을 사용하여 액세스할 수 있습니다.

# IMDSv1: EC2 콘솔에서 GET http://169.254.169.254/latest/meta-data/ 요청 예
> curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
// 생략
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/

IMDSv1, v2 선택은 EC2를 생성/수정할 때 메타정보 옵션을 끄고 키는 것으로 변경할 수 있습니다. (AWS docs IMDSv2 사용하기)

 

AWS SDK for Java v1(1.11.678) 이전 버전은 IMDSv2를 지원하지 않습니다. 마이너 버전을 올리거나 v2로 변경해야 합니다. 

ref: IMDSv2 지원되는 SDK 버전 

 

AWS SDK for Java v1에서 제공하는 IMDSv2 구현체는 아래와 같습니다.

DefaultAWSCredentialsProviderChainEC2ContainerCredentialsProviderWrapperInstanceProfileCredentialsProviderInstanceMetadataServiceCredentialsFetcherInstanceMetadataServiceResourceFetcher (IMDSv2 구현체)

public final class InstanceMetadataServiceResourceFetcher extends EC2ResourceFetcher {
    private static final Log LOG = LogFactory.getLog(InstanceMetadataServiceResourceFetcher.class);
    // IMDSv2 endpoint 확인
    private static final String EC2_TOKEN_ROOT = "/latest/api/token";
    private static final String TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds";
    private static final String TOKEN_HEADER = "x-aws-ec2-metadata-token";
    private static final String DEFAULT_TOKEN_TTL = "21600";

}

Reference

AWS docs

- IMDSv2 사용하기

- IMDSv2 지원되는 SDK 버전 

- IAM 임시 보안 자격 증명 (https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/id_credentials_temp.html)

- 배경사진(https://twitter.com/awsforjava, 공식 배너)

 

댓글