將 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 AnywhereEC2、Amazon 或內部部署基礎設施 AWS Marketplace 的 for Containers ECS 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 管理入口網站 選擇容器,在 中找到它。Containers 頁面會顯示您的產品清單及其相關聯的產品 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 SDK的預設憑證提供者鏈供內部部署使用LicenseManagerTokenCredentialsProvider。這透過在內部部署環境中使用 License Manager OIDC發行的身分字符來提供憑證。您必須在應用程式類別路徑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擲回例外狀況。

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

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

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

  8. 更新應用程式的 Helm Chart,接受 Kubernetes 秘密作為選用輸入,其中包含使用 License Manager 存取授權的組態APIs。組態秘密將包含由 License Manager 發行的身分字符,以及由先前描述的自訂憑證提供者用來取得在容器應用程式部署於內部部署APIs時呼叫 License Manager 的 AWS 憑證 AWS Identity and Access Management 的角色。此外,將 新增 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 上的 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 透過執行下列各節中的步驟,在本機和 Amazon 上測試 License Manager 整合:

    1. 在本機測試 License Manager 整合

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

  11. 成功驗證內部部署 AWS 和內部部署的 License Manager 整合後,您可以依照 中的步驟建立容器產品清單概觀:建立容器產品

在本機測試 License Manager 整合

您可以使用 minikube 或任何其他設定來測試本機任何 Kubernetes 叢集上的 License Manager 整合。確定 Kubernetes 叢集具有傳出網際網路存取權,以呼叫 License Manager API操作。

在本機測試 License Manager 整合
  1. 在具有所需權限的測試賣方帳戶中建立測試授權。若要設定測試授權,請參閱 參考 CreateLicense 中的 。 AWS License Manager 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. 使用先前定義的秘密格式,使用授權字符和IAM角色建立 Kubernetes 秘密。使用 License Manager CreateTokenAPI操作來產生授權權杖。然後,使用 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 和部署規格變更,請參閱中的步驟 9將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合

在 Amazon 上測試 License Manager 整合 EKS

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

在 Amazon 上測試 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。然後, 命令會將其連接至 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 浮動授權權限

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

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

當資源不再使用時,應用程式會使用 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 新增 ,以延伸 AWS SDK的預設憑證提供者鏈供內部部署使用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發行的身分字符提供憑證。您必須在應用程式類別路徑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 SDK的預設憑證提供者鏈供內部部署使用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發行的身分字符提供憑證。您必須在應用程式類別路徑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 } }