Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

durable task scheduler auth extension support #362

Merged
merged 58 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
fc053ec
durable task scheduler auth extension
YunchuWang Jan 7, 2025
6f1d2bc
support local conn with no auth and via http
YunchuWang Jan 9, 2025
51e39bd
be consistent with azuremanaged targetversion
YunchuWang Jan 9, 2025
6987f1b
clean
YunchuWang Jan 9, 2025
e162815
namespace
YunchuWang Jan 9, 2025
e19de44
fix
YunchuWang Jan 9, 2025
3a6dc52
fix
YunchuWang Jan 9, 2025
65dfb85
doc
YunchuWang Jan 9, 2025
29445a0
ppl
YunchuWang Jan 9, 2025
e63f12a
remove
YunchuWang Jan 9, 2025
5c72ee7
test proj to sln
YunchuWang Jan 9, 2025
c6e42c5
fix warning
YunchuWang Jan 9, 2025
6a66aaa
remove dup
YunchuWang Jan 9, 2025
131c575
fix
YunchuWang Jan 9, 2025
65fa607
Revert "fix"
YunchuWang Jan 9, 2025
4f45ec5
save
YunchuWang Jan 10, 2025
d4607e4
fix
YunchuWang Jan 10, 2025
552a9c8
save
YunchuWang Jan 10, 2025
54dba76
save
YunchuWang Jan 10, 2025
5e97555
some fb
YunchuWang Jan 10, 2025
9c890c5
fix
YunchuWang Jan 10, 2025
49c6282
update
YunchuWang Jan 10, 2025
1adf7cc
update tests
YunchuWang Jan 10, 2025
14b94eb
sample
YunchuWang Jan 10, 2025
5696b2f
save
YunchuWang Jan 11, 2025
62e2b30
save
YunchuWang Jan 11, 2025
02b02c7
update
YunchuWang Jan 11, 2025
ee517d2
save
YunchuWang Jan 11, 2025
34a47ad
save
YunchuWang Jan 11, 2025
2decb79
save
YunchuWang Jan 11, 2025
ee295d0
save
YunchuWang Jan 11, 2025
522d4b0
fix
YunchuWang Jan 11, 2025
d289c10
fix
YunchuWang Jan 11, 2025
0d2a20c
Revert "sample"
YunchuWang Jan 11, 2025
f4f03fc
fix tests
YunchuWang Jan 11, 2025
59c5e9c
test accesstokencache
YunchuWang Jan 11, 2025
3dfbb72
split
YunchuWang Jan 12, 2025
5083703
fix shared
YunchuWang Jan 12, 2025
a505004
save
YunchuWang Jan 12, 2025
443613e
fix client managed
YunchuWang Jan 12, 2025
8744f8a
fix worker
YunchuWang Jan 12, 2025
37d3d5e
fix shared tests
YunchuWang Jan 12, 2025
c7f499a
fix client tests
YunchuWang Jan 12, 2025
f077e98
fix worker tests
YunchuWang Jan 12, 2025
89b7071
fix tests path
YunchuWang Jan 12, 2025
77cd2d9
update sln
YunchuWang Jan 12, 2025
3af8b62
add sample
YunchuWang Jan 10, 2025
549e105
Update src/Shared/AzureManaged/Shared.AzureManaged.csproj
YunchuWang Jan 13, 2025
3a40d09
fix compile
YunchuWang Jan 13, 2025
c697fbc
refactor
YunchuWang Jan 14, 2025
55eea54
fix
YunchuWang Jan 14, 2025
1ede0d4
fb
YunchuWang Jan 15, 2025
02d5992
fb
YunchuWang Jan 15, 2025
e0bd4e2
fb
YunchuWang Jan 15, 2025
5b4f648
fb
YunchuWang Jan 15, 2025
6f28efc
remove validateonstart
YunchuWang Jan 16, 2025
3334130
change ver
YunchuWang Jan 16, 2025
d87eed6
Update CHANGELOG and mark packages as preview
cgillum Jan 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Microsoft.DurableTask.sln
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.Tests", "test\Ana
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsApp.Tests", "samples\AzureFunctionsUnitTests\AzureFunctionsApp.Tests.csproj", "{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{5227C712-2355-403F-90D6-51D0BCAE4D38}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azure\Azure.csproj", "{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -185,6 +189,10 @@ Global
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.Build.0 = Release|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -220,6 +228,8 @@ Global
{998E9D97-BD36-4A9D-81FC-5DAC1CE40083} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{541FCCCE-1059-4691-B027-F761CD80DE92} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
{5227C712-2355-403F-90D6-51D0BCAE4D38} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC} = {5227C712-2355-403F-90D6-51D0BCAE4D38}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
Expand Down
27 changes: 27 additions & 0 deletions src/Extensions/Azure/Azure.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<PackageDescription>Azure extensions for the Durable Task Framework.</PackageDescription>
<EnableStyleCop>true</EnableStyleCop>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.13.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../../Client/Core/Client.csproj" />
<ProjectReference Include="../../Worker/Core/Worker.csproj" />
<ProjectReference Include="../../Worker/Grpc/Worker.Grpc.csproj" />
<ProjectReference Include="../../Client/Grpc/Client.Grpc.csproj" />
</ItemGroup>

<ItemGroup>
<SharedSection Include="Core" />
<SharedSection Include="DependencyInjection" />
<SharedSection Include="Grpc" />
</ItemGroup>

</Project>
82 changes: 82 additions & 0 deletions src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// ------------------------------------------------------------

Check warning on line 1 in src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The file header copyright text should match the copyright text from the settings. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1636.md)

Check warning on line 1 in src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs

View workflow job for this annotation

GitHub Actions / build

The file header copyright text should match the copyright text from the settings. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1636.md)
// Copyright (c) Microsoft Corporation.All rights reserved.
// ------------------------------------------------------------

using System.Data.Common;

namespace DurableTask.Extensions.Azure;
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Represents the constituent parts of a connection string for a Durable Task Scheduler service.
/// </summary>
public sealed class DurableTaskSchedulerConnectionString
{
readonly DbConnectionStringBuilder builder;

/// <summary>
/// Initializes a new instance of the <see cref="DurableTaskSchedulerConnectionString"/> class.
/// </summary>
/// <param name="connectionString">A connection string for a Durable Task Scheduler service.</param>
public DurableTaskSchedulerConnectionString(string connectionString)
{
this.builder = new() { ConnectionString = connectionString };
}

/// <summary>
/// Gets the authentication method specified in the connection string (if any).
/// </summary>
public string Authentication => this.GetRequiredValue("Authentication");

/// <summary>
/// Gets the managed identity or workload identity client ID specified in the connection string (if any).
/// </summary>
public string? ClientId => this.GetValue("ClientID");

/// <summary>
/// Gets the "AdditionallyAllowedTenants" property, optionally used by Workload Identity.
/// Multiple values can be separated by a comma.
/// </summary>
public IList<string>? AdditionallyAllowedTenants =>
string.IsNullOrEmpty(this.AdditionallyAllowedTenantsStr)
? null
: this.AdditionallyAllowedTenantsStr!.Split(',');

/// <summary>
/// Gets the "TenantId" property, optionally used by Workload Identity.
/// </summary>
public string? TenantId => this.GetValue("TenantId");

/// <summary>
/// Gets the "TokenFilePath" property, optionally used by Workload Identity.
/// </summary>
public string? TokenFilePath => this.GetValue("TokenFilePath");

/// <summary>
/// Gets the endpoint specified in the connection string (if any).
/// </summary>
public string Endpoint => this.GetRequiredValue("Endpoint");

/// <summary>
/// Gets the task hub name specified in the connection string.
/// </summary>
public string TaskHubName => this.GetRequiredValue("TaskHub");

string? AdditionallyAllowedTenantsStr => this.GetValue("AdditionallyAllowedTenants");

string? GetValue(string name) =>
this.builder.TryGetValue(name, out object? value)
? value as string
: null;

string GetRequiredValue(string name)
{
string? value = this.GetValue(name);
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException(
$"The connection string is missing the required '{name}' property.");
}

return value!;
}
}
156 changes: 156 additions & 0 deletions src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using Azure.Core;

Check warning on line 1 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The file header is missing or not located at the top of the file. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1633.md)
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
using System.Diagnostics;

namespace DurableTask.Extensions.Azure;

// NOTE: These extension methods will eventually be provided by the Durable Task SDK itself.
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved
public static class DurableTaskSchedulerExtensions

Check warning on line 9 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions'

Check warning on line 9 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Check warning on line 9 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions'
{
// Configure the Durable Task *Worker* to use the Durable Task Scheduler service with the specified options.
public static void UseDurableTaskScheduler(

Check warning on line 12 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions.UseDurableTaskScheduler(IDurableTaskWorkerBuilder, string, string, TokenCredential, Action<DurableTaskSchedulerOptions>?)'

Check warning on line 12 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions.UseDurableTaskScheduler(IDurableTaskWorkerBuilder, string, string, TokenCredential, Action<DurableTaskSchedulerOptions>?)'
this IDurableTaskWorkerBuilder builder,
string endpointAddress,
string taskHubName,
TokenCredential credential,
Action<DurableTaskSchedulerOptions>? configure = null)
{
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);

configure?.Invoke(options);
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved

builder.UseGrpc(GetGrpcChannelForOptions(options));
}

public static void UseDurableTaskScheduler(

Check warning on line 26 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions.UseDurableTaskScheduler(IDurableTaskWorkerBuilder, string, Action<DurableTaskSchedulerOptions>?)'

Check warning on line 26 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions.UseDurableTaskScheduler(IDurableTaskWorkerBuilder, string, Action<DurableTaskSchedulerOptions>?)'
this IDurableTaskWorkerBuilder builder,
string connectionString,
Action<DurableTaskSchedulerOptions>? configure = null)
{
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
configure?.Invoke(options);
builder.UseGrpc(GetGrpcChannelForOptions(options));
}

// Configure the Durable Task *Client* to use the Durable Task Scheduler service with the specified options.
public static void UseDurableTaskScheduler(

Check warning on line 37 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions.UseDurableTaskScheduler(IDurableTaskClientBuilder, string, string, TokenCredential, Action<DurableTaskSchedulerOptions>?)'

Check warning on line 37 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions.UseDurableTaskScheduler(IDurableTaskClientBuilder, string, string, TokenCredential, Action<DurableTaskSchedulerOptions>?)'
this IDurableTaskClientBuilder builder,
string endpointAddress,
string taskHubName,
TokenCredential credential,
Action<DurableTaskSchedulerOptions>? configure = null)
{
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);

configure?.Invoke(options);

builder.UseGrpc(GetGrpcChannelForOptions(options));
}

public static void UseDurableTaskScheduler(

Check warning on line 51 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions.UseDurableTaskScheduler(IDurableTaskClientBuilder, string, Action<DurableTaskSchedulerOptions>?)'

Check warning on line 51 in src/Extensions/Azure/DurableTaskSchedulerExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'DurableTaskSchedulerExtensions.UseDurableTaskScheduler(IDurableTaskClientBuilder, string, Action<DurableTaskSchedulerOptions>?)'
this IDurableTaskClientBuilder builder,
string connectionString,
Action<DurableTaskSchedulerOptions>? configure = null)
{
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
configure?.Invoke(options);
builder.UseGrpc(GetGrpcChannelForOptions(options));
}

static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved
{
if (string.IsNullOrEmpty(options.EndpointAddress))
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved
{
throw RequiredOptionMissing(nameof(options.TaskHubName));
}

if (string.IsNullOrEmpty(options.TaskHubName))
{
throw RequiredOptionMissing(nameof(options.TaskHubName));
}

TokenCredential credential = options.Credential ?? throw RequiredOptionMissing(nameof(options.Credential));

string taskHubName = options.TaskHubName;
string endpoint = options.EndpointAddress;

if (!endpoint.Contains("://"))
{
endpoint = $"https://{endpoint}";
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved
}

string resourceId = options.ResourceId ?? "https://durabletask.io";
#if NET6_0
int processId = Environment.ProcessId;
#else
int processId = Process.GetCurrentProcess().Id;
#endif
string workerId = options.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid()}";
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved

TokenCache? cache =
options.Credential is not null
? new(
options.Credential,
new(new[] { $"{options.ResourceId}/.default" }),
TimeSpan.FromMinutes(5))
: null;

CallCredentials managedBackendCreds = CallCredentials.FromInterceptor(
async (context, metadata) =>
{
metadata.Add("taskhub", taskHubName);
metadata.Add("workerid", workerId);

if (cache is null)
{
return;
}

AccessToken token = await cache.GetTokenAsync(context.CancellationToken);

metadata.Add("Authorization", $"Bearer {token.Token}");
});

#if NET6_0
return GrpcChannel.ForAddress(
endpoint,
new GrpcChannelOptions
{
Credentials = ChannelCredentials.Create(ChannelCredentials.SecureSsl, managedBackendCreds),
});
#else
return new GrpcChannel(
endpoint,
ChannelCredentials.Create(ChannelCredentials.SecureSsl, managedBackendCreds));
#endif
}

static Exception RequiredOptionMissing(string optionName)
{
return new ArgumentException(message: $"Required option '{optionName}' was not provided.");
}

sealed class TokenCache(TokenCredential credential, TokenRequestContext context, TimeSpan margin)
YunchuWang marked this conversation as resolved.
Show resolved Hide resolved
{
readonly TokenCredential credential = credential;
readonly TokenRequestContext context = context;
readonly TimeSpan margin = margin;

AccessToken? token;

public async Task<AccessToken> GetTokenAsync(CancellationToken cancellationToken)
{
DateTimeOffset nowWithMargin = DateTimeOffset.UtcNow.Add(this.margin);

if (this.token is null
|| this.token.Value.RefreshOn < nowWithMargin
|| this.token.Value.ExpiresOn < nowWithMargin)
{
this.token = await this.credential.GetTokenAsync(this.context, cancellationToken);
}

return this.token.Value;
}
}
}
Loading
Loading