Skip to content

Issue with repeatedly requesting new DefaultAzureCredential #14

@brad-dawson

Description

@brad-dawson

When using tus while developing on a localhost connecting to remote Azure resources, I experience the error shown in the screenshot. It seems like Azure might be rate limiting our DefaultAzureCredential requests, breaking the tus upload process with the exception Azure.Identity.AuthenticationFailedException: ManagedIdentityCredential authentication failed: Invalid argument.

Image

From what I see, this can be resolved by giving the developer the option to select a lazy identity option and reuse the DefaultAzureCredential for the use case where we only care about refreshing the credential on server start.

// AzureBlobTusStoreAuthenticationMode.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace Xtensible.TusDotNet.Azure
{
    /// <summary>
    /// Modes for how the library authenticates with the Azure storage account.
    /// ConnectionString: Use a connection string with either a Shared Access Signature (SAS) or access key. See https://learn.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string.
    /// SystemAssignedManagedIdentity: Authenticate using Azure system-assigned managed identity. See https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview.
    /// SystemAssignedManagedIdentityLazy: Uses the <see cref="System.Lazy{DefaultAzureCredential}"/> to create a single instance of the credential. This is useful when the credential is expensive to create.
    ///     Thread safe using the <see cref="System.Threading.LazyThreadSafetyMode.ExecutionAndPublication"/> mode.
    /// </summary>
    public enum AzureBlobTusStoreAuthenticationMode
    {
        ConnectionString = 0,
        SystemAssignedManagedIdentity = 1,
        SystemAssignedManagedIdentityLazy = 2,
    }
}

// AzureBlobClientFactory.cs:

using Azure.Core;
using Azure.Identity;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Specialized;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

namespace Xtensible.TusDotNet.Azure
{
    internal static class AzureBlobClientFactory
    {
        private static readonly Lazy<DefaultAzureCredential> AzureCredential = new(() => new DefaultAzureCredential(), LazyThreadSafetyMode.ExecutionAndPublication);
        
        public static BlobServiceClient CreateBlobServiceClient(AzureBlobTusStoreAuthenticationMode authenticationMode, string connectionString)
        {

            if (new [] {AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentity, AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentityLazy}.Contains(authenticationMode))
            {
                var connectionStringIsUri = Uri.TryCreate(connectionString, UriKind.Absolute, out var blobStorageUri);
                if (!connectionStringIsUri)
                {
                    throw new ArgumentException("connectionString must be a Azure Blob Storage URI when authentication mode is SystemAssignedManagedIdentity");
                }

                return new BlobServiceClient(blobStorageUri, authenticationMode == AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentityLazy 
                    ? AzureCredential.Value 
                    : new DefaultAzureCredential());
            }
            else
            {
                return new BlobServiceClient(connectionString);
            }
        }

        public static AppendBlobClient CreateAppendBlobClient(AzureBlobTusStoreAuthenticationMode authenticationMode, string connectionString, string containerName, string blobPath) 
        {
            if (new [] {AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentity, AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentityLazy}.Contains(authenticationMode))
            {
                var connectionStringIsUri = Uri.TryCreate(connectionString, UriKind.Absolute, out var blobStorageUri);
                if (!connectionStringIsUri)
                {
                    throw new ArgumentException("connectionString must be a Azure Blob Storage URI when authentication mode is SystemAssignedManagedIdentity");
                }
                return new AppendBlobClient(new Uri(blobStorageUri, $"{containerName}/{blobPath}"), authenticationMode == AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentityLazy 
                    ? AzureCredential.Value 
                    : new DefaultAzureCredential());
            }
            else
            {
                return new AppendBlobClient(connectionString, containerName, blobPath);
            }
        }

        public static BlobContainerClient CreateBlobContainerClient(AzureBlobTusStoreAuthenticationMode authenticationMode, string connectionString, string containerName) 
        {
            if (new [] {AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentity, AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentityLazy}.Contains(authenticationMode))
            {
                var connectionStringIsUri = Uri.TryCreate(connectionString, UriKind.Absolute, out var blobStorageUri);
                if (!connectionStringIsUri)
                {
                    throw new ArgumentException("connectionString must be a Azure Blob Storage URI when authentication mode is SystemAssignedManagedIdentity");
                }
                return new BlobContainerClient(new Uri(blobStorageUri, containerName), authenticationMode == AzureBlobTusStoreAuthenticationMode.SystemAssignedManagedIdentityLazy 
                    ? AzureCredential.Value 
                    : new DefaultAzureCredential());
            }
            else
            {
                return new BlobContainerClient(connectionString, containerName);
            }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions