將 AWS Marketplace for Containers Anywhere 與 License Manager 整合 - AWS Marketplace

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

將 AWS Marketplace for Containers Anywhere 與 License Manager 整合

身為 AWS Marketplace 賣方,您可以 AWS License Manager 整合適用於 Amazon EKS Anywhere、Amazon ECS Anywhere、Amazon EC2 或內部部署基礎設施 AWS Marketplace 的 for Containers Anywhere 產品。以下各節提供此整合的說明。

如需與 License Manager 整合的一般資訊 AWS Marketplace,包括可用的授權模型,請參閱 使用 的容器產品的合約定價 AWS License Manager。如需 的詳細資訊 AWS License Manager,請參閱 AWS License Manager 使用者指南AWS CLI 命令參考AWS License Manager一節。

將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合

使用以下指示將 AWS Marketplace for Containers Anywhere 產品與 整合 AWS License Manager。

將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合
  1. 開啟 Web 瀏覽器並登入 AWS Marketplace 管理入口網站

  2. 執行下列步驟,為您的容器產品建立產品 ID。您將在容器映像中使用此 ID 進行後續步驟的授權檢查。

    1. 從選單列中,展開資產,然後選擇容器

    2. 輸入產品面向客戶的名稱,然後選擇建立。您可以稍後變更此名稱。

    3. 記下產品 ID。您將在建立或更新產品定價詳細資訊時使用它。

      提示

      如果您遺失產品 ID,您可以從資產功能表中 AWS Marketplace 管理入口網站 選擇容器,在 中找到它。容器頁面會顯示您的產品清單及其相關聯的產品 IDs。

  3. 下載最新的公有 AWS SDK,然後將其安裝在您的容器應用程式中。您可以在建置於 AWS 的工具中找到您偏好 AWS SDK 的安裝說明。

    注意

    若要從 Amazon EKS Anywhere 或 未提供的 Kubernetes 叢集呼叫 License Manager API 操作 AWS,您必須使用支援的 AWS SDK。若要檢視支援的 AWS SDKs 清單,請參閱使用支援的 AWS SDK

  4. 使用自訂登入資料提供者建立 AWS License Manager 用戶端,以便其可以提供登入資料給部署在 上的容器應用程式, AWS 以及內部部署。如需自訂登入資料提供者的完整原始碼LicenseCredentialProvider,請參閱下列各節:

    LicenseCredentialsProvider 新增 ,以擴充 AWS 軟體開發套件的預設登入資料提供者鏈,供內部部署使用LicenseManagerTokenCredentialsProvider。這透過在內部部署環境中使用 License Manager OIDC 發行的身分字符來提供憑證。您必須在應用程式 classpath LicenseCredentialsProvider中包含 的原始碼。

    注意

    延伸 DefaultCredentialsProvider可讓相同的容器應用程式在現場部署環境中執行 AWS 和 時取得登入資料。如果容器應用程式已使用自訂登入資料提供者鏈而非預設值,也可以透過將 LicenseManagerTokenCredentialsProvider新增至自訂鏈來延伸。

    下列程式碼片段是使用 Java 建立 AWS License Manager 用戶端的範例。

    LicenseManagerClientBuilder clientBuilder = LicenseManagerClient.builder().credentialsProvider(LicenseCredentialsProvider.create());
  5. 使用產品方案中每個付費容器映像的 aws license-manager checkout-license命令來呼叫 CheckoutLicense API 操作。這會檢查買方是否有權為您的應用程式使用授權。如果買方有權使用應用程式, 會CheckoutLicense成功並傳回請求的權限及其值。如果買方無權使用應用程式, 會CheckoutLicense擲回例外狀況。

    呼叫 CheckoutLicense API 操作時,需要下列參數:

    • 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。

    下列程式碼片段是使用 CheckoutLicense API 操作搭配 的呼叫範例 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. 定期呼叫 CheckoutLicense API 操作,以識別由於在 上進行的續約、升級或取消而對客戶授權的任何變更 AWS Marketplace。節奏取決於應用程式。我們建議您每天檢查一次授權,以便在不進行任何買方介入的情況下自動接收變更。

    部署的內部部署應用程式可能具有不可靠的傳出網路存取權,以定期檢查授權。在這種情況下,應用程式應該使用快取的授權來提供足夠的彈性。如需詳細資訊,請參閱與現場部署的 License Manager 整合的最佳實務

  7. CheckoutLicense呼叫與容器應用程式整合後,請建置具有變更的 Docker 容器映像新版本。

  8. 更新應用程式的 Helm Chart,以接受 Kubernetes 秘密做為選用輸入,其中包含使用 License Manager APIs組態。組態秘密將包含 License Manager 發行的身分字符和角色,該 AWS Identity and Access Management 角色將由先前描述的自訂憑證提供者使用,以取得在現場部署容器應用程式時呼叫 License Manager APIs 的 AWS 憑證。此外,將 新增 AWS 區域 為預設值為 的輸入us-east-1

    現場部署容器應用程式的買方可以透過容器產品的 AWS Marketplace 買方體驗來建立 Kubernetes 秘密。提供 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 Chart 中的應用程式部署範本 AWS License Manager ,以包含下列項目:

    • Pod 的服務帳戶 – Amazon EKS 上的 Helm 部署需要該服務帳戶。它用於透過在容器映像上設定服務帳戶的 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. 透過執行以下章節中的步驟,在本機和 Amazon EKS 上測試 License Manager 整合:

    1. 在本機測試 License Manager 整合

    2. 在 Amazon EKS 上測試 License Manager 整合

  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 操作來產生授權字符。然後,使用 IAM CreateRole API 操作來建立具有許可和信任政策的 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 和部署規格變更,請參閱 中的步驟 9將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合

在 Amazon EKS 上測試 License Manager 整合

您也可以在 Amazon EKS 上測試 License Manager 整合。測試 以確保應用程式可以呼叫 License Manager API 操作,而不需要授權組態秘密。此外,請確定服務帳戶可用來設定服務帳戶 (IRSA) 的 IAM 角色,並提供應用程式相關登入資料。

在 Amazon EKS 上測試 License Manager 整合
  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。然後,命令會將其連接至 Amazon EKS 叢集test_sa的服務帳戶,其中應部署 License Manager 整合映像。因此,服務帳戶可以取得適當的登入資料來呼叫 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 浮動授權權限

使用浮動授權時,當使用者登入應用程式時,會從可用授權集區中提取授權。當使用者登出時,授權會新增回可用授權的集區。

對於浮動授權,應用程式會使用 CheckoutLicense API 操作,在使用資源時從權限集區簽出權限。CheckoutLicense API 操作的回應包含授權使用權杖,這是結帳的唯一識別符。授權耗用權杖可以對已簽出的權限執行其他動作,例如將它們重新簽入授權集區或延長簽出。

當資源不再使用時,應用程式會使用 CheckInLicense API 操作來檢查權限是否回到集區。

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

如果將授權檢查回集區失敗,例如,如果應用程式在操作期間當機,則會在 60 分鐘後自動將權利檢查回集區。因此,如果資源使用時間超過 60 分鐘,最佳實務是將權利保持在集區之外。若要執行此操作,只要資源正在使用,就使用 ExtendLicenseConsumption API 操作。

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

與現場部署的 License Manager 整合的最佳實務

現場部署環境中的容器應用程式部署可能會遇到不可靠的傳出網路存取。使用下列最佳實務來新增彈性,以避免因網際網路連線能力不佳造成潛在問題而導致買方的服務中斷:

  • 充分重試 – 暫時性網路問題可能會讓您的應用程式無法連線到 AWS License Manager。實作重試最多 30 分鐘,指數退避。這有助於避免短期中斷或網路問題。

  • 避免硬性限制 – 部署在連線叢集中的應用程式可以定期檢查授權,以識別由於升級或續約而產生的任何變更。透過不可靠的傳出存取,應用程式可能無法識別這些變更。盡可能避免因為無法透過 License Manager 檢查授權而中斷對買方的服務。當授權過期且無法檢查授權是否有效時,應用程式可能會回到免費試用或開放原始碼體驗。

  • 通知客戶 – 使用快取授權時,授權的任何變更 (包括續約或升級) 都不會自動反映在執行中的工作負載上。通知您的客戶 (他們必須暫時允許應用程式的傳出存取,以便應用程式可以更新其快取的授權。例如,透過應用程式本身或文件通知客戶。同樣地,當回復到一組較低的功能時,請通知客戶其權利已耗盡或授權已過期。然後,他們可以選擇升級或續約。

LicenseManagerCredentialsProvider - Java 實作

LicenseCredentialsProvider 新增 ,以擴充 AWS 軟體開發套件的預設登入資料提供者鏈,供內部部署使用LicenseManagerTokenCredentialsProvider

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 發行的身分字符來提供憑證。您必須在應用程式 classpath 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 新增 ,以擴充 AWS 軟體開發套件的預設登入資料提供者鏈,供內部部署使用LicenseManagerTokenCredentialsProvider

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 發行的身分字符來提供憑證。您必須在應用程式 classpath 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 } }