Skip to content

Commit 3dc36e6

Browse files
committed
Ensure containers EH and SB files can be used from non-root containers
1 parent c829499 commit 3dc36e6

File tree

5 files changed

+83
-2
lines changed

5 files changed

+83
-2
lines changed

src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,15 @@ public static IResourceBuilder<AzureEventHubsResource> RunAsEmulator(this IResou
322322
// Deterministic file path for the configuration file based on its content
323323
var configJsonPath = aspireStore.GetFileNameWithContent($"{builder.Resource.Name}-Config.json", tempConfigFile);
324324

325+
// The docker container runs as a non-root user, so we need to grant other user's read/write permission
326+
if (!OperatingSystem.IsWindows())
327+
{
328+
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
329+
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
330+
331+
File.SetUnixFileMode(configJsonPath, mode);
332+
}
333+
325334
builder.WithAnnotation(new ContainerMountAnnotation(
326335
configJsonPath,
327336
AzureEventHubsEmulatorResource.EmulatorConfigJsonPath,

src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,15 @@ public static IResourceBuilder<AzureServiceBusResource> RunAsEmulator(this IReso
423423
// Deterministic file path for the configuration file based on its content
424424
var configJsonPath = aspireStore.GetFileNameWithContent($"{builder.Resource.Name}-Config.json", tempConfigFile);
425425

426+
// The docker container runs as a non-root user, so we need to grant other user's read/write permission
427+
if (!OperatingSystem.IsWindows())
428+
{
429+
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
430+
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
431+
432+
File.SetUnixFileMode(configJsonPath, mode);
433+
}
434+
426435
builder.WithAnnotation(new ContainerMountAnnotation(
427436
configJsonPath,
428437
AzureServiceBusEmulatorResource.EmulatorConfigJsonPath,

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,41 @@ public async Task VerifyAzureEventHubsEmulatorResource(bool referenceHub)
110110
}
111111
}
112112

113+
[Fact]
114+
[RequiresDocker]
115+
public async Task AzureEventHubsNs_ProducesAndConsumes()
116+
{
117+
using var builder = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper);
118+
var eventHubns = builder.AddAzureEventHubs("eventhubns")
119+
.RunAsEmulator();
120+
var eventHub = eventHubns.AddHub("hub");
121+
122+
using var app = builder.Build();
123+
await app.StartAsync();
124+
125+
var hb = Host.CreateApplicationBuilder();
126+
127+
hb.Configuration["ConnectionStrings:eventhubns"] = await eventHubns.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
128+
hb.AddAzureEventHubProducerClient("eventhubns", settings => settings.EventHubName = "hub");
129+
hb.AddAzureEventHubConsumerClient("eventhubns", settings => settings.EventHubName = "hub");
130+
131+
using var host = hb.Build();
132+
await host.StartAsync();
133+
134+
var producerClient = host.Services.GetRequiredService<EventHubProducerClient>();
135+
var consumerClient = host.Services.GetRequiredService<EventHubConsumerClient>();
136+
137+
// If no exception is thrown when awaited, the Event Hubs service has acknowledged
138+
// receipt and assumed responsibility for delivery of the set of events to its partition.
139+
await producerClient.SendAsync([new EventData(Encoding.UTF8.GetBytes("hello worlds"))]);
140+
141+
await foreach (var partitionEvent in consumerClient.ReadEventsAsync(new ReadEventOptions { MaximumWaitTime = TimeSpan.FromSeconds(5) }))
142+
{
143+
Assert.Equal("hello worlds", Encoding.UTF8.GetString(partitionEvent.Data.EventBody.ToArray()));
144+
break;
145+
}
146+
}
147+
113148
[Fact]
114149
public void AzureEventHubsUseEmulatorCallbackWithWithDataBindMountResultsInBindMountAnnotationWithDefaultPath()
115150
{
@@ -393,6 +428,18 @@ public async Task AzureEventHubsEmulatorResourceGeneratesConfigJsonWithCustomiza
393428

394429
var configJsonContent = File.ReadAllText(volumeAnnotation.Source!);
395430

431+
if (!OperatingSystem.IsWindows())
432+
{
433+
// Ensure the configuration file has correct attributes
434+
var fileInfo = new FileInfo(volumeAnnotation.Source!);
435+
436+
var expectedUnixFileMode =
437+
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
438+
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
439+
440+
Assert.True(fileInfo.UnixFileMode.HasFlag(expectedUnixFileMode));
441+
}
442+
396443
Assert.Equal(/*json*/"""
397444
{
398445
"UserConfig": {

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ public async Task VerifyWaitForOnServiceBusEmulatorBlocksDependentResources()
215215
await app.StopAsync();
216216
}
217217

218-
[Fact(Skip = "Azure ServiceBus emulator is not reliable in CI - https://github.com/dotnet/aspire/issues/7066")]
218+
[Fact]
219219
[RequiresDocker]
220220
public async Task VerifyAzureServiceBusEmulatorResource()
221221
{
@@ -470,6 +470,18 @@ public async Task AzureServiceBusEmulatorResourceGeneratesConfigJson()
470470
var serviceBusEmulatorResource = builder.Resources.OfType<AzureServiceBusResource>().Single(x => x is { } serviceBusResource && serviceBusResource.IsEmulator);
471471
var volumeAnnotation = serviceBusEmulatorResource.Annotations.OfType<ContainerMountAnnotation>().Single();
472472

473+
if (!OperatingSystem.IsWindows())
474+
{
475+
// Ensure the configuration file has correct attributes
476+
var fileInfo = new FileInfo(volumeAnnotation.Source!);
477+
478+
var expectedUnixFileMode =
479+
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
480+
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
481+
482+
Assert.True(fileInfo.UnixFileMode.HasFlag(expectedUnixFileMode));
483+
}
484+
473485
var configJsonContent = File.ReadAllText(volumeAnnotation.Source!);
474486

475487
Assert.Equal(/*json*/"""

tests/Shared/DistributedApplicationTestingBuilderExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ public static IDistributedApplicationTestingBuilder WithTestAndResourceLogging(t
2323
builder.Services.AddLogging(builder => builder.AddFilter("Aspire.Hosting", LogLevel.Trace));
2424
return builder;
2525
}
26+
2627
public static IDistributedApplicationTestingBuilder WithTempAspireStore(this IDistributedApplicationTestingBuilder builder)
2728
{
28-
builder.Configuration["Aspire:Store:Path"] = Path.GetTempPath();
29+
// We create the Aspire Store in a folder with user-only access. This way non-root containers won't be able
30+
// to access the files unless they correctly assign the required permissions for the container to work.
31+
32+
builder.Configuration["Aspire:Store:Path"] = Directory.CreateTempSubdirectory().FullName;
2933
return builder;
3034
}
3135
}

0 commit comments

Comments
 (0)