簡體   English   中英

使用MSI驗證到Azure BlobStorage的問題

[英]Problems using MSI to authenticate to Azure BlobStorage

我正在嘗試將C#控制台應用程序(.net core 2.1)連接到Blob存儲。 我用兩種不同的方式初始化Blob存儲客戶端。 他們是:

  1. 連接字符串 -在開發過程中很有用
  2. 服務原則 -生產部署的可能性
  3. MSI身份驗證 -更安全,密鑰會自動為我們循環

在我的代碼中,如果未顯式設置連接字符串,則根據定義的應用設置(以下示例初始化代碼)使用服務原理或MSI生成它。 無論使用三種方式中的哪一種,我都將最終使用連接字符串(在1.情況下顯式設置,或在2.和3.情況下由代碼生成)初始化客戶端。

下面的代碼對1(連接字符串)和2(服務原理)可以100%正常工作,但是在嘗試達到3(MSI)時出現錯誤。

在本地運行時,出現此錯誤:

訪問令牌來自錯誤的頒發者' https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/ '。 它必須與與此訂閱關聯的租戶' https://sts.windows.net/ {my-subscription-id} /'相匹配。 請使用權限(URL)' https://login.windows.net/ {my-subscription-id}'來獲取令牌。 請注意,如果將訂閱轉移到另一個租戶,則對服務沒有影響,但是有關新租戶的信息可能需要花費一些時間才能傳播(最多一個小時)。 如果您剛剛轉移了訂閱並看到此錯誤消息,請稍后再試。

因此,我不知道“ f8cdef31-a31e-4b4a-93e4-5f571e91255a”來自哪里,它可能是全球性的Microsoft實例。 我試圖通過在啟用了MSI的Azure內的webjob中運行我的代碼來緩解這種情況,以獲取以下信息:

System.AggregateException:發生一個或多個錯誤。 (在服務連接期間發生異常,請參閱內部異常以獲取更多詳細信息)---> System.Exception:在服務連接期間發生異常,請參見內部異常以獲取更多詳細信息---> Microsoft.Rest.Azure.CloudException:客戶端對象ID為“ {my-subscription-id}”的“ {my-subscription-id}”無權對范圍“ / subscriptions / {my-subscription-id}”執行操作“ Microsoft.Storage/storageAccounts/read” ”。

請注意,我已將MSI帳戶設置為Blob存儲的“所有者”和“存儲帳戶密鑰操作員”)

我通過以下方式初始化CloudStorageAccount客戶端:

public void InitializeClient()
{
    // Always using the connection string, no matter how it's generated.
    if (ConnectionString.IsNullOrEmpty()) // if not already set, then build.
        ConnectionString = BuildStorageConnection().GetAwaiter().GetResult();

    CloudStorageAccount.TryParse(ConnectionString, out var storageAccount);

    if (storageAccount == null)
        throw new InvalidOperationException("Cannot find storage account");

    // CloudBlobClient that represents the Blob storage endpoint.
    _cloudBlobClient = storageAccount.CreateCloudBlobClient();
}

並如下構建連接字符串:

internal async Task<string> BuildStorageConnection()
{
    try
    {
        string token = null;

        if (Config.UseMsi)
        {
            // Managed Service Identity (MSI) authentication.
            var provider = new AzureServiceTokenProvider();
            token = provider.GetAccessTokenAsync("https://management.azure.com/").GetAwaiter().GetResult();

            if (string.IsNullOrEmpty(token))
                throw new InvalidOperationException("Could not authenticate using Managed Service Identity");

            _expiryTime = DateTime.Now.AddDays(1);
        }
        else
        {
            // Service Principle authentication
            // Grab an authentication token from Azure.
            var context = new AuthenticationContext("https://login.windows.net/" + Config.TenantId);

            var credential = new ClientCredential(Config.AppId, Config.AppSecret);
            var tokenResult = context.AcquireTokenAsync("https://management.azure.com/", credential).GetAwaiter().GetResult();

            if (tokenResult == null || tokenResult.AccessToken == null)
                throw new InvalidOperationException($"Could not authenticate using Service Principle");

            _expiryTime = tokenResult.ExpiresOn;
            token = tokenResult.AccessToken;
        }

        // Set credentials and grab the authenticated REST client.
        var tokenCredentials = new TokenCredentials(token);

        var client = RestClient.Configure()
            .WithEnvironment(AzureEnvironment.AzureGlobalCloud)
            .WithLogLevel(HttpLoggingDelegatingHandler.Level.BodyAndHeaders)
            .WithCredentials(new AzureCredentials(tokenCredentials, tokenCredentials, string.Empty, AzureEnvironment.AzureGlobalCloud))
            .WithRetryPolicy(new RetryPolicy(new HttpStatusCodeErrorDetectionStrategy(), new FixedIntervalRetryStrategy(3, TimeSpan.FromMilliseconds(500))))
            .Build();

        // Authenticate against the management layer.
        var azureManagement = Azure.Authenticate(client, string.Empty).WithSubscription(Config.SubscriptionId);

        // Get the storage namespace for the passed in instance name.
        var storageNamespace = azureManagement.StorageAccounts.List().FirstOrDefault(n => n.Name == Config.StorageInstanceName);

        // If we cant find that name, throw an exception.
        if (storageNamespace == null)
        {
            throw new InvalidOperationException($"Could not find the storage instance {Config.StorageInstanceName} in the subscription with ID {Config.SubscriptionId}");
        }

        // Storage accounts use access keys - this will be used to build a connection string.
        var accessKeys = await storageNamespace.GetKeysAsync();

        // If the access keys are not found (not configured for some reason), throw an exception.
        if (accessKeys == null)
        {
            throw new InvalidOperationException($"Could not find access keys for the storage instance {Config.StorageInstanceName}");
        }

        // We just default to the first key.
        var key = accessKeys[0].Value;

        // Build and return the connection string.
        return $"DefaultEndpointsProtocol=https;AccountName={Config.StorageInstanceName};AccountKey={key};EndpointSuffix=core.windows.net";
    }
    catch (Exception e)
    {
        Logger?.LogError(e, "An exception occured during connection to blob storage");
        throw new Exception("An exception occurred during service connection, see inner exception for more detail", e);
    }
}

我獲得訪問令牌的主要區別在於,使用服務原理,我具有身份驗證上下文,而使用MSI,我沒有。 這會影響認證范圍嗎? 任何幫助和建議,對此深表感謝!

剛剛意識到如何解決上述問題-將GetTokenAsync更改為具有TenantId的第二個參數可為身份驗證調用提供上下文。

這是您需要的代碼:

token = await provider.GetAccessTokenAsync("https://management.azure.com/", Config.TenantId);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM