-
Notifications
You must be signed in to change notification settings - Fork 53
durable task scheduler auth extension support #362
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
Changes from all commits
fc053ec
6f1d2bc
51e39bd
6987f1b
e162815
e19de44
3a6dc52
65dfb85
29445a0
e63f12a
5c72ee7
c6e42c5
6a66aaa
131c575
65fa607
4f45ec5
d4607e4
552a9c8
54dba76
5e97555
9c890c5
49c6282
1adf7cc
14b94eb
5696b2f
62e2b30
02b02c7
ee517d2
34a47ad
2decb79
ee295d0
522d4b0
d289c10
0d2a20c
f4f03fc
59c5e9c
3dfbb72
5083703
a505004
443613e
8744f8a
37d3d5e
c7f499a
f077e98
89b7071
77cd2d9
3af8b62
549e105
3a40d09
c697fbc
55eea54
1ede0d4
02d5992
e0bd4e2
5b4f648
6f28efc
3334130
d87eed6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net6.0</TargetFramework> | ||
| <PackageDescription>Azure Managed extensions for the Durable Task Framework client.</PackageDescription> | ||
| <EnableStyleCop>true</EnableStyleCop> | ||
| <VersionSuffix>preview.1</VersionSuffix> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="../../Client/Grpc/Client.Grpc.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.Identity" Version="1.13.1" /> | ||
| <PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="6.0.1" /> | ||
| <PackageReference Include="Grpc.Net.Client" Version="2.67.0" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <SharedSection Include="AzureManaged" /> | ||
| <SharedSection Include="Core" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Azure.Core; | ||
| using Microsoft.DurableTask.Client.Grpc; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.DependencyInjection.Extensions; | ||
| using Microsoft.Extensions.Options; | ||
|
|
||
| namespace Microsoft.DurableTask.Client.AzureManaged; | ||
|
|
||
| /// <summary> | ||
| /// Extension methods for configuring Durable Task clients to use the Azure Durable Task Scheduler service. | ||
| /// </summary> | ||
| public static class DurableTaskSchedulerClientExtensions | ||
| { | ||
| /// <summary> | ||
| /// Configures Durable Task client to use the Azure Durable Task Scheduler service. | ||
| /// </summary> | ||
| /// <param name="builder">The Durable Task client builder to configure.</param> | ||
| /// <param name="endpointAddress">The endpoint address of the Durable Task Scheduler resource. Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".</param> | ||
| /// <param name="taskHubName">The name of the task hub resource associated with the Durable Task Scheduler resource.</param> | ||
| /// <param name="credential">The credential used to authenticate with the Durable Task Scheduler task hub resource.</param> | ||
| /// <param name="configure">Optional callback to dynamically configure DurableTaskSchedulerClientOptions.</param> | ||
| public static void UseDurableTaskScheduler( | ||
| this IDurableTaskClientBuilder builder, | ||
| string endpointAddress, | ||
| string taskHubName, | ||
| TokenCredential credential, | ||
| Action<DurableTaskSchedulerClientOptions>? configure = null) | ||
| { | ||
| ConfigureSchedulerOptions( | ||
| builder, | ||
| options => | ||
| { | ||
| options.EndpointAddress = endpointAddress; | ||
| options.TaskHubName = taskHubName; | ||
| options.Credential = credential; | ||
| }, | ||
| configure); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Configures Durable Task client to use the Azure Durable Task Scheduler service using a connection string. | ||
| /// </summary> | ||
| /// <param name="builder">The Durable Task client builder to configure.</param> | ||
| /// <param name="connectionString">The connection string used to connect to the Durable Task Scheduler service.</param> | ||
| /// <param name="configure">Optional callback to dynamically configure DurableTaskSchedulerClientOptions.</param> | ||
| public static void UseDurableTaskScheduler( | ||
| this IDurableTaskClientBuilder builder, | ||
| string connectionString, | ||
| Action<DurableTaskSchedulerClientOptions>? configure = null) | ||
| { | ||
| var connectionOptions = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString); | ||
| ConfigureSchedulerOptions( | ||
| builder, | ||
| options => | ||
| { | ||
| options.EndpointAddress = connectionOptions.EndpointAddress; | ||
| options.TaskHubName = connectionOptions.TaskHubName; | ||
| options.Credential = connectionOptions.Credential; | ||
| }, | ||
| configure); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Configures Durable Task client to use the Azure Durable Task Scheduler service using configuration options. | ||
| /// </summary> | ||
| /// <param name="builder">The Durable Task client builder to configure.</param> | ||
| /// <param name="configure">Callback to configure DurableTaskSchedulerClientOptions.</param> | ||
| public static void UseDurableTaskScheduler( | ||
| this IDurableTaskClientBuilder builder, | ||
| Action<DurableTaskSchedulerClientOptions>? configure = null) | ||
| { | ||
| ConfigureSchedulerOptions(builder, _ => { }, configure); | ||
| } | ||
|
|
||
| static void ConfigureSchedulerOptions( | ||
| IDurableTaskClientBuilder builder, | ||
| Action<DurableTaskSchedulerClientOptions> initialConfig, | ||
| Action<DurableTaskSchedulerClientOptions>? additionalConfig) | ||
| { | ||
| builder.Services.AddOptions<DurableTaskSchedulerClientOptions>(builder.Name) | ||
| .Configure(initialConfig) | ||
| .Configure(additionalConfig ?? (_ => { })) | ||
| .ValidateDataAnnotations(); | ||
|
|
||
| builder.Services.TryAddEnumerable( | ||
| ServiceDescriptor.Singleton<IConfigureOptions<GrpcDurableTaskClientOptions>, ConfigureGrpcChannel>()); | ||
| builder.UseGrpc(_ => { }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Configuration class that sets up gRPC channels for client options | ||
| /// using the provided Durable Task Scheduler options. | ||
| /// </summary> | ||
| /// <param name="schedulerOptions">Monitor for accessing the current scheduler options configuration.</param> | ||
| class ConfigureGrpcChannel(IOptionsMonitor<DurableTaskSchedulerClientOptions> schedulerOptions) : | ||
| IConfigureNamedOptions<GrpcDurableTaskClientOptions> | ||
| { | ||
| /// <summary> | ||
| /// Configures the default named options instance. | ||
| /// </summary> | ||
| /// <param name="options">The options instance to configure.</param> | ||
| public void Configure(GrpcDurableTaskClientOptions options) => this.Configure(Options.DefaultName, options); | ||
|
|
||
| /// <summary> | ||
| /// Configures a named options instance. | ||
| /// </summary> | ||
| /// <param name="name">The name of the options instance to configure.</param> | ||
| /// <param name="options">The options instance to configure.</param> | ||
| public void Configure(string? name, GrpcDurableTaskClientOptions options) | ||
| { | ||
| DurableTaskSchedulerClientOptions source = schedulerOptions.Get(name ?? Options.DefaultName); | ||
| options.Channel = source.CreateChannel(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
| using System.ComponentModel.DataAnnotations; | ||
| using Azure.Core; | ||
| using Azure.Identity; | ||
| using Grpc.Core; | ||
| using Grpc.Net.Client; | ||
|
|
||
| namespace Microsoft.DurableTask; | ||
|
|
||
| /// <summary> | ||
| /// Options for configuring the Durable Task Scheduler. | ||
| /// </summary> | ||
| public class DurableTaskSchedulerClientOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the endpoint address of the Durable Task Scheduler resource. | ||
| /// Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io". | ||
| /// </summary> | ||
| [Required(ErrorMessage = "Endpoint address is required")] | ||
| public string EndpointAddress { get; set; } = string.Empty; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the name of the task hub resource associated with the Durable Task Scheduler resource. | ||
| /// </summary> | ||
| [Required(ErrorMessage = "Task hub name is required")] | ||
| public string TaskHubName { get; set; } = string.Empty; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the credential used to authenticate with the Durable Task Scheduler task hub resource. | ||
| /// </summary> | ||
| public TokenCredential? Credential { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the resource ID of the Durable Task Scheduler resource. | ||
| /// The default value is https://durabletask.io. | ||
| /// </summary> | ||
| public string ResourceId { get; set; } = "https://durabletask.io"; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a value indicating whether to allow insecure channel credentials. | ||
| /// This should only be set to true in local development/testing scenarios. | ||
| /// </summary> | ||
| public bool AllowInsecureCredentials { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Creates a new instance of <see cref="DurableTaskSchedulerClientOptions"/> from a connection string. | ||
| /// </summary> | ||
| /// <param name="connectionString">The connection string to parse.</param> | ||
| /// <returns>A new instance of <see cref="DurableTaskSchedulerClientOptions"/>.</returns> | ||
| public static DurableTaskSchedulerClientOptions FromConnectionString(string connectionString) | ||
| { | ||
| return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a gRPC channel for communicating with the Durable Task Scheduler service. | ||
| /// </summary> | ||
| /// <returns>A configured <see cref="GrpcChannel"/> instance that can be used to make gRPC calls.</returns> | ||
| internal GrpcChannel CreateChannel() | ||
| { | ||
| Verify.NotNull(this.EndpointAddress, nameof(this.EndpointAddress)); | ||
| Verify.NotNull(this.TaskHubName, nameof(this.TaskHubName)); | ||
| string taskHubName = this.TaskHubName; | ||
| string endpoint = !this.EndpointAddress.Contains("://") | ||
| ? $"https://{this.EndpointAddress}" | ||
| : this.EndpointAddress; | ||
| AccessTokenCache? cache = | ||
| this.Credential is not null | ||
| ? new AccessTokenCache( | ||
| this.Credential, | ||
| new TokenRequestContext(new[] { $"{this.ResourceId}/.default" }), | ||
| TimeSpan.FromMinutes(5)) | ||
| : null; | ||
| CallCredentials managedBackendCreds = CallCredentials.FromInterceptor( | ||
| async (context, metadata) => | ||
| { | ||
| metadata.Add("taskhub", taskHubName); | ||
| if (cache == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| AccessToken token = await cache.GetTokenAsync(context.CancellationToken); | ||
| metadata.Add("Authorization", $"Bearer {token.Token}"); | ||
| }); | ||
|
|
||
| // Production will use HTTPS, but local testing will use HTTP | ||
| ChannelCredentials channelCreds = endpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ? | ||
| ChannelCredentials.SecureSsl : | ||
| ChannelCredentials.Insecure; | ||
| return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions | ||
| { | ||
| Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds), | ||
| UnsafeUseInsecureChannelCallCredentials = this.AllowInsecureCredentials, | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a new instance of <see cref="DurableTaskSchedulerClientOptions"/> from a parsed connection string. | ||
| /// </summary> | ||
| /// <param name="connectionString">The connection string to parse.</param> | ||
| /// <returns>A new instance of <see cref="DurableTaskSchedulerClientOptions"/>.</returns> | ||
| internal static DurableTaskSchedulerClientOptions FromConnectionString( | ||
|
Check warning on line 104 in src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
|
||
| DurableTaskSchedulerConnectionString connectionString) => new() | ||
| { | ||
| EndpointAddress = connectionString.Endpoint, | ||
| TaskHubName = connectionString.TaskHubName, | ||
| Credential = GetCredentialFromConnectionString(connectionString), | ||
| }; | ||
|
|
||
| static TokenCredential? GetCredentialFromConnectionString(DurableTaskSchedulerConnectionString connectionString) | ||
| { | ||
| string authType = connectionString.Authentication; | ||
|
|
||
| // Parse the supported auth types, in a case-insensitive way and without spaces | ||
| switch (authType.ToLowerInvariant()) | ||
| { | ||
| case "defaultazure": | ||
| return new DefaultAzureCredential(); | ||
| case "managedidentity": | ||
| return new ManagedIdentityCredential(connectionString.ClientId); | ||
| case "workloadidentity": | ||
| var opts = new WorkloadIdentityCredentialOptions(); | ||
| if (!string.IsNullOrEmpty(connectionString.ClientId)) | ||
| { | ||
| opts.ClientId = connectionString.ClientId; | ||
| } | ||
|
|
||
| if (!string.IsNullOrEmpty(connectionString.TenantId)) | ||
| { | ||
| opts.TenantId = connectionString.TenantId; | ||
| } | ||
|
|
||
| if (connectionString.AdditionallyAllowedTenants is not null) | ||
| { | ||
| foreach (string tenant in connectionString.AdditionallyAllowedTenants) | ||
| { | ||
| opts.AdditionallyAllowedTenants.Add(tenant); | ||
| } | ||
| } | ||
|
|
||
| return new WorkloadIdentityCredential(opts); | ||
| case "environment": | ||
| return new EnvironmentCredential(); | ||
| case "azurecli": | ||
| return new AzureCliCredential(); | ||
| case "azurepowershell": | ||
| return new AzurePowerShellCredential(); | ||
| case "none": | ||
| return null; | ||
| default: | ||
| throw new ArgumentException( | ||
| $"The connection string contains an unsupported authentication type '{authType}'.", | ||
| nameof(connectionString)); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.