使用 MSI 对 Azure Blob 存储进行身份验证时出现问题

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) 时遇到错误。

当 运行 在本地时,我收到此错误:

The access token is from the wrong issuer 'https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/'. It must match the tenant 'https://sts.windows.net/{my-subscription-id}/' associated with this subscription. Please use the authority (URL) 'https://login.windows.net/{my-subscription-id}' to get the token. Note, if the subscription is transferred to another tenant there is no impact to the services, but information about new tenant could take time to propagate (up to an hour). If you just transferred your subscription and see this error message, please try back later.

有了这个,我不知道 'f8cdef31-a31e-4b4a-93e4-5f571e91255a' 来自哪里,它可能是全球 Microsoft 实例。我试图通过 运行 我在 Azure 中启用了 MSI 的 webjob 中的代码来缓解这种情况,看看它是否有效,我得到:

System.AggregateException: One or more errors occurred. (An exception occurred during service connection, see inner exception for more detail) ---> System.Exception: An exception occurred during service connection, see inner exception for more detail ---> Microsoft.Rest.Azure.CloudException: The client '{my-subscription-id}' with object id '{my-subscription-id}' does not have authorization to perform action 'Microsoft.Storage/storageAccounts/read' over scope '/subscriptions/{my-subscription-id}'.

(注意 我将 MSI 帐户设置为 'owner' 和 'Storage Account Key Operator' 的 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);