

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

# 將 AWS Marketplace for Containers Anywhere 與 License Manager 整合
<a name="container-anywhere-license-manager-integration"></a>

身為 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](container-license-manager-integration.md)。如需 的詳細資訊 AWS License Manager，請參閱 [AWS License Manager 使用者指南](https://docs.aws.amazon.com/license-manager/latest/userguide/license-manager.html)和 *AWS CLI 命令參考*的 [AWS License Manager](https://docs.aws.amazon.com/cli/latest/reference/license-manager/index.html)一節。

**Topics**
+ [將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合](#containers-anywhere-integrate-with-LM)
+ [在本機測試 License Manager 整合](#container-testing-LM-integration-locally)
+ [在 Amazon EKS 上測試 License Manager 整合](#container-testing-LM-integration-EKS)
+ [使用 License Manager 浮動授權權利](#container-LM-floating-license)
+ [與現場部署的 License Manager 整合的最佳實務](#container-LM-best-practices-on-prem)
+ [`LicenseManagerCredentialsProvider` - Java 實作](#container-license-manager-cred-provider-java)
+ [`LicenseManagerCredentialsProvider` - `Golang`實作](#container-license-manager-cred-provider-golang)

## 將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合
<a name="containers-anywhere-integrate-with-LM"></a>

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

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

1. 開啟 Web 瀏覽器並登入 [AWS Marketplace 管理入口網站](https://us-east-1.console.aws.amazon.com/partnercentral/home)。

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

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

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

   1. 記下**產品 ID**。您將在建立或更新產品定價詳細資訊時使用它。
**提示**  
如果您遺失產品 ID，您可以從**資產**功能表中 AWS Marketplace 管理入口網站 選擇**容器**，在 中找到它。**Containers** 頁面會顯示您的產品清單及其相關聯的產品 IDs。

1. 下載最新的公有 AWS SDK，然後將其安裝在您的容器應用程式中。您可以在在 [AWS 上建置的工具](https://aws.amazon.com/tools/)中找到您偏好 AWS SDK 的安裝指示。
**注意**  
若要從 Amazon EKS Anywhere 或 未提供的 Kubernetes 叢集呼叫 License Manager API 操作 AWS，您必須使用支援的 AWS SDK。若要檢視支援的 AWS SDKs 清單，請參閱[使用支援的 AWS SDK](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-minimum-sdk.html)。

1. 使用自訂登入資料提供者建立 AWS License Manager 用戶端，以便為部署在 上的容器應用程式 AWS 以及內部部署提供登入資料。如需自訂登入資料提供者的完整原始碼`LicenseCredentialProvider`，請參閱下列章節：
   + [`LicenseManagerCredentialsProvider` - Java 實作](#container-license-manager-cred-provider-java)
   + [`LicenseManagerCredentialsProvider` - `Golang`實作](#container-license-manager-cred-provider-golang)

    `LicenseCredentialsProvider` 透過新增 來擴展 AWS SDK 的預設登入資料提供者鏈，以供內部部署使用`LicenseManagerTokenCredentialsProvider`。這透過在內部部署環境中使用 License Manager OIDC 發行的身分字符來提供憑證。您必須在應用程式 classpath `LicenseCredentialsProvider`中包含 的原始碼。
**注意**  
延伸 `DefaultCredentialsProvider`可讓相同的容器應用程式在內部部署環境中執行 AWS 和 時取得登入資料。如果容器應用程式已使用自訂登入資料提供者鏈而非預設值，也可以透過將 `LicenseManagerTokenCredentialsProvider`新增至自訂鏈來延伸。

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

   ```
   LicenseManagerClientBuilder clientBuilder = LicenseManagerClient.builder().credentialsProvider(LicenseCredentialsProvider.create());
   ```

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

   呼叫 `CheckoutLicense` API 操作時需要下列參數：
   + `CheckoutType` – 有效值為 `PROVISIONAL`或 `PERPETUAL`：
     + 當已從集區中耗盡已簽出的權限數量`PERPETUAL`時，請使用 。

       範例：買方有權處理 500 GB 的資料。隨著他們繼續處理資料，數量會從 500 GB 的集區中消耗。
     + `PROVISIONAL` 用於浮動授權權利，其中權利會從集區中簽出，並在使用後傳回。

       範例：使用者有權在應用程式上同時存取 500 名使用者。當使用者登入或登出時，系統會將使用者繪製或傳回至 500 名使用者的集區。若要進一步了解浮動授權權利，請參閱 [使用 License Manager 浮動授權權利](#container-LM-floating-license)。
   + `ClientToken` – 唯一且區分大小寫的識別符。我們建議針對每個唯一請求使用隨機 UUID。
   + `Entitlements` – 要簽出的權限清單。
     + 針對功能權利，提供 `Name`和 `Unit` 屬性，如下所示。

       ```
       {
         "Name": "<Entitlement_Name>",
         "Unit": "None"
       }
       ```
     + 針對計數權利，提供 `Name`、 `Unit`和 `Count` 屬性，如下所示。

       ```
       {
         "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 整合的最佳實務](#container-LM-best-practices-on-prem)。

1. 定期呼叫 `CheckoutLicense` API 操作，以識別因續約、升級或取消 而導致客戶授權的任何變更 AWS Marketplace。節奏取決於應用程式。我們建議您每天檢查一次授權，以自動收取變更，而無需任何買方介入。

   部署於內部部署的應用程式可能具有不可靠的傳出網路存取權，以定期檢查授權。在這種情況下，應用程式應使用快取的授權以獲得足夠的彈性。如需詳細資訊，請參閱[與現場部署的 License Manager 整合的最佳實務](#container-LM-best-practices-on-prem)。

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

1. 更新應用程式的 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
   ```

1. 針對與 整合的容器映像，更新 Helm Chart 中的應用程式部署範本 AWS License Manager ，以包含下列項目：
   + Pod 的服務帳戶 – 在 Amazon EKS 上的 Helm 部署需要 服務帳戶。它用於透過在容器映像上設定服務帳戶的 IAM 角色來取得呼叫 License Manager API 操作的許可。如需服務帳戶的 IAM 角色詳細資訊，請參閱[服務帳戶的 IAM 角色](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)。
   + 內部部署的授權存取 – 需要授權組態秘密，才能提供登入資料和適當的許可，以在內部部署環境中呼叫 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 整合映像的服務帳戶。

1. 透過執行以下章節中的步驟，在本機和 Amazon EKS 上測試 License Manager 整合：

   1. [在本機測試 License Manager 整合](#container-testing-LM-integration-locally)

   1. [在 Amazon EKS 上測試 License Manager 整合](#container-testing-LM-integration-EKS)

1. 在現場部署 AWS 和現場部署成功驗證 License Manager 整合之後，您可以依照中的步驟建立容器產品清單[概觀：建立容器產品](container-product-getting-started.md#create-container-product)。

## 在本機測試 License Manager 整合
<a name="container-testing-LM-integration-locally"></a>

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

**在本機測試 License Manager 整合**

1. 在具有所需權限的測試賣方帳戶中建立測試授權。若要設定測試授權，請參閱 *AWS License Manager API 參考*中的 [CreateLicense](https://docs.aws.amazon.com/license-manager/latest/APIReference/API_CreateLicense.html)。或者，使用以下指令碼來建立測試授權，然後建立授權給測試買方帳戶以取用授權。下列指令碼使用測試賣方帳戶登入資料。

   ```
   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"
   ```

1. 使用先前定義的秘密格式，使用授權字符和 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
   ```

1. 設定在外部託管的任何 Kubernetes 叢集 AWS。使用它來測試容器應用程式是否可以從 以外的環境連線至 AWS License Manager API， AWS 並且自訂登入資料提供者已與應用程式充分整合。

1. 將先前產生的授權字符和 IAM 角色部署到本機 Kubernetes 叢集。

   ```
   kubectl create secret generic "awsmp-license-access-config" \
   --from-literal=license_token=${TOKEN} \
   --from-literal=iam_role=${ROLE_ARN}
   ```

1. 透過 Helm 以秘密名稱做為輸入來部署應用程式，並確認應用程式可以呼叫 License Manager API 操作來執行權利檢查。如需 Helm 和部署規格變更，請參閱 中的步驟 9[將 AWS Marketplace for Containers Anywhere 產品與 License Manager 整合](#containers-anywhere-integrate-with-LM)。

## 在 Amazon EKS 上測試 License Manager 整合
<a name="container-testing-LM-integration-EKS"></a>

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

**在 Amazon EKS 上測試 License Manager 整合**

1. 在具有所需權利的測試賣方帳戶中建立測試授權。請參閱 [CreateLicense API 參考](https://docs.aws.amazon.com/license-manager/latest/APIReference/API_CreateLicense.html)以設定您的測試授權，或使用下列指令碼建立一個授權，並建立授權給測試買方帳戶以取用授權。下列指令碼使用測試賣方帳戶登入資料。

   ```
   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"
   ```

1. 建立所需組態的測試 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
   ```

1. 為現有叢集建立服務帳戶，並將其與 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
   ```

1. 在 IAM 角色與上一個命令相關聯的服務帳戶中，透過 Helm 部署應用程式。確認應用程式可以呼叫 License Manager API 操作來執行權利檢查。

## 使用 License Manager 浮動授權權利
<a name="container-LM-floating-license"></a>

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

對於浮動授權，應用程式會使用 `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 整合的最佳實務
<a name="container-LM-best-practices-on-prem"></a>

內部部署環境中的容器應用程式部署可能會遇到不可靠的傳出網路存取。使用下列最佳實務來新增彈性，以避免因網際網路連線能力不佳造成潛在問題而導致買方的服務中斷：
+ **充分重試** – 暫時性網路問題可能會讓您的應用程式無法連線到 AWS License Manager。實作重試最多 30 分鐘，並呈指數退避。這有助於避免短期中斷或網路問題。
+ **避免硬性限制** – 部署在連線叢集中的應用程式可以定期檢查授權，以識別因升級或續約而產生的任何變更。透過不可靠的傳出存取，應用程式可能無法識別這些變更。盡可能避免由於無法透過 License Manager 檢查授權而導致買方的服務中斷。當授權過期且無法檢查授權是否有效時，應用程式可以恢復免費試用或開放原始碼的體驗。
+ **通知客戶** – 使用快取的授權時，授權的任何變更 （包括續約或升級） 都不會自動反映在執行中的工作負載上。通知您的客戶 （他們必須暫時允許傳出存取應用程式，以便應用程式可以更新其快取的授權。例如，透過應用程式本身或文件通知客戶。同樣地，當回復到一組較低的功能時，請通知客戶其權利已耗盡或授權已過期。然後，他們可以選擇升級或續約。

## `LicenseManagerCredentialsProvider` - Java 實作
<a name="container-license-manager-cred-provider-java"></a>

`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 發行的身分字符來提供登入資料。您必須在應用程式 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`實作
<a name="container-license-manager-cred-provider-golang"></a>

**`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 發行的身分字符來提供登入資料。您必須在應用程式 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
	}
}
```