c# - 使用 MSI 向 Azure Blob 存储进行身份验证时出现问题

标签 c# azure .net-core azure-webjobs service-principal

我正在尝试将我的 C# 控制台应用程序 (.net core 2.1) 连接到 blob 存储。我通过几种不同的方式初始化 Blob 存储客户端。他们是:

  1. 连接字符串 - 在开发过程中有用
  2. 服务原则 - 生产部署的可能性
  3. MSI 身份验证 - 更安全, key 会自动为我们循环

在我的代码中,如果未显式设置连接字符串,我会根据定义的应用程序设置使用服务原则或 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 实例。我尝试通过在启用了 MSI 的 Azure 中的 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 帐户设置为 Blob 存储的“所有者”和“存储帐户 key 运算符(operator)”)

我通过以下方式初始化 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);
    }
}

我获取访问 token 的主要区别在于,使用服务原则我有身份验证上下文,而使用 MSI 我没有。这会影响身份验证的范围吗?非常感谢任何帮助和建议!

最佳答案

刚刚意识到如何解决上述问题 - 更改 GetTokenAsync 以使用 TenantId 的第二个参数为身份验证调用提供上下文。

这是您需要的代码:

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

关于c# - 使用 MSI 向 Azure Blob 存储进行身份验证时出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53639305/

相关文章:

azure - 无法将虚拟机添加到 azure 中的可用性集

c# - `PowerShell.Create()` 返回 null

c# - 大多数时候,使用 google GetExternalLoginInfoAsync 的 dotnetcore 3 web api 外部登录返回 null

c# - 索引在修剪文本处超出数组边界

c# - C# 中的 SQLString

c# - 如何在 C# 中创建可点击的不规则形状区域

azure - 如何使用azure python sdk获取资源成本?

C#:验证扩展方法中的 "this"参数的最佳实践

git - 我的 Azure Pipeline 使用哪个分支?

kubernetes - .NET Core/Kubernetes - SIGTERM,干净关闭