AWS/AWS Marketplace

AWS Marketplace AssumeRole 적용하기

행운개발자 2024. 1. 17. 00:50
728x90

AssumeRole을 적용해야하는지 판단하기

AssumeRole API를 적용할 필요성이 있는지 먼저 확인해봐야합니다.

AssumeRole이 필요한 상황

아래의 2가지 에러가 발생한 경우에는 AssumeRole이 대안이 될 수 있습니다.

  1. API를 호출할 권한이 없는 경우
    1. is not authorized to perform: aws-marketplace:ResolveCustomer because no identity-based policy allows the aws-marketplace:ResolveCustomer action
  2. AWS Marketplace Seller Account가 아닌 경우
    1. User is not authorized to call ResolveCustomer for this product.

직접 판단해보기

AWS Marketplace Seller 계정에서는 API를 사용하기 위해서 아래의 Role을 사용합니다.

// https://docs.aws.amazon.com/marketplace/latest/userguide/iam-user-policy-for-aws-marketplace-actions.html#iam-user-policy-for-saas-products
{
    "Version": "2012-10-17",
    "Statement": [
         {
         "Action": [
                 "aws-marketplace:ResolveCustomer",
                 "aws-marketplace:BatchMeterUsage"
         ],
         "Effect": "Allow",
         "Resource": "*"
         }
    ]
}

 

그런데 운영 환경에 배포하려고 보니 아래와 같은 문제를 마주할 수 있습니다.

  1. AWS Marketplace 제품을 등록한 Seller 계정과 운영 환경의 EC2를 관리하는 계정을 분리하고 싶다.
  2. AWS Marketplace ResolveCustomer API는 반드시 Seller Account에서 호출되어야 한다
    1. The API needs to called from the seller account id used to publish the SaaS application to successfully resolve the token.

이러한 설명한 문제와 마주했다면 AssumeRole을 사용하면 해결할 수 있습니다.

권한 설정하기

AWS Marketplace Seller 계정에서 해야하는 것

AWS Marketplace API에 필요한 정책을 추가한 Role ARN을 생성합니다.

그리고 생성된 Role의 신뢰 관계에 EC2 관리 계정를 추가하면 됩니다.

EC2 관리 계정에서 해야하는 것

Seller 계정에서 생성된 Role에 대해 AssumeRole을 호출할 수 있는 EC2 관리 계정의 Role을 생성 (EC2 관리 계정에는 ResolveCustomer 권한이 없지만, Seller 계정의 권한을 가져와서(AssumeRole) ResolveCustomer를 호출할 수 있는 권한을 임시적으로 가진다.)

AssumeRole 코드에 적용하기

여기까지 설정되면 코드는 아래와 같이 작성할 수 있습니다.

StsClient stsClient = getStsClient();
Credentials credentials = assumeRole(stsClient, conf.aws_marketplace_assume_role_arn);
AWSMarketplaceMeteringClientBuilder builder = AWSMarketplaceMeteringClientBuilder.standard();
BasicSessionCredentials basicSessionCredentials = new BasicSessionCredentials(
        credentials.accessKeyId(),
        credentials.secretAccessKey(),
        credentials.sessionToken());
builder.setCredentials(new AWSStaticCredentialsProvider(basicSessionCredentials));
this.marketplaceClient = builder.build();

하나씩 뜯어보겠습니다. AssumeRole을 호출하기 위한 StsClient를 생성합니다.

private StsClient getStsClient() {
    if (stsClient != null) {
        return stsClient;
    }
    stsClient = StsClient.create();
    return stsClient;
}

StsClient를 생성할 때 EC2 관리 계정에서 추가한 RoleARN이 필요합니다. 아래와 같이 SystemProperties를 사용하거나, EC2에 RoleARN을 지정합니다.

 

아래의 두 포스팅을 참고해주세요.

2024.01.16 - [개발/AWS Marketplace] - AWS API에 사용할 Access Key, Access Secret Key 발급하는 방법

2024.01.16 - [개발/AWS Marketplace] - AWS API에서 AccessKey, SecretAccessKey 대신 Role ARN 사용하기

 

private void setAwsRegionProps() {
    System.setProperty("aws.region", conf.aws_marketplace_region);
}

private void setStsClientProps() {
    // @See software.amazon.awssdk.services.sts.StsClient.create
    // @See software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
    System.setProperty("aws.accessKeyId", conf.aws_marketplace_access_key);
    System.setProperty("aws.secretAccessKey", conf.aws_marketplace_secret_access_key);
}

 

다음으로 생성된 StsClient로 AssumeRole을 호출합니다.

private Credentials assumeRole(StsClient stsClient, String roleArn) {
    AssumeRoleRequest roleRequest = AssumeRoleRequest.builder()
            .roleArn(roleArn)
            .roleSessionName("whatap-aws-mp-session-" + DateUtil.now())
            .build();
    AssumeRoleResponse roleResponse = stsClient.assumeRole(roleRequest);
    Credentials myCreds = roleResponse.credentials();

    Instant exTime = myCreds.expiration();
    assumeRoleExpirationTime = exTime.toEpochMilli();
    logger.info("[AWS AssumeRole] success. assumeRoleExpiredTime : {}", DateUtil.datetime(assumeRoleExpirationTime));
    return myCreds;
}

주의할 사항은 AssumeRole에는 만료 시간이 있다는 것입니다. 기본 1시간동안만 권한을 가져올 수 있습니다. 이 시간이 지나면 다시 AssumeRole을 호출해야합니다.

 

 

이렇게 가져온 Credentials 정보로 아래와 같이 AWS Marketplace Client를 생성할 수 있습니다

Credentials credentials = assumeRole(stsClient, conf.aws_marketplace_assume_role_arn);
AWSMarketplaceMeteringClientBuilder builder = AWSMarketplaceMeteringClientBuilder.standard();
BasicSessionCredentials basicSessionCredentials = new BasicSessionCredentials(
        credentials.accessKeyId(),
        credentials.secretAccessKey(),
        credentials.sessionToken());
builder.setCredentials(new AWSStaticCredentialsProvider(basicSessionCredentials));
this.marketplaceClient = builder.build();

만료 시간이 지났을 때 새로 AssumeRole을 호출하는 로직에는 아래의 Gist에 포함되어 있습니다. 자세한 코드는 gist를 확인해주세요.

 

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.services.marketplacemetering.AWSMarketplaceMetering;
import com.amazonaws.services.marketplacemetering.AWSMarketplaceMeteringClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
import software.amazon.awssdk.services.sts.model.Credentials;
import javax.annotation.PostConstruct;
import java.time.Instant;
@Service
public class CreateAwsMarketplaceClient {
private final Long NOT_INITIALIZED = 0L;
private final Logger logger = LoggerFactory.getLogger(CreateAwsMarketplaceClient.class);
private final Configure conf = Configure.getInstance();
private StsClient stsClient;
private AWSMarketplaceMetering marketplaceClient;
private long assumeRoleExpirationTime = NOT_INITIALIZED;
@PostConstruct
public void initClient() {
try {
if (conf.enable_aws_marketplace == false) {
return;
}
// 운영 환경에서는 system props를 사용하지 않음
// @See software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
if (conf.enable_aws_marketplace_system_properties) {
setAwsRegionProps();
setAwsClientProps();
}
getAwsMarketplaceClient();
} catch (Exception e) {
logger.error("[AWS MP] " + e.getMessage(), e);
}
}
private void setAwsClientProps() {
/**
*
* The 'ResolveCustomer' API needs to called from the seller account id
* used to publish the SaaS application to successfully resolve the token.
* @See https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_ResolveCustomer.html
*
* 아래의 설정에 seller가 아닌 다른 계정의 API KEY를 지정하는 경우, assume_role을 사용해야한다
* conf.aws_marketplace_access_key, conf.aws_marketplace_secret_access_key
*/
if (conf.enable_aws_marketplace_assume_role) {
setStsClientProps();
} else {
setAwsMarketplaceClientProps();
}
}
private void setAwsRegionProps() {
System.setProperty("aws.region", conf.aws_marketplace_region);
}
private void setStsClientProps() {
// @See software.amazon.awssdk.services.sts.StsClient.create
// @See software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
System.setProperty("aws.accessKeyId", conf.aws_marketplace_access_key);
System.setProperty("aws.secretAccessKey", conf.aws_marketplace_secret_access_key);
}
private void setAwsMarketplaceClientProps() {
// @See com.amazonaws.services.marketplacemetering.AWSMarketplaceMeteringClientBuilder#defaultClient
// @See com.amazonaws.auth.DefaultAWSCredentialsProviderChain
System.setProperty("aws.accessKeyId", conf.aws_marketplace_access_key);
System.setProperty("aws.secretKey", conf.aws_marketplace_secret_access_key);
}
private AWSMarketplaceMetering getAwsMarketplaceClient() throws CustomError {
try {
updateAwsMarketplaceClient();
return this.marketplaceClient;
} catch (Exception e) {
logger.error("[AWS MP] " + e.getMessage(), e);
}
throw new CustomError(ModuleName.ACCOUNT, ErrorType.INTERNAL_SERVER_ERROR);
}
private void updateAwsMarketplaceClient(){
if (conf.enable_aws_marketplace_assume_role) {
if (this.assumeRoleExpirationTime == NOT_INITIALIZED || this.assumeRoleExpirationTime < DateUtil.now()) {
StsClient stsClient = getStsClient();
Credentials credentials = assumeRole(stsClient, conf.aws_marketplace_assume_role_arn);
AWSMarketplaceMeteringClientBuilder builder = AWSMarketplaceMeteringClientBuilder.standard();
BasicSessionCredentials basicSessionCredentials = new BasicSessionCredentials(
credentials.accessKeyId(),
credentials.secretAccessKey(),
credentials.sessionToken());
builder.setCredentials(new AWSStaticCredentialsProvider(basicSessionCredentials));
this.marketplaceClient = builder.build();
logger.info("[AWS MP] client initialized success");
}
} else {
if (this.marketplaceClient == null) {
AWSMarketplaceMeteringClientBuilder builder = AWSMarketplaceMeteringClientBuilder.standard();
this.marketplaceClient = builder.build();
logger.info("[AWS MP] client initialized success");
}
}
}
private StsClient getStsClient() {
if (stsClient != null) {
return stsClient;
}
stsClient = StsClient.create();
return stsClient;
}
/**
* @param stsClient
* @param roleArn 3가지 조건을 만족해야 함
* 1. AWS Marketplace Seller 계정에서 생성한 Role
* 2. 다음 2가지 권한을 가진 Role
* - "aws-marketplace:BatchMeterUsage"
* - "aws-marketplace:ResolveCustomer"
* 3. 운영 환경에서 Instance를 관리할 계정이 Trusted entities로 설정되어야 함
*/
private Credentials assumeRole(StsClient stsClient, String roleArn) {
AssumeRoleRequest roleRequest = AssumeRoleRequest.builder()
.roleArn(roleArn)
.roleSessionName("aws-mp-session-" + DateUtil.now())
.build();
AssumeRoleResponse roleResponse = stsClient.assumeRole(roleRequest);
Credentials myCreds = roleResponse.credentials();
Instant exTime = myCreds.expiration();
assumeRoleExpirationTime = exTime.toEpochMilli();
logger.info("[AWS AssumeRole] success. assumeRoleExpiredTime : {}", DateUtil.datetime(assumeRoleExpirationTime));
return myCreds;
}
}

728x90