【Azure Developer】Python程式碼通過AAD認證訪問微軟Azure金鑰保管庫(Azure Key Vault)中機密資訊(Secret)

路邊兩盞燈發表於2021-01-16

關鍵字說明

什麼是 Azure Active Directory?Azure Active Directory(Azure AD, AAD) 是 Microsoft 的基於雲的標識和訪問管理服務,可幫助員工登入和訪問 Azure 中的資源:

  • 外部資源,例如 Microsoft 365、Azure 門戶(https://portal.azure.cn/)以及成千上萬的其他 SaaS 應用程式。

  • 內部資源,例如公司網路和 Intranet 上的應用,以及由自己的組織開發的任何雲應用。

什麼是Azure Key Vault?Azure Key Vault 是一個用於安全地儲存和訪問機密的雲服務。 機密是你想要嚴格控制對其的訪問的任何內容,例如 API 金鑰、密碼、證照或加密金鑰。 Key Vault 服務支援:保管庫。 保管庫支援儲存軟體金鑰、機密和證照。

  • 機密管理 - Azure Key Vault 可以用來安全地儲存令牌、密碼、證照、API 金鑰和其他機密,並對其訪問進行嚴格控制
  • 金鑰管理 - Azure Key Vault 也可用作金鑰管理解決方案。 可以通過 Azure Key Vault 輕鬆建立和控制用於加密資料的加密金鑰。
  • 證照管理 - Azure Key Vault 也是一項服務,可用來輕鬆預配、管理和部署公用和專用傳輸層安全性/安全套接字層 (TLS/SSL) 證照,以用於 Azure 以及內部連線資源。

 

問題描述

在官方的示例文件中,我們可以發現Python 應用程式以使用 Azure 資源(虛擬機器 VM)的託管標識(Identity)從 Azure Key Vault 讀取資訊,那如果同樣的Python應用程式碼,如果想在非Azure託管的客戶端執行,如何來實現呢?如何來解決如下的錯誤呢?

EnvironmentCredential.get_token failed: EnvironmentCredential authentication unavailable. Environment variables are not fully configured.
ManagedIdentityCredential.get_token failed: ManagedIdentityCredential authentication unavailable, no managed identity endpoint found.
SharedTokenCacheCredential.get_token failed: Azure Active Directory error '(invalid_resource) AADSTS500011: The resource principal named https://vault.azure.cn was not found in 
the tenant named xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
Trace ID: 11af8f36-693a-4105-8a6c-9e271a1b9f00
Correlation ID: 43cc4c0d-fd23-4b84-a03b-284ff93484a4
Timestamp: 2021-01-16 07:58:47Z'
DefaultAzureCredential.get_token failed: SharedTokenCacheCredential raised unexpected error "Azure Active Directory error '(invalid_resource) AADSTS500011: The resource principal named https://vault.azure.cn was not found in the tenant named xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
Trace ID: 11af8f36-693a-4105-8a6c-9e271a1b9f00
Correlation ID: 43cc4c0d-fd23-4b84-a03b-284ff93484a4
Timestamp: 2021-01-16 07:58:47Z'"
DefaultAzureCredential failed to retrieve a token from the included credentials.
Attempted credentials:
        EnvironmentCredential: EnvironmentCredential authentication unavailable. Environment variables are not fully configured.
        ManagedIdentityCredential: ManagedIdentityCredential authentication unavailable, no managed identity endpoint found.
        SharedTokenCacheCredential: Azure Active Directory error '(invalid_resource) AADSTS500011: The resource principal named https://vault.azure.cn was not found in the tenant named xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
Trace ID: 11af8f36-693a-4105-8a6c-9e271a1b9f00
Correlation ID: 43cc4c0d-fd23-4b84-a03b-284ff93484a4
Timestamp: 2021-01-16 07:58:47Z'
Traceback (most recent call last):
  File "case.py", line 26, in <module>
    retrieved_secret = client.get_secret(secretName)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\core\tracing\decorator.py", line 83, in wrapper_use_tracer
    return func(*args, **kwargs)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\keyvault\secrets\_client.py", line 67, in get_secret
    bundle = self._client.get_secret(
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\keyvault\secrets\_generated\_operations_mixin.py", line 1475, in get_secret
    return mixin_instance.get_secret(vault_base_url, secret_name, secret_version, **kwargs)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\keyvault\secrets\_generated\v7_1\operations\_key_vault_client_operations.py", line 276, in get_secret
    pipeline_response = self._client._pipeline.run(request, stream=False, **kwargs)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\core\pipeline\_base.py", line 211, in run
    return first_node.send(pipeline_request)  # type: ignore
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\core\pipeline\_base.py", line 71, in send
    response = self.next.send(request)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\core\pipeline\_base.py", line 71, in send
    response = self.next.send(request)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\core\pipeline\_base.py", line 71, in send
    response = self.next.send(request)
  [Previous line repeated 2 more times]
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\core\pipeline\policies\_redirect.py", line 157, in send
    response = self.next.send(request)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\core\pipeline\policies\_retry.py", line 436, in send
    response = self.next.send(request)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\keyvault\secrets\_shared\challenge_auth_policy.py", line 111, in send
    self._handle_challenge(request, challenge)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\keyvault\secrets\_shared\challenge_auth_policy.py", line 137, in _handle_challenge
    self._token = self._credential.get_token(scope)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\identity\_credentials\default.py", line 144, in get_token
    return super(DefaultAzureCredential, self).get_token(*scopes, **kwargs)
  File "C:\Users\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\azure\identity\_credentials\chained.py", line 90, in get_token
    raise ClientAuthenticationError(message=message)
azure.core.exceptions.ClientAuthenticationError: DefaultAzureCredential failed to retrieve a token from the included credentials.
Attempted credentials:
        EnvironmentCredential: EnvironmentCredential authentication unavailable. Environment variables are not fully configured.
        ManagedIdentityCredential: ManagedIdentityCredential authentication unavailable, no managed identity endpoint found.
        SharedTokenCacheCredential: Azure Active Directory error '(invalid_resource) AADSTS500011: The resource principal named https://vault.azure.cn was not found in the tenant named xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
Trace ID: 11af8f36-693a-4105-8a6c-9e271a1b9f00
Correlation ID: 43cc4c0d-fd23-4b84-a03b-284ff93484a4
Timestamp: 2021-01-16 07:58:47Z'

 

解決思路

根據錯誤提示,需要在Azure AD中為Python應用程式註冊一個授權應用(AAD Application)。並獲取AAD應用中的客戶端訪問密碼(Secret)客戶端ID(Client ID),租戶ID(Tenant ID),配置Python應用端的環境變數,呼叫azure.identity庫中的DefaultAzureCredential函式,完成訪問Key Vault資源的授權。整個方案需要以下3個關鍵步驟:

  1. 註冊AAD應用並設定客戶端密碼
  2. 在Key Vault中對AAD應用完成訪問授權(Access Policy)
  3. 配置Python應用端的環境變數

下文為整個方案的操作步驟

 

操作步驟

一:註冊AAD 應用,複製出應用的關鍵資訊(Client ID, Tenant ID, Object ID)

【Azure Developer】Python程式碼通過AAD認證訪問微軟Azure金鑰保管庫(Azure Key Vault)中機密資訊(Secret)

二:設定AAD 應用客戶端密碼

  •  在AAD應用頁面,進入“證照和密碼”頁面,點選“新客戶端密碼”按鈕,新增新的Secret(因密碼值只能在最開始建立時可見,所以必須在離開頁面前複製它

【Azure Developer】Python程式碼通過AAD認證訪問微軟Azure金鑰保管庫(Azure Key Vault)中機密資訊(Secret)

三:在Key Vault中設定訪問授權

  • 登入Azure門戶,進入Key Vault頁面(https://portal.azure.cn/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.KeyVault%2Fvaults)
  • 選擇“Access policies”,點選“Add access policy”,使用第一步中的AAD應用名稱或Application ID作為Principal。點選“Add”後“Save”。

【Azure Developer】Python程式碼通過AAD認證訪問微軟Azure金鑰保管庫(Azure Key Vault)中機密資訊(Secret)

四:設定環境變數

 使用azure.identity庫中的DefaultAzureCredential函式,預設會去檢測當前環境中是否包含AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_CLIENT_SECRET三個變數。

  • AZURE_CLIENT_ID:第一步中的Client ID
  • AZURE_TENANT_ID:第一步中的Tenant ID
  • AZURE_CLIENT_SECRET:第二步中的Secret Value

【Azure Developer】Python程式碼通過AAD認證訪問微軟Azure金鑰保管庫(Azure Key Vault)中機密資訊(Secret)

五:執行Python應用

可以在程式碼中輸出上一步中設定的環境變數,以驗證值是否正確。

import os
import cmd
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

# print('AZURE_TENANT_ID:' +os.environ["AZURE_TENANT_ID"])
# print('AZURE_CLIENT_ID:' +os.environ["AZURE_CLIENT_ID"])
# print('AZURE_CLIENT_SECRET:' +os.environ["AZURE_CLIENT_SECRET"])

keyVaultName = 'mykeyvalue01'
secretName = 'mysecret'
KVUri = f"https://{keyVaultName}.vault.azure.cn"

print(KVUri)
credential = DefaultAzureCredential()
token = credential.get_token("https://vault.azure.cn/.default")
print(token)
client = SecretClient(vault_url=KVUri, credential=credential)

retrieved_secret = client.get_secret(secretName)

print(f"Your secret is '{retrieved_secret.value}'.")
print(f"Your secret properties not_before(nbf) is '{retrieved_secret.properties.not_before}'.")
print(f"Your secret properties expires_on is '{retrieved_secret.properties.expires_on}'.")

 

六:附加:如何獲取程式碼中的Token值

DefaultAzureCredential方法返回credential物件,如果需要知道請求中使用的token值,呼叫get_token方法,同時需要指定scope,如這裡使用的為"https://vault.azure.cn/.default"

credential = DefaultAzureCredential()
token = credential.get_token("https://vault.azure.cn/.default")
print(token)

測試結果如圖:

【Azure Developer】Python程式碼通過AAD認證訪問微軟Azure金鑰保管庫(Azure Key Vault)中機密資訊(Secret)

 

 

 

附錄一:如遺忘以上第三步,沒有在Key Vault中對AAD應用賦予訪問策略(Access Policy),則獲取的錯誤訊息為

azure.core.exceptions.HttpResponseError: (Forbidden) The user, group or application 'appid=xxxxxxxx-05ad-4999-8679-xxxxxxxxxxxx;oid=xxxxxxxx-e256-4650-8ef8-xxxxxxxxxxxx;iss=https://sts.chinacloudapi.cn/xxxxxxxx-66d7-47a8-8f9f-xxxxxxxxxxxx/' does not have secrets get permission on key vault 'keyvault01;location=chinanorth'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287

 

參考資料

什麼是 Azure Active Directory? https://docs.microsoft.com/zh-cn/azure/active-directory/fundamentals/active-directory-whatis

關於 Azure Key Vault: https://docs.azure.cn/zh-cn/key-vault/general/overview

Azure Key Vault 基本概念 : https://docs.azure.cn/zh-cn/key-vault/general/basic-concepts

將 Azure Key Vault 與通過 Python 編寫的虛擬機器配合使用 :https://docs.azure.cn/zh-cn/key-vault/general/tutorial-python-virtual-machine

DefaultAzureCredential get_token : https://docs.microsoft.com/en-us/python/api/azure-identity/azure.identity.defaultazurecredential?view=azure-python#get-token--scopes----kwargs-

使用Postman獲取Azure AD中註冊應用程式的授權Token,及為Azure REST API設定Authorization : https://www.cnblogs.com/lulight/p/14279338.html

 

相關文章