Skip to content

Commit 7dee91c

Browse files
davidfowleerhardt
andauthored
Cosmos, Redis and Postgres show keyvault when using key access or passwords with emulator (#8406)
* Follow up keyvault changes - Pass the IKeyVaultSecretReference to the SecretResolver - Don't add the default keyvault when using the emulator. * Add the KeyVault resource, but remove it from the model in BeforeStart if the Azure resource is emulated or container in run mode. * Respond to PR feedback Fix #8364 --------- Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
1 parent 9d15bb0 commit 7dee91c

File tree

12 files changed

+124
-14
lines changed

12 files changed

+124
-14
lines changed

src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,20 @@ public static IResourceBuilder<AzureCosmosDBResource> WithAccessKeyAuthenticatio
342342
var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv")
343343
.WithParentRelationship(builder.Resource);
344344

345+
// remove the KeyVault from the model if the emulator is used during run mode.
346+
// need to do this later in case builder becomes an emulator after this method is called.
347+
if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
348+
{
349+
builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((data, _) =>
350+
{
351+
if (builder.Resource.IsEmulator)
352+
{
353+
data.Model.Resources.Remove(kv.Resource);
354+
}
355+
return Task.CompletedTask;
356+
});
357+
}
358+
345359
return builder.WithAccessKeyAuthentication(kv);
346360
}
347361

src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBResource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ internal ReferenceExpression GetChildConnectionString(string childResourceName,
134134

135135
var builder = new ReferenceExpressionBuilder();
136136

137-
if (UseAccessKeyAuthentication)
137+
if (UseAccessKeyAuthentication && !IsEmulator)
138138
{
139139
builder.AppendFormatted(ConnectionStringSecretOutput.Resource.GetSecretReference(GetKeyValueSecretName(childResourceName)));
140140
}

src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultResource.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ public class AzureKeyVaultResource(string name, Action<AzureResourceInfrastructu
3434
BicepOutputReference IKeyVaultResource.VaultUriOutputReference => VaultUri;
3535

3636
// In run mode, this is set to the secret client used to access the Azure Key Vault.
37-
internal Func<string, CancellationToken, Task<string?>>? SecretResolver { get; set; }
37+
internal Func<IKeyVaultSecretReference, CancellationToken, Task<string?>>? SecretResolver { get; set; }
3838

39-
Func<string, CancellationToken, Task<string?>>? IKeyVaultResource.SecretResolver
39+
Func<IKeyVaultSecretReference, CancellationToken, Task<string?>>? IKeyVaultResource.SecretResolver
4040
{
4141
get => SecretResolver;
4242
set => SecretResolver = value;

src/Aspire.Hosting.Azure.KeyVault/AzureKeyVaultSecretReference.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal sealed class AzureKeyVaultSecretReference(string secretName, AzureKeyVa
2828
{
2929
if (azureKeyVaultResource.SecretResolver is { } secretResolver)
3030
{
31-
return await secretResolver(secretName, cancellationToken).ConfigureAwait(false);
31+
return await secretResolver(this, cancellationToken).ConfigureAwait(false);
3232
}
3333

3434
throw new InvalidOperationException($"Secret '{secretName}' not found in Key Vault '{azureKeyVaultResource.Name}'.");

src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,20 @@ public static IResourceBuilder<AzurePostgresFlexibleServerResource> WithPassword
289289
var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv")
290290
.WithParentRelationship(builder.Resource);
291291

292+
// remove the KeyVault from the model if the emulator is used during run mode.
293+
// need to do this later in case builder becomes an emulator after this method is called.
294+
if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
295+
{
296+
builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((data, token) =>
297+
{
298+
if (builder.Resource.IsContainer())
299+
{
300+
data.Model.Resources.Remove(kv.Resource);
301+
}
302+
return Task.CompletedTask;
303+
});
304+
}
305+
292306
return builder.WithPasswordAuthentication(kv, userName, password);
293307
}
294308

src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,20 @@ public static IResourceBuilder<AzureRedisCacheResource> WithAccessKeyAuthenticat
195195
var kv = builder.ApplicationBuilder.AddAzureKeyVault($"{builder.Resource.Name}-kv")
196196
.WithParentRelationship(builder.Resource);
197197

198+
// remove the KeyVault from the model if the emulator is used during run mode.
199+
// need to do this later in case builder becomes an emulator after this method is called.
200+
if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
201+
{
202+
builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((data, token) =>
203+
{
204+
if (builder.Resource.IsContainer())
205+
{
206+
data.Model.Resources.Remove(kv.Resource);
207+
}
208+
return Task.CompletedTask;
209+
});
210+
}
211+
198212
return builder.WithAccessKeyAuthentication(kv);
199213
}
200214

src/Aspire.Hosting.Azure/IKeyVaultResource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public interface IKeyVaultResource : IResource, IAzureResource
2323
/// <summary>
2424
/// Gets or sets the secret resolver function used to resolve secrets at runtime.
2525
/// </summary>
26-
Func<string, CancellationToken, Task<string?>>? SecretResolver { get; set; }
26+
Func<IKeyVaultSecretReference, CancellationToken, Task<string?>>? SecretResolver { get; set; }
2727

2828
/// <summary>
2929
/// Gets a secret reference for the specified secret name.

src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,9 @@ await notificationService.PublishUpdateAsync(resource, state =>
284284

285285
// Set the client for resolving secrets at runtime
286286
var client = new SecretClient(new(vaultUri), context.Credential);
287-
kvr.SecretResolver = async (secretName, ct) =>
287+
kvr.SecretResolver = async (secretRef, ct) =>
288288
{
289-
var secret = await client.GetSecretAsync(secretName, cancellationToken: ct).ConfigureAwait(false);
289+
var secret = await client.GetSecretAsync(secretRef.SecretName, cancellationToken: ct).ConfigureAwait(false);
290290
return secret.Value.Value;
291291
};
292292
}

tests/Aspire.Hosting.Azure.Tests/AzureBicepResourceTests.cs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Net.Sockets;
5+
using System.Runtime.CompilerServices;
56
using System.Text.Json.Nodes;
67
using Aspire.Hosting.ApplicationModel;
78
using Aspire.Hosting.Lifecycle;
@@ -237,6 +238,24 @@ public async Task AddAzureCosmosDBEmulator()
237238
Assert.Equal(cs, await ((IResourceWithConnectionString)cosmos.Resource).GetConnectionStringAsync());
238239
}
239240

241+
[Fact]
242+
public async Task AddAzureCosmosDB_WithAccessKeyAuthentication_NoKeyVaultWithEmulator()
243+
{
244+
using var builder = TestDistributedApplicationBuilder.Create();
245+
246+
builder.AddAzureCosmosDB("cosmos").WithAccessKeyAuthentication().RunAsEmulator();
247+
248+
#pragma warning disable ASPIRECOSMOSDB001
249+
builder.AddAzureCosmosDB("cosmos2").WithAccessKeyAuthentication().RunAsPreviewEmulator();
250+
#pragma warning restore ASPIRECOSMOSDB001
251+
252+
var app = builder.Build();
253+
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
254+
await ExecuteBeforeStartHooksAsync(app, CancellationToken.None);
255+
256+
Assert.Empty(model.Resources.OfType<AzureKeyVaultResource>());
257+
}
258+
240259
[Theory]
241260
[InlineData(null)]
242261
[InlineData("mykeyvault")]
@@ -264,16 +283,24 @@ public async Task AddAzureCosmosDBViaRunMode_WithAccessKeyAuthentication(string?
264283
var db = cosmos.AddCosmosDatabase("db", databaseName: "mydatabase");
265284
db.AddContainer("container", "mypartitionkeypath", containerName: "mycontainer");
266285

267-
var kv = builder.CreateResourceBuilder<AzureKeyVaultResource>(kvName);
286+
var app = builder.Build();
287+
288+
await ExecuteBeforeStartHooksAsync(app, CancellationToken.None);
289+
290+
var model = app.Services.GetRequiredService<DistributedApplicationModel>();
291+
292+
var kv = model.Resources.OfType<AzureKeyVaultResource>().Single();
293+
294+
Assert.Equal(kvName, kv.Name);
268295

269296
var secrets = new Dictionary<string, string>
270297
{
271298
["connectionstrings--cosmos"] = "mycosmosconnectionstring"
272299
};
273300

274-
kv.Resource.SecretResolver = (name, _) =>
301+
kv.SecretResolver = (secretRef, _) =>
275302
{
276-
if (!secrets.TryGetValue(name, out var value))
303+
if (!secrets.TryGetValue(secretRef.SecretName, out var value))
277304
{
278305
return Task.FromResult<string?>(null);
279306
}
@@ -533,9 +560,9 @@ public async Task AddAzureCosmosDBViaPublishMode_WithAccessKeyAuthentication(str
533560
["connectionstrings--cosmos"] = "mycosmosconnectionstring"
534561
};
535562

536-
kv.Resource.SecretResolver = (name, _) =>
563+
kv.Resource.SecretResolver = (secretRef, _) =>
537564
{
538-
if (!secrets.TryGetValue(name, out var value))
565+
if (!secrets.TryGetValue(secretRef.SecretName, out var value))
539566
{
540567
return Task.FromResult<string?>(null);
541568
}
@@ -3130,6 +3157,9 @@ public async Task InfrastructureCanBeMutatedAfterCreation()
31303157
Assert.Equal(expectedBicep, bicep);
31313158
}
31323159

3160+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ExecuteBeforeStartHooksAsync")]
3161+
private static extern Task ExecuteBeforeStartHooksAsync(DistributedApplication app, CancellationToken cancellationToken);
3162+
31333163
private sealed class ProjectA : IProjectMetadata
31343164
{
31353165
public string ProjectPath => "projectA";

tests/Aspire.Hosting.Azure.Tests/AzureCosmosDBExtensionsTests.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,26 @@ public void AzureCosmosDBHasCorrectConnectionStrings_ForAccountEndpoint()
106106
Assert.Equal("AccountEndpoint={cosmos.outputs.connectionString};Database=db1;Container=container1", container1.Resource.ConnectionStringExpression.ValueExpression);
107107
}
108108

109-
[Fact]
110-
public void AzureCosmosDBHasCorrectConnectionStrings()
109+
[Theory]
110+
[InlineData(true)]
111+
[InlineData(false)]
112+
public void AzureCosmosDBHasCorrectConnectionStrings(bool useAccessKeyAuth)
111113
{
112114
using var builder = TestDistributedApplicationBuilder.Create();
113115

114116
var cosmos = builder.AddAzureCosmosDB("cosmos").RunAsEmulator();
117+
if (useAccessKeyAuth)
118+
{
119+
cosmos.WithAccessKeyAuthentication();
120+
}
115121
var db1 = cosmos.AddCosmosDatabase("db1");
116122
var container1 = db1.AddContainer("container1", "id");
117123

118124
var cosmos1 = builder.AddAzureCosmosDB("cosmos1").RunAsEmulator();
125+
if (useAccessKeyAuth)
126+
{
127+
cosmos1.WithAccessKeyAuthentication();
128+
}
119129
var db2 = cosmos1.AddCosmosDatabase("db2", "db");
120130
var container2 = db2.AddContainer("container2", "id", "container");
121131

0 commit comments

Comments
 (0)