Skip to content

Commit 382c283

Browse files
authored
Add CertificateClient and KeyClient support to Aspire.Azure.Security.KeyVault (#8408)
1 parent b0c0000 commit 382c283

13 files changed

+676
-59
lines changed

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
<PackageVersion Include="Azure.Search.Documents" Version="11.6.0" />
2222
<PackageVersion Include="Azure.Messaging.WebPubSub" Version="1.4.0" />
2323
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
24+
<PackageVersion Include="Azure.Security.KeyVault.Certificates" Version="4.7.0" />
25+
<PackageVersion Include="Azure.Security.KeyVault.Keys" Version="4.7.0" />
2426
<PackageVersion Include="Azure.Storage.Blobs" Version="12.24.0" />
2527
<PackageVersion Include="Azure.Storage.Queues" Version="12.22.0" />
2628
<PackageVersion Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="8.1.1" />
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using Aspire.Azure.Common;
6+
using Azure.Core;
7+
using Azure.Core.Extensions;
8+
using Microsoft.Extensions.Azure;
9+
10+
namespace Aspire.Azure.Security.KeyVault;
11+
12+
/// <summary>
13+
/// <para>Abstracts the common configuration binding required by <see cref="AzureComponent{TSettings, TClient, TClientOptions}"/></para>
14+
/// <para>Deriving type implements KeyVaultClient specific item:</para>
15+
/// <para><see cref="AzureComponent{TSettings, TClient, TClientOptions}.CreateHealthCheck(TClient, TSettings)"/></para>
16+
/// </summary>
17+
/// <typeparam name="TClient">The KeyVaultClient type for this component.</typeparam>
18+
/// <typeparam name="TOptions">The associated configuration for the <typeparamref name="TClient"/></typeparam>
19+
internal abstract class AbstractAzureKeyVaultComponent<TClient, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TOptions>
20+
: AzureComponent<AzureSecurityKeyVaultSettings, TClient, TOptions>
21+
where TClient : class
22+
where TOptions : class
23+
{
24+
internal abstract TClient CreateComponentClient(Uri vaultUri, TOptions options, TokenCredential cred);
25+
26+
protected override IAzureClientBuilder<TClient, TOptions> AddClient(AzureClientFactoryBuilder azureFactoryBuilder, AzureSecurityKeyVaultSettings settings, string connectionName, string configurationSectionName)
27+
{
28+
return azureFactoryBuilder.AddClient<TClient, TOptions>((options, cred, _) =>
29+
{
30+
if (settings.VaultUri is null)
31+
{
32+
throw new InvalidOperationException($"VaultUri is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'VaultUri' key in the '{configurationSectionName}' configuration section.");
33+
}
34+
35+
return CreateComponentClient(settings.VaultUri, options, cred);
36+
});
37+
}
38+
39+
protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
40+
=> !settings.DisableHealthChecks;
41+
42+
protected override TokenCredential? GetTokenCredential(AzureSecurityKeyVaultSettings settings)
43+
=> settings.Credential;
44+
45+
protected override bool GetMetricsEnabled(AzureSecurityKeyVaultSettings settings)
46+
=> false;
47+
48+
protected override bool GetTracingEnabled(AzureSecurityKeyVaultSettings settings)
49+
=> !settings.DisableTracing;
50+
}

src/Components/Aspire.Azure.Security.KeyVault/Aspire.Azure.Security.KeyVault.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
<PackageReference Include="AspNetCore.HealthChecks.Azure.KeyVault.Secrets" />
2020
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
2121
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
22+
<PackageReference Include="Azure.Security.KeyVault.Certificates" />
23+
<PackageReference Include="Azure.Security.KeyVault.Keys" />
2224
<PackageReference Include="Microsoft.Extensions.Azure" />
2325
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
2426
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />

src/Components/Aspire.Azure.Security.KeyVault/AspireKeyVaultExtensions.cs

Lines changed: 94 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33

44
using Aspire.Azure.Common;
55
using Aspire.Azure.Security.KeyVault;
6-
using Azure.Core;
76
using Azure.Core.Extensions;
87
using Azure.Extensions.AspNetCore.Configuration.Secrets;
98
using Azure.Identity;
9+
using Azure.Security.KeyVault.Certificates;
10+
using Azure.Security.KeyVault.Keys;
1011
using Azure.Security.KeyVault.Secrets;
11-
using HealthChecks.Azure.KeyVault.Secrets;
12-
using Microsoft.Extensions.Azure;
1312
using Microsoft.Extensions.Configuration;
1413
using Microsoft.Extensions.DependencyInjection;
15-
using Microsoft.Extensions.Diagnostics.HealthChecks;
1614

1715
namespace Microsoft.Extensions.Hosting;
1816

@@ -42,7 +40,8 @@ public static void AddAzureKeyVaultClient(
4240
ArgumentNullException.ThrowIfNull(builder);
4341
ArgumentException.ThrowIfNullOrEmpty(connectionName);
4442

45-
new KeyVaultComponent().AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName, serviceKey: null);
43+
new AzureKeyVaultSecretsComponent()
44+
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName, serviceKey: null);
4645
}
4746

4847
/// <summary>
@@ -64,7 +63,96 @@ public static void AddKeyedAzureKeyVaultClient(
6463
ArgumentNullException.ThrowIfNull(builder);
6564
ArgumentException.ThrowIfNullOrEmpty(name);
6665

67-
new KeyVaultComponent().AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName: name, serviceKey: name);
66+
new AzureKeyVaultSecretsComponent()
67+
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName: name, serviceKey: name);
68+
}
69+
70+
/// <summary>
71+
/// Registers <see cref="CertificateClient"/> as a singleton in the services provided by the <paramref name="builder"/>.
72+
/// </summary>
73+
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
74+
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
75+
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureSecurityKeyVaultSettings"/>. It's invoked after the settings are read from the configuration.</param>
76+
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{TClient, TOptions}"/>.</param>
77+
/// <remarks>Reads the configuration from "Aspire:Azure:Security:KeyVault:{name}" section.</remarks>
78+
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="AzureSecurityKeyVaultSettings.VaultUri"/> is not provided.</exception>
79+
public static void AddAzureKeyVaultCertificateClient(
80+
this IHostApplicationBuilder builder,
81+
string connectionName,
82+
Action<AzureSecurityKeyVaultSettings>? configureSettings = null,
83+
Action<IAzureClientBuilder<CertificateClient, CertificateClientOptions>>? configureClientBuilder = null)
84+
{
85+
ArgumentNullException.ThrowIfNull(builder);
86+
ArgumentException.ThrowIfNullOrEmpty(connectionName);
87+
88+
new AzureKeyVaultCertificatesComponent()
89+
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName, serviceKey: null);
90+
}
91+
92+
/// <summary>
93+
/// Registers <see cref="CertificateClient"/> as a singleton for given <paramref name="name"/> in the services provided by the <paramref name="builder"/>.
94+
/// </summary>
95+
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
96+
/// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection information from the ConnectionStrings configuration section.</param>
97+
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureSecurityKeyVaultSettings"/>. It's invoked after the settings are read from the configuration.</param>
98+
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{TClient, TOptions}"/>.</param>
99+
/// <remarks>Reads the configuration from "Aspire:Azure:Security:KeyVault:{name}" section.</remarks>
100+
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="AzureSecurityKeyVaultSettings.VaultUri"/> is not provided.</exception>
101+
public static void AddKeyedAzureKeyVaultCertificateClient(
102+
this IHostApplicationBuilder builder,
103+
string name,
104+
Action<AzureSecurityKeyVaultSettings>? configureSettings = null,
105+
Action<IAzureClientBuilder<CertificateClient, CertificateClientOptions>>? configureClientBuilder = null)
106+
{
107+
ArgumentNullException.ThrowIfNull(builder);
108+
ArgumentException.ThrowIfNullOrEmpty(name);
109+
110+
new AzureKeyVaultCertificatesComponent()
111+
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName: name, serviceKey: name);
112+
}
113+
114+
/// <summary>
115+
/// Registers <see cref="KeyClient"/> as a singleton in the services provided by the <paramref name="builder"/>.
116+
/// </summary>
117+
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
118+
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
119+
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureSecurityKeyVaultSettings"/>. It's invoked after the settings are read from the configuration.</param>
120+
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{TClient, TOptions}"/>.</param>
121+
/// <remarks>Reads the configuration from "Aspire:Azure:Security:KeyVault:{name}" section.</remarks>
122+
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="AzureSecurityKeyVaultSettings.VaultUri"/> is not provided.</exception>
123+
public static void AddAzureKeyVaultKeyClient(
124+
this IHostApplicationBuilder builder,
125+
string connectionName,
126+
Action<AzureSecurityKeyVaultSettings>? configureSettings = null,
127+
Action<IAzureClientBuilder<KeyClient, KeyClientOptions>>? configureClientBuilder = null)
128+
{
129+
ArgumentNullException.ThrowIfNull(builder);
130+
ArgumentException.ThrowIfNullOrEmpty(connectionName);
131+
132+
new AzureKeyVaultKeysComponent()
133+
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName, serviceKey: null);
134+
}
135+
136+
/// <summary>
137+
/// Registers <see cref="KeyClient"/> as a singleton for given <paramref name="name"/> in the services provided by the <paramref name="builder"/>.
138+
/// </summary>
139+
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
140+
/// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection information from the ConnectionStrings configuration section.</param>
141+
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureSecurityKeyVaultSettings"/>. It's invoked after the settings are read from the configuration.</param>
142+
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{TClient, TOptions}"/>.</param>
143+
/// <remarks>Reads the configuration from "Aspire:Azure:Security:KeyVault:{name}" section.</remarks>
144+
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="AzureSecurityKeyVaultSettings.VaultUri"/> is not provided.</exception>
145+
public static void AddKeyedAzureKeyVaultKeyClient(
146+
this IHostApplicationBuilder builder,
147+
string name,
148+
Action<AzureSecurityKeyVaultSettings>? configureSettings = null,
149+
Action<IAzureClientBuilder<KeyClient, KeyClientOptions>>? configureClientBuilder = null)
150+
{
151+
ArgumentNullException.ThrowIfNull(builder);
152+
ArgumentException.ThrowIfNullOrEmpty(name);
153+
154+
new AzureKeyVaultKeysComponent()
155+
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName: name, serviceKey: name);
68156
}
69157

70158
/// <summary>
@@ -118,49 +206,4 @@ private static SecretClient GetSecretClient(
118206

119207
return new SecretClient(settings.VaultUri, settings.Credential ?? new DefaultAzureCredential(), clientOptions);
120208
}
121-
122-
private sealed class KeyVaultComponent : AzureComponent<AzureSecurityKeyVaultSettings, SecretClient, SecretClientOptions>
123-
{
124-
protected override IAzureClientBuilder<SecretClient, SecretClientOptions> AddClient(
125-
AzureClientFactoryBuilder azureFactoryBuilder, AzureSecurityKeyVaultSettings settings,
126-
string connectionName, string configurationSectionName)
127-
{
128-
return azureFactoryBuilder.AddClient<SecretClient, SecretClientOptions>((options, cred, _) =>
129-
{
130-
if (settings.VaultUri is null)
131-
{
132-
throw new InvalidOperationException($"VaultUri is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'VaultUri' key in the '{configurationSectionName}' configuration section.");
133-
}
134-
135-
return new SecretClient(settings.VaultUri, cred, options);
136-
});
137-
}
138-
139-
protected override IHealthCheck CreateHealthCheck(SecretClient client, AzureSecurityKeyVaultSettings settings)
140-
=> new AzureKeyVaultSecretsHealthCheck(client, new AzureKeyVaultSecretsHealthCheckOptions());
141-
142-
protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<SecretClient, SecretClientOptions> clientBuilder, IConfiguration configuration)
143-
{
144-
#pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works
145-
clientBuilder.ConfigureOptions(options => configuration.Bind(options));
146-
#pragma warning restore IDE0200
147-
}
148-
149-
protected override void BindSettingsToConfiguration(AzureSecurityKeyVaultSettings settings, IConfiguration configuration)
150-
{
151-
configuration.Bind(settings);
152-
}
153-
154-
protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
155-
=> !settings.DisableHealthChecks;
156-
157-
protected override TokenCredential? GetTokenCredential(AzureSecurityKeyVaultSettings settings)
158-
=> settings.Credential;
159-
160-
protected override bool GetMetricsEnabled(AzureSecurityKeyVaultSettings settings)
161-
=> false;
162-
163-
protected override bool GetTracingEnabled(AzureSecurityKeyVaultSettings settings)
164-
=> !settings.DisableTracing;
165-
}
166209
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Azure.Common;
5+
using Aspire.Azure.Security.KeyVault;
6+
using Azure.Core;
7+
using Azure.Core.Extensions;
8+
using Azure.Security.KeyVault.Certificates;
9+
using Microsoft.Extensions.Azure;
10+
using Microsoft.Extensions.Configuration;
11+
using Microsoft.Extensions.Diagnostics.HealthChecks;
12+
13+
namespace Microsoft.Extensions.Hosting;
14+
15+
/// <summary>
16+
/// Representation of an <see cref="AzureComponent{TSettings, TClient, TClientOptions}"/> configured as a <see cref="CertificateClient"/>
17+
/// </summary>
18+
internal sealed class AzureKeyVaultCertificatesComponent : AbstractAzureKeyVaultComponent<CertificateClient, CertificateClientOptions>
19+
{
20+
internal override CertificateClient CreateComponentClient(Uri vaultUri, CertificateClientOptions options, TokenCredential cred)
21+
=> new(vaultUri, cred, options);
22+
23+
protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
24+
=> false;
25+
26+
protected override IHealthCheck CreateHealthCheck(CertificateClient client, AzureSecurityKeyVaultSettings settings)
27+
=> throw new NotImplementedException();
28+
29+
protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<CertificateClient, CertificateClientOptions> clientBuilder, IConfiguration configuration)
30+
#pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works
31+
=> clientBuilder.ConfigureOptions(options => configuration.Bind(options));
32+
#pragma warning restore IDE0200 // Remove unnecessary lambda expression
33+
34+
protected override void BindSettingsToConfiguration(AzureSecurityKeyVaultSettings settings, IConfiguration configuration)
35+
=> configuration.Bind(settings);
36+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Azure.Security.KeyVault;
5+
using Azure.Core;
6+
using Azure.Core.Extensions;
7+
using Azure.Security.KeyVault.Keys;
8+
using Microsoft.Extensions.Azure;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.Extensions.Diagnostics.HealthChecks;
11+
12+
namespace Microsoft.Extensions.Hosting;
13+
14+
internal sealed class AzureKeyVaultKeysComponent : AbstractAzureKeyVaultComponent<KeyClient, KeyClientOptions>
15+
{
16+
protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
17+
=> false;
18+
19+
protected override IHealthCheck CreateHealthCheck(KeyClient client, AzureSecurityKeyVaultSettings settings)
20+
=> throw new NotImplementedException();
21+
22+
internal override KeyClient CreateComponentClient(Uri vaultUri, KeyClientOptions options, TokenCredential cred)
23+
=> new(vaultUri, cred, options);
24+
25+
protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<KeyClient, KeyClientOptions> clientBuilder, IConfiguration configuration)
26+
#pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works
27+
=> clientBuilder.ConfigureOptions(options => configuration.Bind(options));
28+
#pragma warning restore IDE0200 // Remove unnecessary lambda expression
29+
30+
protected override void BindSettingsToConfiguration(AzureSecurityKeyVaultSettings settings, IConfiguration configuration)
31+
=> configuration.Bind(settings);
32+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Azure.Security.KeyVault;
5+
using Azure.Core;
6+
using Azure.Core.Extensions;
7+
using Azure.Security.KeyVault.Secrets;
8+
using HealthChecks.Azure.KeyVault.Secrets;
9+
using Microsoft.Extensions.Azure;
10+
using Microsoft.Extensions.Configuration;
11+
using Microsoft.Extensions.Diagnostics.HealthChecks;
12+
13+
namespace Microsoft.Extensions.Hosting;
14+
15+
internal sealed class AzureKeyVaultSecretsComponent : AbstractAzureKeyVaultComponent<SecretClient, SecretClientOptions>
16+
{
17+
protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<SecretClient, SecretClientOptions> clientBuilder, IConfiguration configuration)
18+
#pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works
19+
=> clientBuilder.ConfigureOptions(options => configuration.Bind(options));
20+
#pragma warning restore IDE0200 // Remove unnecessary lambda expression
21+
22+
protected override void BindSettingsToConfiguration(AzureSecurityKeyVaultSettings settings, IConfiguration configuration)
23+
=> configuration.Bind(settings);
24+
25+
protected override IHealthCheck CreateHealthCheck(SecretClient client, AzureSecurityKeyVaultSettings settings)
26+
=> new AzureKeyVaultSecretsHealthCheck(client, new AzureKeyVaultSecretsHealthCheckOptions());
27+
28+
internal override SecretClient CreateComponentClient(Uri vaultUri, SecretClientOptions options, TokenCredential cred)
29+
=> new(vaultUri, cred, options);
30+
}

0 commit comments

Comments
 (0)