【Explanation】Azure Security — 設計授權存取 Storage Account 資料

Implementing Secure Access Controls for Azure Storage Accounts

Kellen
17 min readApr 4, 2024

當開始探索如何在 Azure 中存取 Storage Account 內的資料時,雖然程式一般都會跑,但 Developer 背後還會面臨不同的授權認證類型和方法,這些方法有共用金鑰、共用存取簽章權杖以及 Microsoft Entra TokenCredential,一開始開發可以先了解認證類型,了解每種授權認證其特定的用途和限制,若一開始沒有選定好的話,一旦日後更改類型,那包含應用程式、Identity 註冊或管理幾乎是要大翻修。

認證類型

劇透:因為想避免帳密硬編碼在程式中,因此後面會有一些篇幅介紹微軟所推薦的 — Microsoft Entra ID(Azure AD)整合 TokenCredential

credential根據您要使用的授權類型而定,參數可能會以許多不同的形式提供。例如 Tables library 可支援下列授權

  • 共用金鑰(Shared Key)
  • 連接字串(Connection String)
  • 共用存取簽章權杖(Shared Access Signature Token)
  • TokenCredential(Azure AD)(Supported on Storage)
Shared Access Signature Token 不支援 File

共用金鑰(Shared Key)& 連接字串(Connection String)

  • 因原理與配發方式都一樣,只是 credential 長不太一樣,放一起說明
  • 共用金鑰授權,可以適用於 Blob、File、Queue 和 Table Storage。使用Shared Key 的用戶端會在每個要求中,傳遞使用儲存體帳戶存取金鑰所簽署的標頭
    📕 參閱使用共用金鑰進行授權
  • 因為算是較為傳統的方法或滿常看到大家分享程式碼就用這個方式處理,但 Microsoft Best Practice 是建議母湯!最好是「禁止」儲存體帳戶的共用金鑰授權(Shared Key),用戶端可以改使用 Microsoft Entra ID 或使用者委派 SAS 來授權該儲存體帳戶中的資料存取
    📕 參閱防止 Azure 儲存體帳戶使用共用金鑰授權
Prevent Shared Key authorization for an Azure Storage account

共用存取簽章權杖(Shared Access Signature Token)

  • 也適用於 Blob、File、Queue 和 Table Storage。 共用存取簽章權杖可透過簽署的 URL 提供儲存體帳戶資源的有限委派存取權。簽署的 URL 會指定授與資源的權限,以及指定有效簽章的間隔。 服務 SAS 或帳戶 SAS 會以帳戶金鑰簽署,而使用者委派 SAS 會以 Microsoft Entra 認證簽署且僅適用於 Blob。
    📕 參閱使用共用存取簽章 (SAS)

Microsoft Entra ID(Azure AD)整合 TokenCredential

  • 用來授權向 Blob、Queue 和 Table Storage 資源發出的要求,較上面少一個 File 喔。 Microsoft 建議盡可能使用 Microsoft Entra 認證來授權資料要求,以獲得最佳的安全性和使用上的便利性。可以使用 Azure 角色型存取控制 (Azure RBAC) 來管理儲存體帳戶中安全性
    📕 參閱 Microsoft Entra 資訊,BlobQueueTable Storage

Sample Code 驗證

1. 共用金鑰(Shared Key)

使用前確保了解 Azure 在講的這四種授權認證差別

(1) WebUI

兩組 key 是可以在更換主要密鑰或發生安全事件時,使用備用密鑰來保持系統的運行,同時在後續的適當時間點進行密鑰的輪換和系統的修復使用

(2) az Cli

az storage account keys list -g <MyResourceGroup> -n <MyStorageAccount>
# Result
[Warning] This output may compromise security by showing the following secrets: value. Learn more at: https://go.microsoft.com/fwlink/?linkid=2258669
[
{
"creationTime": "2024-03-14T14:43:10.550758+00:00",
"keyName": "key1",
"permissions": "FULL",
"value": "xxxxx==sas-key1==xxxxxx"
},
{
"creationTime": "2024-03-14T14:43:10.550758+00:00",
"keyName": "key2",
"permissions": "FULL",
"value": "xxxxx==sas-key2==xxxxxx
}
]

2. 連接字串(Connection String)

(1) az Cli

$ az storage account show-connection-string -g <MyResourceGroup> -n <MyStorageAccount>
[Warning] This output may compromise security by showing the following secrets: connectionString. Learn more at: https://go.microsoft.com/fwlink/?linkid=2258669
# Result
{
"connectionString": "DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net;AccountName=storage-account;AccountKey=sas-key;BlobEndpoint=https://storage-account.blob.core.windows.net/;FileEndpoint=https://storage-account.file.core.windows.net/;QueueEndpoint=https://storage-account.queue.core.windows.net/;TableEndpoint=https://storage-account.table.core.windows.net/"
}

(2)test-conn-string.py

from azure.data.tables import TableServiceClient

connection_string = "AccountName=<account>;AccountKey=<key>;EndpointSuffix=<endpoint_suffix>"
with TableServiceClient.from_connection_string(conn_str=connection_string) as table_service_client:
properties = table_service_client.get_service_properties()
print(f"{properties}")
# List all the tables in the service
print("Listing tables:")
for table in table_service_client.list_tables():
print("\t{}".format(table.name))

# Result
{'analytics_logging': , 'hour_metrics': , 'minute_metrics': , 'cors': []}
Listing tables:
nba
people
sampledata

3. 共用存取簽章權杖(Shared Access Signature Token)

(1)test-sastoken.py

from datetime import datetime, timedelta
from azure.data.tables import TableServiceClient, generate_account_sas, ResourceTypes, AccountSasPermissions
from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential

credential = AzureNamedKeyCredential("my_account_name", "my_access_key")
# Create a SAS token to use for authentication of a client
sas_token = generate_account_sas(
credential,
resource_types=ResourceTypes(service=True),
permission=AccountSasPermissions(read=True),
expiry=datetime.utcnow() + timedelta(hours=1),
)
with TableServiceClient(
endpoint="https://<my_account_name>.table.core.windows.net", credential=AzureSasCredential(sas_token)
) as table_service_client:
properties = table_service_client.get_service_properties()
print(f"{properties}")

4. Microsoft Entra 整合 TokenCredential(Azure AD)

from azure.data.tables import TableServiceClient
from azure.identity import DefaultAzureCredential

with TableServiceClient(
endpoint="https://<my_account_name>.table.core.windows.net", credential=DefaultAzureCredential()
) as table_service_client:
properties = table_service_client.get_service_properties()
print(f"{properties}")
print("Listing tables:")
for table in table_service_client.list_tables():
print("\t{}".format(table.name))

應用程式採用使用無密碼連線作法

先行閱讀

使用 Azure Active Directory(AAD)取得的 OAuth 令牌對儲存帳戶進行驗證,這有幾個優點:

  • 不需要傳遞儲存帳戶的存取金鑰,它就像主密碼:它控制對該帳戶的所有存取。如果它被洩露,該帳戶將不再安全
  • 可以使用基於角色的存取控制來限制允許哪些使用者使用該帳戶以及他們可以執行哪些操作
  • 與共用存取簽章(SAS)不同,AAD 驗證沒有硬性到期日。只要 AAD 身分(使用者、服務主體等)有正確的權限,就始終可以連接到儲存帳戶

前置作業

若要在 Python 應用程式中使用 DefaultAzureCredential,安裝 azure-identity 套件:

pip install azure-identity

建立 BlobServiceClient 物件以連線至 Azure Blob 儲存體,並務必在 BlobServiceClient 的 URI 中更新儲存體帳戶名稱

from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
blob_service_client = BlobServiceClient(
account_url = "https://%s.blob.core.windows.net" % storage_account_name,
credential = credential
)

👣Step1. 建立託管身份(Associate the managed identity with your web app)

Managed Identities 大概可以想像是把服務帳號進行包裝,變成一個 Microsoft 的邏輯層來讓認證可以抽象一層變的更簡單

這種由使用者指派的受控識別 managed identity 不需將認證儲存在程式碼中,此類受控識別 managed identity 亦會作為獨立的 Azure 資源,並有自己獨立的生命週期進行管理。單一資源(例如虛擬機器)可利用多個使用者指派的受控識別。同樣地,單一使用者指派的受控識別也可在多個資源 (例如虛擬機器)間共用。

可以使用 Azure 入口網站或 Azure CLI 建立使用者指派的託管識別碼。之後應用程式就可以使用該身分,來向其他服務進行身份驗證

  1. 在 Azure 入口網站頂部,搜尋 ~ 託管身分(Migration Identity)
  2. 選擇託管身分概述頁面頂部的+ 建立。
  3. 在「基本」標籤上,輸入以下值:
  • 訂閱:選擇您想要的訂閱
  • 資源群組:選擇您所需的資源群組
  • 區域:選擇您所在位置附近的區域
  • 名稱:輸入您的身分的可識別名稱,例如 MigrationIdentity

👣Step2. 將託管身分與您的 Web 應用程式關聯

設定 Web 應用程式以使用剛建立的託管識別(managed identity)。使用 Azure 入口網站或 Azure CLI 將識別碼指派給應用程式。

分為系統指派跟此次我們使用者指派,由使用指派我認為可以作為一個把應用程式如 Function, VM, WebApp 集中到此 identity 進行納管之效
VM 預設就沒開啟系統指派,在這個應用程式的一個元件,我們給他下一個邏輯層的 identity

亦適用於以下 Azure 服務

  • Azure Spring Apps
  • Azure Container Apps
  • Azure virtual machines
  • Azure Kubernetes Service
  1. 從左側導覽中選擇「身份識別」頁面,切換到「使用者指派」標籤
  2. 選擇 “+ 新增” 以開啟 “新增使用者指派的託管識別” 彈出視窗
  3. 選擇您之前用於建立身分的訂閱
  4. 按名稱搜尋 MigrationIdentity 並從搜尋結果中選擇它
  5. 選擇 “新增” 以將身分與您的應用程式關聯

👣Step3. 將角色指派給託管身份(Assign roles to the managed identity)

有了身份後,接下來要綁定 Role(Role 背後代表即有那些 permission),需要向您建立的託管標識授予存取儲存帳戶的權限。透過向託管識別分配角色來授予權限
註:Principe / Role / Permission 先了解雲端權限這三位一體的概念,就滿好上手

  • 在角色搜尋框中,搜尋 Storage Blob Data Contributor,這是用於管理 Blob 資料操作的常用角色。可以指派適合的任何角色
  • 「新增角色指派」畫面上,對於「指派存取權限」選項,選擇「託管身分」。然後選擇+選擇成員
  • 在彈出視窗中,搜尋您按名稱建立的託管標識並從結果中選擇它

👣Step4. 實測運作(Python)

更新一下應用程式程式碼以尋找您在部署到 Azure 時建立的特定託管識別碼,在託管身分(managed identity)頁面上,將 Client ID 值複製,DefaultAzureCredential將建構函式的 Managed_identity_client_id 參數設定為客戶端 ID。

managed_identity_client_id 至 managed identity 的這邊查詢
credential = DefaultAzureCredential(
managed_identity_client_id = managed_identity_client_id
)

補充

在 Azure 入口網站中預設為 Microsoft Entra 授權

當建立新的儲存體帳戶時,可以指定當使用者瀏覽至 Blob 資料時,Azure 入口網站將預設為使用 Microsoft Entra ID 進行授權,使用者可以覆寫此設定,並選擇使用帳戶金鑰來授權資料存取。

AzureNamedKeyCredential Class vs DefaultAzureCredential Class

一種明確指定帳戶名稱和認證密鑰的方法,相較 DefaultAzureCredential,他會嘗試從多種認證方式並就其來源獲取憑證,例如環境變量、Azure Cli 等,若是希望在不同環境保證靈活性或不要硬編碼在程式中,會推薦此 DefaultAzureCredential,可以適用在不同環境並作為上營運環境的一個認證方式。

AzureNamedKeyCredential Class
DefaultAzureCredential Class

Reference

建立應用程式註冊時,會建立兩個物件:應用程式物件和租用戶中的 Service Principle(Service Principle 2)。也可以使用此 Service Principle 授予其存取資料庫的權限(以橘色標記),但此 Service Principle 不會與您的 Azure 應用程式服務綁定。換句話說,如果您想在應用程式服務中使用 Service Principle 2,除了在資料庫中為此 Service Principle 建立使用者之外,您還需要在建立新的 SQL 連線時取得此 Service Principle 的存取權杖到您的應用程式中

--

--

Kellen

Backend(Python)/K8s and Container eco-system/Technical&Product Manager/host Developer Experience/早期投入資料創新與 ETL 工作,近期堆疊 Cloud☁️ 解決方案並記錄實作與一些雲端概念💡