AssumeRole을 적용해야하는지 판단하기
AssumeRole API를 적용할 필요성이 있는지 먼저 확인해봐야합니다.
AssumeRole이 필요한 상황
아래의 2가지 에러가 발생한 경우에는 AssumeRole이 대안이 될 수 있습니다.
- API를 호출할 권한이 없는 경우
- is not authorized to perform: aws-marketplace:ResolveCustomer because no identity-based policy allows the aws-marketplace:ResolveCustomer action
- AWS Marketplace Seller Account가 아닌 경우
- 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": "*"
}
]
}
그런데 운영 환경에 배포하려고 보니 아래와 같은 문제를 마주할 수 있습니다.
- AWS Marketplace 제품을 등록한 Seller 계정과 운영 환경의 EC2를 관리하는 계정을 분리하고 싶다.
- AWS Marketplace ResolveCustomer API는 반드시 Seller Account에서 호출되어야 한다
이러한 설명한 문제와 마주했다면 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; | |
} | |
} |
'AWS > AWS Marketplace' 카테고리의 다른 글
AWS Marketplace 연동 가이드 | 02. AWS Marketplace Client 생성하기 (0) | 2024.01.17 |
---|---|
AWS Marketplace 연동 가이드 | 01. AWS Marketplace Seller 계정 생성부터 x-amzn-marketplace-token 토큰 수신까지 (0) | 2024.01.17 |
AWS API에서 AccessKey, SecretAccessKey 대신 Role ARN 사용하기 (0) | 2024.01.16 |
AWS API에 사용할 Access Key, Access Secret Key 발급하는 방법 (0) | 2024.01.16 |
AWS Marketplace API Region 확인하는 방법 (0) | 2024.01.16 |