AWS Marketplace 使用 License Manager 将适用于任何地方的容器集成 - AWS Marketplace

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

AWS Marketplace 使用 License Manager 将适用于任何地方的容器集成

作为 AWS Marketplace 卖家,您可以 AWS License Manager 与 AWS Marketplace 适用于 Amazon Anywhere、Amazon Anywhere、Amazon EKS 或本地基础设施的 for Contain EC2 ers ECS Anywhere 产品集成。以下各节提供了有关此集成的说明。

有关 License Manager 与集成的一般信息 AWS Marketplace,包括可用的许可模式,请参阅集装箱产品的合同定价 AWS License Manager。有关 AWS License Manager的更多信息,请参阅《AWS License Manager 用户指南》和《AWS CLI 命令参考》中的 AWS License Manager 部分。

将 “容器随处 AWS Marketplace 可用” 产品与 License Manager 集成

按照以下说明将你的 for Container AWS Marketplace s Anywhere 产品与集成 AWS License Manager。

将 for Container AWS Marketplace s Anywhere 产品与 License M
  1. 打开 Web 浏览器并登录 AWS Marketplace 管理门户

  2. 执行以下步骤,为您的容器产品创建产品 ID。您将在容器映像中使用此 ID,以便在后续步骤中进行许可证检查。

    1. 从菜单栏中,展开资产,然后选择容器

    2. 为您的产品输入面向客户的名称,然后选择创建。您以后可以更改此名称。

    3. 记下产品 ID。之后在创建或更新产品定价详情时会使用它。

      提示

      如果您丢失了产品 ID,可以从 “资产” 菜单中选择 “容器”,在 “容器” 中找到它。 AWS Marketplace 管理门户 器页面显示您的产品及其关联产品的列表IDs。

  3. 下载最新的公共版本 AWS SDK,然后将其安装到您的容器应用程序中。你可以在 T ools to Build O AWS SDK n 中找到你喜欢的安装说明AWS。

    注意

    要从 Amazon EKS Anywhere 或未提供的 Kubernetes 集群调用许可证管理器API操作 AWS,必须使用支持的。 AWS SDK要查看支持的列表 AWS SDKs,请参阅使用支持的 AWS SDK

  4. 使用自定义凭证提供程序创建 AWS License Manager 客户端,以便它可以为部署在 AWS 本地和本地的容器应用程序提供凭证。有关自定义凭证提供程序的完整源代码 LicenseCredentialProvider,请参阅以下各部分:

    LicenseCredentialsProvider通过添加LicenseManagerTokenCredentialsProvider,扩展了 AWS SDK的默认凭证提供者链以供本地使用。这通过在本地环境中使用 License Manager OIDC 颁发的身份令牌来提供凭证。必须在应用程序类路径中包含 LicenseCredentialsProvider 的源代码。

    注意

    扩展DefaultCredentialsProvider允许同一个容器应用程序在本地环境中运行 AWS 和在本地环境中运行时获得证书。如果容器应用程序已经使用自定义凭证提供程序链而不是默认凭证提供程序链,则也可以通过将 LicenseManagerTokenCredentialsProvider 添加到自定义链中来进行扩展。

    以下代码段是使用 Java 创建 AWS License Manager 客户端的示例。

    LicenseManagerClientBuilder clientBuilder = LicenseManagerClient.builder().credentialsProvider(LicenseCredentialsProvider.create());
  5. 使用产品中每个付费容器镜像中的aws license-manager checkout-license命令调用该CheckoutLicenseAPI操作。这将检查买家是否有权使用您的应用程序许可证。如果买家有权使用该应用程序,则 CheckoutLicense 成功并返回所请求的权利及其值。如果买家无权使用该应用程序,则 CheckoutLicense 会抛出异常。

    调用该CheckoutLicenseAPI操作时需要以下参数:

    • CheckoutType – 有效值为 PROVISIONALPERPETUAL

      • PERPETUAL – 当已签出的权利池中的权利数量将用尽时使用。

        示例:买家有权处理 500 GB 的数据。当他们继续处理数据时,会消耗 500 GB 池的数量并将其耗尽。

      • PROVISIONAL 用于浮动许可证权利,其中权利从池中签出,并在使用后返回。

        示例:某用户被授予应用程序 500 个并发用户的权利。当用户登录或注销时,用户会被消耗或返回到 500 个用户的池中。要了解有关浮动许可证权利的更多信息,请参阅使用 License Manager 管理浮动许可证特权

    • ClientToken – 区分大小写的唯一标识符。我们建议UUID对每个唯一请求使用随机值。

    • Entitlements – 待签出的权利列表。

      • 对于特征权利,请按以下方式提供 NameUnit 属性。

        { "Name": "<Entitlement_Name>", "Unit": "None" }
      • 对于技术特权,请按以下方式提供 NameUnitCount 属性。

        { "Name": "<Entitlement_Name>", "Unit": "<Entitlement_Unit>", "Value": <Desired_Count> }
    • KeyFingerprint – 由 AWS Marketplace 颁发的许可证的密钥指纹是 aws:294406891311:AWS/Marketplace:issuer-fingerprint。使用此密钥指纹可确保许可证由不可靠的实体颁发 AWS Marketplace ,而不是由不可靠的实体颁发。

    • ProductSKU— 在之前的步骤 AWS Marketplace 管理门户 中生成的产品 ID。

    以下代码段是使用CheckoutLicenseAPI操作进行调用的示例。 AWS CLI

    aws license-manager checkout-license \ --product-sku "2205b290-19e6-4c76-9eea-377d6bf71a47" \ --checkout-type "PROVISIONAL" \ --client-token "79464194dca9429698cc774587a603a1" \ --entitlements "Name=AWS::Marketplace::Usage/Drawdown/DataConsumption, Value=10, Unit=Gigabytes" \ --key-fingerprint "aws:294406891311:AWS/Marketplace:issuer-fingerprint"
    注意

    要查看许可证,容器应用程序需要出站网络访问权限才能使用 License Manager。部署在本地的应用程序可能会遇到出站网络访问不可靠或缓慢的情况。这些应用程序在调用 License Manager 时应包括够的重试次数。有关更多信息,请参阅 针对本地部署集成 License Manager 的最佳实操

  6. 定期致电CheckoutLicenseAPI运营部门,以确定由于续订、升级或取消而对客户的许可证进行的任何更改。 AWS Marketplace调用频率取决于应用程序。我们建议每天检查一次许可证,以便在没有任何买家干预的情况下自动获取更改。

    部署在本地的应用程序可能具有不可靠的出站网络访问权限,无法定期检查许可证。在这种情况下,应用程序应使用缓存许可证以获得足够的弹性。有关更多信息,请参阅 针对本地部署集成 License Manager 的最佳实操

  7. CheckoutLicense 调用与容器应用程序集成后,基于更改生成 Docker 容器映像的新版本。

  8. 更新应用程序的 Helm 图表以接受 Kubernetes 密钥作为可选输入,其中包含使用许可证管理器访问许可证的配置。APIs配置密钥将包含由 License Manager 颁发的身份令牌和一个 AWS Identity and Access Management 角色,前面描述的自定义凭据提供者将使用该角色来获取在本地部署容器应用程序APIs时调用 License Manager 的 AWS 凭证。另外,将 AWS 区域 作为输入添加,默认值为 us-east-1

    在本地部署容器应用程序的买家可以通过容器产品的购买者体验创建 Kubernetes 秘密。 AWS Marketplace 提供 Kubernetes 密钥名称作为 helm install 命令的输入。配置密钥采用以下格式配置。

    apiVersion: v1 kind: Secret metadata: name: aws-marketplace-license-config type: Opaque stringData: license_token: <token_value> // License Manager issued JWT token iam_role: <role_arn> // AWS Identity and Access Management role to assume with license token
  9. 更新集成的容器镜像的 Helm 图表中的应用程序部署模板, AWS License Manager 使其包含以下内容:

    • pod 的服务账户 — 在 Amazon 上部署 Helm 需要服务账户EKS。它用于通过在容器映像上为服务帐号设置IAM角色来获得调用 License Manager API 操作的权限。有关服务帐号IAM角色的更多信息,请参阅服务帐号的IAM角色

    • 本地部署的许可证访问权限 — 需要许可证配置密钥才能提供凭据和相应权限,以便在本地环境中为 Helm 部署调用 License Manager API 操作。买家将根据 AWS Marketplace 买家体验生成许可证秘密并将其提供给 Helm。

    以下代码片段是示例部署规范,其中包含服务帐号、许可证配置和映像拉取密钥。

    apiVersion: apps/v1 kind: Deployment metadata: name: example-app spec: replicas: 1 selector: matchLabels: app: example-app template: metadata: labels: app: example-app spec: // Service account for pod serviceAccountName: {{ .Values.serviceAccountName }} containers: - name: example-app image: example-app ports: - containerPort: 8001 // Add the following conditional attributes {{ - if .Values.awsmp.licenseConfigSecretName }} //Mount the license volume to the container image volumeMounts: - name: awsmp-product-license mountPath: "/var/run/secrets/product-license" //Add following environment variable to container for credential provider env: - name: AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE value: "/var/run/secrets/product-license/license_token" - name: AWS_ROLE_ARN valueFrom: secretKeyRef: name: {{ .Values.aws.licenseConfigSecretName }} key: iam_role //Mount the license secret as a volume to the pod volumes: - name: awsmp-product-license secret: secretName: {{ .Values.aws.licenseConfigSecretName }} optional: true {{ - end }}
    注意

    许可证配置密钥是可选的。买家仅在本地部署时使用该值。对于 AWS 部署,部署规范必须包含 License Manager 集成映像的服务帐户。

  10. EKS通过执行以下各节中的步骤,在本地和亚马逊上测试 License Manager 集成:

    1. 在本地测试 License Manager 集成

    2. 在亚马逊上测试 License Manager 集成 EKS

  11. 成功验证本地 AWS 和本地的 License Manager 集成后,您可以按照中的步骤创建容器产品清单概述:创建容器产品

在本地测试 License Manager 集成

你可以使用 minikube 或任何其他设置来在本地测试任何 Kubernetes 集群上的 License Manager 集成。确保 Kubernetes 集群具有出站互联网访问权限以调用 License Manager 操作。API

要在本地测试 License Manager 集成,请执行以下操作:
  1. 使用所需权利在测试卖家账户中创建测试许可证。要设置测试许可证,请参阅 “AWS License Manager API参考CreateLicense中的。或者,使用以下脚本创建测试许可证,然后为测试买家账户创建许可授权,以使用该许可证。以下脚本使用测试卖家账户凭证。

    read -p 'AWS Account for test buyer: ' TEST_BUYER_ACCOUNT_ID read -p 'License entitlements: ' ENTITLEMENTS # TEST_SELLER_ACCOUNT_ID="109876543210" # ENTITLEMENTS="{\"Name\": \"ByData\",\"MaxCount\": 1000,\"Overage\":true,\"Unit\": \"Gigabits\",\"AllowCheckIn\": true}" # Create License NOW=$(date +"%Y-%m-%dT00:00:00+00:00") PRODUCT_NAME="My awesome product" PRODUCT_SKU="c97b7825-44c4-4f42-b025-12baa4c171e0" LICENSE_BENEFICIARY=" arn:aws:iam::$TEST_BUYER_ACCOUNT_ID:root " LICENSE_ISSUER_NAME="test-seller" LICENSE_NAME="test-seller-license" CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367" CONSUMPTION_TTL=180 CONSUMPTION_RENEW_TYPE="None" HOME_REGION="us-east-1" LICENSE_ARN=$(aws license-manager create-license --license-name "$LICENSE_NAME" --product-name "$PRODUCT_NAME" --product-sku "$PRODUCT_SKU" --issuer Name="$LICENSE_ISSUER_NAME" --home-region "$HOME_REGION" --validity Begin="$NOW" --entitlements "$ENTITLEMENTS" --beneficiary "$LICENSE_BENEFICIARY" --consumption-configuration RenewType="$CONSUMPTION_RENEW_TYPE",ProvisionalConfiguration={MaxTimeToLiveInMinutes=$CONSUMPTION_TTL} --client-token "$CLIENT_TOKEN" | jq -r ".LicenseArn" ) echo "License arn: $LICENSE_ARN" # Create Grant GRANT_TOKEN="e9a14140-4fca-4219-8230-57511a6ea6" GRANT_NAME="test-grant" GRANT_ARN=$(aws license-manager create-grant --grant-name "$GRANT_NAME" --license-arn "$LICENSE_ARN" --principals "$LICENSE_BENEFICIARY" --home-region "$HOME_REGION" --client-token "$GRANT_TOKEN" --allowed-operations "CheckoutLicense" "CheckInLicense" "ExtendConsumptionLicense" "CreateToken" | jq -r ".GrantArn") echo "Grant arn: $GRANT_ARN"
  2. 使用之前定义的密钥格式使用许可证令牌和IAM角色创建 Kubernetes 密钥。使用 License Manager CreateToken API 操作生成许可证令牌。然后,使用IAMCreateRoleAPI操作创建具有权限和信任策略的IAM角色。请参阅以下示例脚本中的示例。以下脚本使用测试买家账户凭证。

    read -p 'AWS Account for test license: ' TEST_ACCOUNT_ID read -p 'License Arn' LICENSE_ARN # Create IAM Role ROLE_NAME="AWSLicenseManagerConsumptionTestRole" ROLE_DESCRIPTION="Role to test AWS License Manager integration on-prem" ROLE_POLICY_ARN="arn:aws:iam::aws:policy/service-role/AWSLicenseManagerConsumptionPolicy" ROLE_TRUST_POLICY="{\"Version\": \"2012-10-17\",\"Statement\": [{ \"Effect\":\"Allow\", \"Principal\": { \"Federated\": \"openid-license-manager.amazonaws.com\" }, \"Action\": \"sts:AssumeRoleWithWebIdentity\",\"Condition\": { \"ForAnyValue:StringLike\": { \"openid-license-manager.amazonaws.com:amr\": \"aws:license-manager:token-issuer-account-id:${TEST_ACCOUNT_ID}\" }}}]}" ROLE_SESSION_DURATION=3600 ROLE_ARN=$(aws iam create-role --role-name "$ROLE_NAME" --description "$ROLE_DESCRIPTION" --assume-role-policy-document "$ROLE_TRUST_POLICY" --max-session-duration $ROLE_SESSION_DURATION | jq ".Role" | jq -r ".Arn") aws iam attach-role-policy --role-name "$ROLE_NAME" --policy-arn "$ROLE_POLICY_ARN" echo "Role arn: $ROLE_ARN" # Create Token CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367" TOKEN=$(aws license-manager create-token --license-arn $LICENSE_ARN --role-arns $ROLE_ARN --client-token $CLIENT_TOKEN | jq '.Token') echo "License access token: $TOKEN"c
  3. 设置托管在外部的任何 Kubernetes 集群。 AWS使用它来测试容器应用程序是否可以 AWS License Manager API从其他环境连接到, AWS 以及自定义凭证提供程序是否已很好地集成到应用程序中。

  4. 将之前生成的许可证令牌和IAM角色部署到本地 Kubernetes 集群中。

    kubectl create secret generic "awsmp-license-access-config" \ --from-literal=license_token=${TOKEN} \ --from-literal=iam_role=${ROLE_ARN}
  5. 使用密码名称作为输入通过 Helm 部署应用程序,并验证应用程序是否可以调用 License Manager API 操作来执行授权检查。有关 Helm 和部署规范的更改,请参阅将 “容器随处 AWS Marketplace 可用” 产品与 License Manager 集成中的步骤 9。

在亚马逊上测试 License Manager 集成 EKS

您也可以在亚马逊上测试 License Manager 的集成EKS。测试以确保应用程序可以在没有许可证配置密钥的情况下调用 License Manager API 操作。此外,请确保该服务帐号可用于为服务帐号设置IAM角色 (IRSA),并为应用程序提供相关凭证。

在亚马逊上测试 License Manager 集成 EKS
  1. 使用所需权利在测试卖家账户中创建测试许可证。请参阅CreateLicense API参考资料来设置您的测试许可证,或者使用以下脚本创建测试许可证,然后向测试买家账户创建使用许可证的许可授权。以下脚本使用测试卖家账户凭证。

    read -p 'AWS Account for test buyer: ' TEST_BUYER_ACCOUNT_ID read -p 'License entitlements: ' ENTITLEMENTS # TEST_SELLER_ACCOUNT_ID="109876543210" # ENTITLEMENTS="{\"Name\": \"ByData\",\"MaxCount\": 1000,\"Overage\": true,\"Unit\": \"Gigabits\",\"AllowCheckIn\": true}" # Create License NOW=$(date +"%Y-%m-%dT00:00:00+00:00") PRODUCT_NAME="My awesome product" PRODUCT_SKU="c97b7825-44c4-4f42-b025-12baa4c171e0" LICENSE_BENEFICIARY=" arn:aws:iam::$TEST_BUYER_ACCOUNT_ID:root " LICENSE_ISSUER_NAME="test-seller" LICENSE_NAME="test-seller-license" CLIENT_TOKEN="b3920968-a94f-4547-af07-3dd232319367" CONSUMPTION_TTL=180 CONSUMPTION_RENEW_TYPE="None" HOME_REGION="us-east-1" LICENSE_ARN=$(aws license-manager create-license --license-name "$LICENSE_NAME" --product-name "$PRODUCT_NAME" --product-sku "$PRODUCT_SKU" --issuer Name="$LICENSE_ISSUER_NAME" --home-region "$HOME_REGION" --validity Begin="$NOW" --entitlements "$ENTITLEMENTS" --beneficiary "$LICENSE_BENEFICIARY" --consumption-configuration RenewType="$CONSUMPTION_RENEW_TYPE",ProvisionalConfiguration={MaxTimeToLiveInMinutes=$CONSUMPTION_TTL} --client-token "$CLIENT_TOKEN" | jq -r ".LicenseArn" ) echo "License arn: $LICENSE_ARN" # Create Grant GRANT_TOKEN="e9a14140-4fca-4219-8230-57511a6ea6" GRANT_NAME="test-grant" GRANT_ARN=$(aws license-manager create-grant --grant-name "$GRANT_NAME" --license-arn "$LICENSE_ARN" --principals "$LICENSE_BENEFICIARY" --home-region "$HOME_REGION" --client-token "$GRANT_TOKEN" --allowed-operations "CheckoutLicense" "CheckInLicense" "ExtendConsumptionLicense" "CreateToken" | jq -r ".GrantArn") echo "Grant arn: $GRANT_ARN"
  2. 创建包含所需配置的测试 Amazon EKS 集群,或运行以下命令以使用默认配置。

    aws ec2 create-key-pair --region us-west-2 --key-name eks-key-pair
    eksctl create cluster \ --name awsmp-eks-test-example \ --region us-west-2 \ --with-oidc \ --ssh-access \ --ssh-public-key eks-key-pair
  3. 为现有集群创建服务账号并将其与IAM角色关联。以下命令使用创建IAM角色AWSLicenseManagerConsumptionPolicy。然后,该命令将其附加到应在其中部署 License Manager 集成映像的 Amazon EKS 集群的test_sa服务账户。因此,服务帐号可以获得相应的凭据来调用 License Manager API 操作。

    eksctl create iamserviceaccount \ --name test_sa \ --namespace test_namespace \ --cluster awsmp-eks-test-example \ --attach-policy-arn "arn:aws:iam::aws:policy/service-role/AWSLicenseManagerConsumptionPolicy" \ --approve \ --override-existing-serviceaccounts
  4. 通过服务帐号中的 Helm 部署应用程序,该帐号中的IAM角色与上一个命令相关联。验证应用程序是否可以调用 License Manager API 操作来执行授权检查。

使用 License Manager 管理浮动许可证特权

使用浮动许可证,当用户登录应用程序时,将从可用许可证池中消耗许可证。用户注销后,许可证将重新添加到可用许可证池中。

对于浮动许可证,应用程序使用该CheckoutLicenseAPI操作在使用资源时从授权池中检出授权。CheckoutLicenseAPI操作的响应包括许可证消耗令牌,该令牌是结账的唯一标识符。许可证消耗令牌可以对已签出的权利执行其他操作,例如将其签回许可证池或延长签出时间。

当资源不再使用时,应用程序会使用该CheckInLicenseAPI操作将授权签回池中。

aws license-manager check-in-license \ --license-consumption-token "f1603b3c1f574b7284db84a9e771ee12"

如果许可证未能签回池中,例如,如果应用程序在操作过程中崩溃,则权利将在 60 分钟后自动签回池中。因此,如果资源的使用时间超过 60 分钟,则最佳实操是将权利保留在资源池之外。为此,只要资源正在使用,就使用该ExtendLicenseConsumptionAPI操作。

aws license-manager extend-license-consumption \ --license-consumption-token "f1603b3c1f574b7284db84a9e771ee12"

针对本地部署集成 License Manager 的最佳实操

本地环境中的容器应用程序部署可能会遇到不可靠的出站网络访问。使用以下最佳实操来增加弹性,以避免因互联网连接不畅导致的潜在问题而导致买家的服务中断:

  • 充分重试 — 临时网络问题可能会使您的应用程序无法连接到。 AWS License Manager实现最长 30 分钟的重试,并关闭指数级回退。这可以帮助避免短期中断或网络问题。

  • 避免硬限制 – 部署在联网集群中的应用程序可以定期检查许可证,以确定由于升级或续订而导致的任何更改。由于出站访问不可靠,应用程序可能无法识别这些更改。应用程序应尽可能避免因无法通过 License Manager 检查许可证而中断向买家提供的服务。当许可证到期时,应用程序可能会依赖免费试用或开源体验,并且无法检查许可证是否有效。

  • 通知客户 - 使用缓存许可证时,许可证的任何更改(包括续订或升级)都不会自动反映在正在运行的工作负载上。通知您的客户(他们必须暂时允许对应用程序进行出站访问),以便应用程序可以更新其缓存的许可证。例如,通过应用程序本身或通过其文档通知客户。同样,当回退到较低的功能集时,请通知客户其权利已用尽或许可证已过期。然后,他们可以选择升级或续订。

LicenseManagerCredentialsProvider - Java 实现。

LicenseCredentialsProvider通过添加LicenseManagerTokenCredentialsProvider,扩展了 AWS SDK的默认凭证提供者链以供本地使用。

LicenseCredentialsProvider

package com.amazon.awsmp.license; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.credentials.internal.LazyAwsCredentialsProvider; import software.amazon.awssdk.utils.SdkAutoCloseable; public class LicenseCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable { private static final LicenseCredentialsProvider CREDENTIALS_PROVIDER = new LicenseCredentialsProvider(); private final LazyAwsCredentialsProvider providerChain; private LicenseCredentialsProvider() { this.providerChain = createChain(); } public static LicenseCredentialsProvider create() { return CREDENTIALS_PROVIDER; } @Override public AwsCredentials resolveCredentials() { return this.providerChain.resolveCredentials(); } @Override public void close() { this.providerChain.close(); } private LazyAwsCredentialsProvider createChain() { return LazyAwsCredentialsProvider.create(() -> { AwsCredentialsProvider[] credentialsProviders = new AwsCredentialsProvider[]{ DefaultCredentialsProvider.create(), LicenseManagerTokenCredentialsProvider.create()}; return AwsCredentialsProviderChain.builder().reuseLastProviderEnabled(true) .credentialsProviders(credentialsProviders).build(); }); } }

LicenseManagerTokenCredentialsProvider

LicenseManagerTokenCredentialsProvider在本地环境中使用 License Manager OIDC 颁发的身份令牌提供凭证。必须在应用程序类路径中包含 LicenseCredentialsProvider 的源代码。

package com.amazon.awsmp.license; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.retry.RetryPolicyContext; import software.amazon.awssdk.core.retry.conditions.OrRetryCondition; import software.amazon.awssdk.core.retry.conditions.RetryCondition; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; import software.amazon.awssdk.services.licensemanager.LicenseManagerClient; import software.amazon.awssdk.services.licensemanager.model.GetAccessTokenRequest; import software.amazon.awssdk.services.licensemanager.model.GetAccessTokenResponse; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithWebIdentityCredentialsProvider; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; import software.amazon.awssdk.services.sts.model.IdpCommunicationErrorException; import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.SdkAutoCloseable; import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.SystemSetting; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.util.function.Supplier; public class LicenseManagerTokenCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable { private final StsAssumeRoleWithWebIdentityCredentialsProvider credentialsProvider; private final RuntimeException loadException; private Path licenseAccessTokenFile; private String roleArn; private String roleSessionName; private StsClient stsClient; private LicenseManagerClient lmClient; public static LicenseManagerTokenCredentialsProvider create() { return new Builder().build(); } @Override public AwsCredentials resolveCredentials() { if (this.loadException != null) { throw this.loadException; } return this.credentialsProvider.resolveCredentials(); } @Override public void close() { IoUtils.closeQuietly(this.credentialsProvider, null); IoUtils.closeQuietly(this.stsClient, null); IoUtils.closeIfCloseable(this.lmClient, null); } private LicenseManagerTokenCredentialsProvider(Builder builder) { StsAssumeRoleWithWebIdentityCredentialsProvider credentialsProvider = null; RuntimeException loadException = null; try { this.licenseAccessTokenFile = Paths.get(StringUtils.trim(LicenseSystemSetting.AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE.getStringValueOrThrow())); this.roleArn = SdkSystemSetting.AWS_ROLE_ARN.getStringValueOrThrow(); this.roleSessionName = SdkSystemSetting.AWS_ROLE_SESSION_NAME.getStringValue().orElse("aws-sdk-java-" + System.currentTimeMillis()); this.stsClient = builder.stsClient != null ? builder.stsClient : StsClientFactory.create(); this.lmClient = builder.lmClient != null ? builder.lmClient : LicenseManagerClientFactory.create(); AssumeRoleWithWebIdentityRequest request = AssumeRoleWithWebIdentityRequest.builder() .roleArn(this.roleArn).roleSessionName(this.roleSessionName).build(); Supplier<AssumeRoleWithWebIdentityRequest> supplier = new AssumeRoleRequestSupplier(request, this.licenseAccessTokenFile, this.lmClient); credentialsProvider = StsAssumeRoleWithWebIdentityCredentialsProvider.builder() .stsClient(this.stsClient).refreshRequest(supplier).build(); } catch (RuntimeException ex) { loadException = ex; } this.credentialsProvider = credentialsProvider; this.loadException = loadException; } public static final class Builder { private Path licenseAccessTokenFile; private String roleArn; private String roleSessionName; private StsClient stsClient; private LicenseManagerClient lmClient; public LicenseManagerTokenCredentialsProvider build() { return new LicenseManagerTokenCredentialsProvider(this); } public LicenseManagerTokenCredentialsProvider.Builder licenseAccessTokenFile(Path licenseAccessTokenFile) { this.licenseAccessTokenFile = licenseAccessTokenFile; return this; } public LicenseManagerTokenCredentialsProvider.Builder roleArn(String roleArn) { this.roleArn = roleArn; return this; } public LicenseManagerTokenCredentialsProvider.Builder roleSessionName(String roleSessionName) { this.roleSessionName = roleSessionName; return this; } public LicenseManagerTokenCredentialsProvider.Builder stsClient(StsClient stsClient) { this.stsClient = stsClient; return this; } public LicenseManagerTokenCredentialsProvider.Builder lmClient(LicenseManagerClient lmClient) { this.lmClient = lmClient; return this; } } private static final class AssumeRoleRequestSupplier implements Supplier { private final LicenseManagerClient lmClient; private final AssumeRoleWithWebIdentityRequest request; private final Path webIdentityRefreshTokenFile; AssumeRoleRequestSupplier(final AssumeRoleWithWebIdentityRequest request, final Path webIdentityRefreshTokenFile, final LicenseManagerClient lmClient) { this.lmClient = lmClient; this.request = request; this.webIdentityRefreshTokenFile = webIdentityRefreshTokenFile; } public AssumeRoleWithWebIdentityRequest get() { return this.request.toBuilder() .webIdentityToken(getIdentityToken()) .build(); } private String getIdentityToken() { return refreshIdToken(readRefreshToken(this.webIdentityRefreshTokenFile)); } private String readRefreshToken(Path file) { try (InputStream webIdentityRefreshTokenStream = Files.newInputStream(file)) { return IoUtils.toUtf8String(webIdentityRefreshTokenStream); } catch (IOException e) { throw new UncheckedIOException(e); } } private String refreshIdToken(String licenseRefreshToken) { final GetAccessTokenRequest request = GetAccessTokenRequest.builder() .token(licenseRefreshToken) .build(); GetAccessTokenResponse response = this.lmClient.getAccessToken(request); return response.accessToken(); } } private static final class LicenseManagerClientFactory { private static final Duration DEFAULT_API_TIMEOUT = Duration.ofSeconds(30); private static final Duration DEFAULT_API_ATTEMPT_TIMEOUT = Duration.ofSeconds(10); public static LicenseManagerClient create() { return getLicenseManagerClient(); } private static LicenseManagerClient getLicenseManagerClient() { ClientOverrideConfiguration configuration = ClientOverrideConfiguration.builder() .apiCallTimeout(DEFAULT_API_TIMEOUT) .apiCallAttemptTimeout(DEFAULT_API_ATTEMPT_TIMEOUT) .build(); LicenseManagerClient client = LicenseManagerClient.builder() .region(configureLicenseManagerRegion()) .credentialsProvider(AnonymousCredentialsProvider.create()) .overrideConfiguration(configuration).build(); return client; } private static Region configureLicenseManagerRegion() { Region defaultRegion = Region.US_EAST_1; Region region; try { region = (new DefaultAwsRegionProviderChain()).getRegion(); } catch (RuntimeException ex) { region = defaultRegion; } return region; } } private static final class StsClientFactory { private static final Duration DEFAULT_API_TIMEOUT = Duration.ofSeconds(30); private static final Duration DEFAULT_API_ATTEMPT_TIMEOUT = Duration.ofSeconds(10); public static StsClient create() { return getStsClient(); } private static StsClient getStsClient() { OrRetryCondition retryCondition = OrRetryCondition.create(new StsRetryCondition(), RetryCondition.defaultRetryCondition()); ClientOverrideConfiguration configuration = ClientOverrideConfiguration.builder() .apiCallTimeout(DEFAULT_API_TIMEOUT) .apiCallAttemptTimeout(DEFAULT_API_ATTEMPT_TIMEOUT) .retryPolicy(r -> r.retryCondition(retryCondition)) .build(); return StsClient.builder() .region(configureStsRegion()) .credentialsProvider(AnonymousCredentialsProvider.create()) .overrideConfiguration(configuration).build(); } private static Region configureStsRegion() { Region defaultRegion = Region.US_EAST_1; Region stsRegion; try { stsRegion = (new DefaultAwsRegionProviderChain()).getRegion(); } catch (RuntimeException ex) { stsRegion = defaultRegion; } return stsRegion; } private static final class StsRetryCondition implements RetryCondition { public boolean shouldRetry(RetryPolicyContext context) { return context.exception() instanceof IdpCommunicationErrorException; } } } private enum LicenseSystemSetting implements SystemSetting { AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE("aws.webIdentityRefreshTokenFile"); private String systemProperty; private String defaultValue = null; LicenseSystemSetting(String systemProperty) { this.systemProperty = systemProperty; } @Override public String property() { return this.systemProperty; } @Override public String environmentVariable() { return this.name(); } @Override public String defaultValue() { return this.defaultValue; } } }

LicenseManagerCredentialsProvider - Golang 实现。

LicenseCredentialsProvider

LicenseCredentialsProvider通过添加LicenseManagerTokenCredentialsProvider,扩展了 AWS SDK的默认凭证提供者链以供本地使用。

package lib import ( "context" "fmt" "sync" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" ) // LicenseCredentialsProvider is the custom credential provider that can retrieve valid temporary aws credentials type LicenseCredentialsProvider struct { fallBackProvider aws.CredentialsProvider mux sync.RWMutex licenseCredentials aws.Credentials err error } // NewLicenseCredentialsProvider method will create a LicenseCredentialProvider Object which contains valid temporary aws credentials func NewLicenseCredentialsProvider() (*LicenseCredentialsProvider, error) { licenseCredentialProvider := &LicenseCredentialsProvider{} fallBackProvider, err := createCredentialProvider() if err != nil { return licenseCredentialProvider, fmt.Errorf("failed to create LicenseCredentialsProvider, %w", err) } licenseCredentialProvider.fallBackProvider = fallBackProvider return licenseCredentialProvider, nil } // Retrieve method will retrieve temporary aws credentials from the credential provider func (l *LicenseCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) { l.mux.RLock() defer l.mux.RUnlock() l.licenseCredentials, l.err = l.fallBackProvider.Retrieve(ctx) return l.licenseCredentials, l.err } func createCredentialProvider() (aws.CredentialsProvider, error) { // LoadDefaultConfig will examine all "default" credential providers ctx := context.TODO() cfg, err := config.LoadDefaultConfig(ctx) if err != nil { return nil, fmt.Errorf("failed to create FallBackProvider, %w", err) } var useFallbackProvider bool if cfg.Credentials != nil { if _, err := cfg.Credentials.Retrieve(ctx); err != nil { // If the "default" credentials provider cannot retrieve credentials, enable fallback to customCredentialsProvider. useFallbackProvider = true } } else { useFallbackProvider = true } if useFallbackProvider { customProvider, err := newLicenseManagerTokenCredentialsProvider() if err != nil { return cfg.Credentials, fmt.Errorf("failed to create fallBackProvider, %w", err) } // wrap up customProvider with CredentialsCache to enable caching cfg.Credentials = aws.NewCredentialsCache(customProvider) } return cfg.Credentials, nil }

LicenseManagerTokenCredentialsProvider

LicenseManagerTokenCredentialsProvider在本地环境中使用 License Manager OIDC 颁发的身份令牌提供凭证。必须在应用程序类路径中包含 LicenseCredentialsProvider 的源代码。

package lib import ( "context" "fmt" "io/ioutil" "os" "sync" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/sts" ) const awsRefreshTokenFilePathEnvVar = "AWS_LICENSE_ACCESS_FILE" // licenseManagerTokenCredentialsProvider defines and contains StsAssumeRoleWithWebIdentityProvider type licenseManagerTokenCredentialsProvider struct { stsCredentialProvider *stsAssumeRoleWithWebIdentityProvider mux sync.RWMutex licenseCredentials aws.Credentials err error } // Retrieve method will retrieve credentials from credential provider. // Make this method public to make this provider satisfies CredentialProvider interface func (a *licenseManagerTokenCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) { a.mux.RLock() defer a.mux.RUnlock() a.licenseCredentials, a.err = a.stsCredentialProvider.Retrieve(ctx) return a.licenseCredentials, a.err } // newLicenseManagerTokenCredentialsProvider will create and return a LicenseManagerTokenCredentialsProvider Object which wraps up stsAssumeRoleWithWebIdentityProvider func newLicenseManagerTokenCredentialsProvider() (*licenseManagerTokenCredentialsProvider, error) { // 1. Retrieve variables From yaml environment envConfig, err := config.NewEnvConfig() if err != nil { return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err) } roleArn := envConfig.RoleARN var roleSessionName string if envConfig.RoleSessionName == "" { roleSessionName = fmt.Sprintf("aws-sdk-go-v2-%v", time.Now().UnixNano()) } else { roleSessionName = envConfig.RoleSessionName } tokenFilePath := os.Getenv(awsRefreshTokenFilePathEnvVar) b, err := ioutil.ReadFile(tokenFilePath) if err != nil { return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err) } refreshToken := aws.String(string(b)) // 2. Create stsClient cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { return &licenseManagerTokenCredentialsProvider{}, fmt.Errorf("failed to create LicenseManagerTokenCredentialsProvider, %w", err) } stsClient := sts.NewFromConfig(cfg, func(o *sts.Options) { o.Region = configureStsClientRegion(cfg.Region) o.Credentials = aws.AnonymousCredentials{} }) // 3. Configure StsAssumeRoleWithWebIdentityProvider stsCredentialProvider := newStsAssumeRoleWithWebIdentityProvider(stsClient, roleArn, roleSessionName, refreshToken) // 4. Build and return return &licenseManagerTokenCredentialsProvider{ stsCredentialProvider: stsCredentialProvider, }, nil } func configureStsClientRegion(configRegion string) string { defaultRegion := "us-east-1" if configRegion == "" { return defaultRegion } else { return configRegion } }