Azure / durabletask

Durable Task Framework allows users to write long running persistent workflows in C# using the async/await capabilities.
Apache License 2.0
1.47k stars 287 forks source link

Support for connecting to storage using managed identity in DurableTask.AzureStorage and Dotnet Framework 4.8 #1093

Open cool-mist opened 1 month ago

cool-mist commented 1 month ago

Problem

How to use managed identity while connecting to storage account when using DurableTask.AzureStorage?

Hi, we are a microsoft internal team using durable task framework in our service that runs on dotnet framework 4.8. We are trying to use managed identity to connect to storage. The following code already works to connect to storage using managed identity for dotnet 8+, but fails with the error Unhandled Exception: System.InvalidOperationException: Token credential is not supported for this service. when using dotnet 4.8.

Dependencies

    <PackageReference Include="azure.identity" Version="1.11.0" />
    <PackageReference Include="Microsoft.Azure.DurableTask.AzureStorage" Version="1.17.1" />
    <PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />

Repro

The following code correctly uses managed identity for <TargetFramework>net8.0</TargetFramework> but fails with an error when using <TargetFramework>net48</TargetFramework>.

Unhandled Exception: System.InvalidOperationException: Token credential is not supported for this service.
   at Microsoft.WindowsAzure.Storage.Table.CloudTableClient.get_AuthenticationHandler()
   at Microsoft.WindowsAzure.Storage.Table.TableOperation.InsertImpl(TableOperation operation, CloudTableClient client, CloudTable table, TableRequestOptions requestOptions)
   at Microsoft.WindowsAzure.Storage.Table.TableOperation.BeginExecute(CloudTableClient client, CloudTable table, TableRequestOptions requestOptions, OperationContext operationContext, AsyncCallback callback, Ob

Code

using Azure.Core;
using Azure.Identity;
using DurableTask.AzureStorage;
using DurableTask.Core;
using Microsoft.WindowsAzure.Storage.Auth;

internal class Program
{
    private static async Task Main(string[] args)
    {
        var credential = new AzureCliCredential();
        var initialToken = credential.GetToken(new TokenRequestContext(new[] { "https://storage.azure.com/.default" }));
        var service = new AzureStorageOrchestrationService(new AzureStorageOrchestrationServiceSettings
        {
            StorageAccountDetails = new StorageAccountDetails
            {
                AccountName = "sndeltest",
                EndpointSuffix = "core.windows.net",
                StorageCredentials = new StorageCredentials(
                    new Microsoft.WindowsAzure.Storage.Auth.TokenCredential(
                      initialToken.Token,
                      RenewTokenFuncAsync,
                      null,
                      TimeSpan.FromMinutes(5)))
            },
        });
        var client = new TaskHubClient(service);
        var worker = new TaskHubWorker(service);
        worker.AddTaskOrchestrations(typeof(SampleOrchestration));
        worker.AddTaskActivities(typeof(SampleActivity));
        Console.WriteLine("Starting worker...");
        await worker.StartAsync();

        Console.WriteLine("Creating orchestration...");
        var orch = await client.CreateOrchestrationInstanceAsync(typeof(SampleOrchestration), "World");

        Console.WriteLine("Waiting orchestration...");
        var result = await client.WaitForOrchestrationAsync(orch, TimeSpan.FromMinutes(1));

        Console.WriteLine($"Orchestration result : {result.Output}");

        Console.WriteLine("Stopping worker...");
        await worker.StopAsync();
    }

    public static Task<NewTokenAndFrequency> RenewTokenFuncAsync(object state, CancellationToken cancellationToken)
    {
        var credential = new DefaultAzureCredential();
        var initialToken = credential.GetToken(new TokenRequestContext(new[] { "https://storage.azure.com/.default" }));
        var expiresAfter = initialToken.ExpiresOn - DateTimeOffset.UtcNow - TimeSpan.FromMinutes(10);
        return Task.FromResult(new NewTokenAndFrequency(initialToken.Token, expiresAfter));
    }
}

public class SampleOrchestration : TaskOrchestration<string, string>
{
    public override async Task<string> RunTask(OrchestrationContext context, string input)
    {
        return await context.ScheduleTask<string>(typeof(SampleActivity), input);
    }
}

public class SampleActivity : TaskActivity<string, string>
{
    protected override string Execute(TaskContext context, string input)
    {
        return "Hello, " + input + "!";
    }
}
cool-mist commented 1 month ago

I think this might have to do with the usage of WindowsAzure.Storage in the DTF library

ggghhhhhh112 commented 1 month ago

have any undate? it's an security issue, need to be solved.

nytian commented 1 week ago

@cool-mist When using .net48, the DTFx.AS will use dependencies for framework net462 since it's companiable. And thus WindowsAzure.Storage v7.2.1 will be picked. However, this version doesn't support token credentials. So you have to use .NET framework 5.x and onwards to use the dependency WindowsAzure.Storage 9.3.1 which can support token credential class.

nytian commented 1 week ago

If .NET framework 4.8 has to be used, I would recommend using DTFx.AS v2. This package uses the latest Azure Storage SDK and thus can support managed identity with dotnet 4.x. But please notice that this package is still in preview. GA is targeting at the end of this month right now.

Samples to use token credential with DTFx.AS v2:


        var credential = new DefaultAzureCredential(); // use configuration to create credential, it can also be other token credential.
        var service = new AzureStorageOrchestrationService(new AzureStorageOrchestrationServiceSettings
        {
            StorageAccountClientProvider = new StorageAccountClientProvider(yourStorageAccountName, credential),
        });
ismailkareem commented 5 days ago

Hey @nytian, as the month's end approaches, do you have an updated ETA for the release?

nytian commented 5 days ago

@KareemIsmail-M Thanks for being interested at Durable v3! You can refer to this Github issue page to check the progress of v3 GA. The only blocker right now is to decide our .NET versions in our repo, since there will be a change about this on Functions scope. We should decide this by tomorrow and then I can provide a more accurate release date.

Also, this is not a big work item so the release shouldn't be impacted a lot.

saguiitay commented 4 days ago

@nytian, please be aware that we (myself and @KareemIsmail-M) are actually interested with the DTFx (and the AS package) specifically, and not with Durable Functions. We're from an internal MS team that have a dependency on DTfx, and needs the MI capability.