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

[release/9.1] Ensure EH and SB emulator files can be used from non-root containers #7765

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,15 @@ public static IResourceBuilder<AzureEventHubsResource> RunAsEmulator(this IResou
// Deterministic file path for the configuration file based on its content
var configJsonPath = aspireStore.GetFileNameWithContent($"{builder.Resource.Name}-Config.json", tempConfigFile);

// The docker container runs as a non-root user, so we need to grant other user's read/write permission
if (!OperatingSystem.IsWindows())
{
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need UnixFileMode.OtherWrite | UnixFileMode.OtherExecute? This should only need Read permission.

Comment on lines +328 to +329
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite |
UnixFileMode.GroupRead |
UnixFileMode.OtherRead;

The file doesn't need to be executed by anyone. And it shouldn't be written by anyone else. We should also add GroupRead to make it coherent.


File.SetUnixFileMode(configJsonPath, mode);
}

builder.WithAnnotation(new ContainerMountAnnotation(
configJsonPath,
AzureEventHubsEmulatorResource.EmulatorConfigJsonPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,15 @@ public static IResourceBuilder<AzureServiceBusResource> RunAsEmulator(this IReso
// Deterministic file path for the configuration file based on its content
var configJsonPath = aspireStore.GetFileNameWithContent($"{builder.Resource.Name}-Config.json", tempConfigFile);

// The docker container runs as a non-root user, so we need to grant other user's read/write permission
if (!OperatingSystem.IsWindows())
{
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;

File.SetUnixFileMode(configJsonPath, mode);
}

builder.WithAnnotation(new ContainerMountAnnotation(
configJsonPath,
AzureServiceBusEmulatorResource.EmulatorConfigJsonPath,
Expand Down
52 changes: 52 additions & 0 deletions tests/Aspire.Hosting.Azure.Tests/AzureEventHubsExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,46 @@ public async Task VerifyAzureEventHubsEmulatorResource(bool referenceHub)
}
}

[Fact]
[RequiresDocker]
public async Task AzureEventHubsNs_ProducesAndConsumes()
{
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10));

using var builder = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(testOutputHelper);
var eventHubns = builder.AddAzureEventHubs("eventhubns")
.RunAsEmulator();
var eventHub = eventHubns.AddHub("hub");

using var app = builder.Build();
await app.StartAsync();

var hb = Host.CreateApplicationBuilder();

hb.Configuration["ConnectionStrings:eventhubns"] = await eventHubns.Resource.ConnectionStringExpression.GetValueAsync(CancellationToken.None);
hb.AddAzureEventHubProducerClient("eventhubns", settings => settings.EventHubName = "hub");
hb.AddAzureEventHubConsumerClient("eventhubns", settings => settings.EventHubName = "hub");

using var host = hb.Build();
await host.StartAsync();

var rns = app.Services.GetRequiredService<ResourceNotificationService>();
await rns.WaitForResourceHealthyAsync(eventHubns.Resource.Name, cts.Token);

var producerClient = host.Services.GetRequiredService<EventHubProducerClient>();
var consumerClient = host.Services.GetRequiredService<EventHubConsumerClient>();

// If no exception is thrown when awaited, the Event Hubs service has acknowledged
// receipt and assumed responsibility for delivery of the set of events to its partition.
await producerClient.SendAsync([new EventData(Encoding.UTF8.GetBytes("hello worlds"))], cts.Token);

await foreach (var partitionEvent in consumerClient.ReadEventsAsync(new ReadEventOptions { MaximumWaitTime = TimeSpan.FromSeconds(5) }))
{
Assert.Equal("hello worlds", Encoding.UTF8.GetString(partitionEvent.Data.EventBody.ToArray()));
break;
}
}

[Fact]
public void AzureEventHubsUseEmulatorCallbackWithWithDataBindMountResultsInBindMountAnnotationWithDefaultPath()
{
Expand Down Expand Up @@ -393,6 +433,18 @@ public async Task AzureEventHubsEmulatorResourceGeneratesConfigJsonWithCustomiza

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

if (!OperatingSystem.IsWindows())
{
// Ensure the configuration file has correct attributes
var fileInfo = new FileInfo(volumeAnnotation.Source!);

var expectedUnixFileMode =
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;

Assert.True(fileInfo.UnixFileMode.HasFlag(expectedUnixFileMode));
}

Assert.Equal(/*json*/"""
{
"UserConfig": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public async Task VerifyWaitForOnServiceBusEmulatorBlocksDependentResources()
await app.StopAsync();
}

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

if (!OperatingSystem.IsWindows())
{
// Ensure the configuration file has correct attributes
var fileInfo = new FileInfo(volumeAnnotation.Source!);

var expectedUnixFileMode =
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;

Assert.True(fileInfo.UnixFileMode.HasFlag(expectedUnixFileMode));
}

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

Assert.Equal(/*json*/"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ public static IDistributedApplicationTestingBuilder WithTestAndResourceLogging(t
builder.Services.AddLogging(builder => builder.AddFilter("Aspire.Hosting", LogLevel.Trace));
return builder;
}

public static IDistributedApplicationTestingBuilder WithTempAspireStore(this IDistributedApplicationTestingBuilder builder)
{
builder.Configuration["Aspire:Store:Path"] = Path.GetTempPath();
// We create the Aspire Store in a folder with user-only access. This way non-root containers won't be able
// to access the files unless they correctly assign the required permissions for the container to work.

builder.Configuration["Aspire:Store:Path"] = Directory.CreateTempSubdirectory().FullName;
return builder;
}
}
Loading