Skip to content

Commit e516d82

Browse files
authored
Add CORS allowed origins config override to app host (#6250)
1 parent 96ede03 commit e516d82

File tree

5 files changed

+68
-38
lines changed

5 files changed

+68
-38
lines changed

playground/BrowserTelemetry/BrowserTelemetry.AppHost/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
var builder = DistributedApplication.CreateBuilder(args);
55

66
builder.AddProject<Projects.BrowserTelemetry_Web>("web")
7-
.WithExternalHttpEndpoints();
7+
.WithExternalHttpEndpoints()
8+
.WithReplicas(2);
89

910
#if !SKIP_DASHBOARD_REFERENCE
1011
// This project is only added in playground projects to support development/debugging

playground/BrowserTelemetry/BrowserTelemetry.AppHost/Properties/launchSettings.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"DOTNET_ENVIRONMENT": "Development",
1212
"DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "https://localhost:16175",
1313
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:17037",
14-
"DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES": "true"
14+
"DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES": "true",
15+
"DOTNET_DASHBOARD_CORS_ALLOWED_ORIGINS": "*"
1516
}
1617
},
1718
"http": {
@@ -25,7 +26,8 @@
2526
"DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "http://localhost:16175",
2627
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:17037",
2728
"DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES": "true",
28-
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
29+
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true",
30+
"DOTNET_DASHBOARD_CORS_ALLOWED_ORIGINS": "*"
2931
}
3032
},
3133
"generate-manifest": {

src/Aspire.Hosting/Dashboard/DashboardLifecycleHook.cs

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -178,38 +178,19 @@ private void ConfigureAspireDashboardResource(IResource dashboardResource)
178178
{
179179
context.EnvironmentVariables[DashboardConfigNames.DashboardOtlpHttpUrlName.EnvVarName] = otlpHttpEndpointUrl;
180180

181-
var model = context.ExecutionContext.ServiceProvider.GetRequiredService<DistributedApplicationModel>();
182-
var allResourceEndpoints = model.Resources
183-
.Where(r => !string.Equals(r.Name, KnownResourceNames.AspireDashboard, StringComparisons.ResourceName))
184-
.SelectMany(r => r.Annotations)
185-
.OfType<EndpointAnnotation>()
186-
.ToList();
187-
188-
var corsOrigins = new HashSet<string>(StringComparers.UrlHost);
189-
foreach (var endpoint in allResourceEndpoints)
190-
{
191-
if (endpoint.UriScheme is "http" or "https")
192-
{
193-
// Prefer allocated endpoint over EndpointAnnotation.Port.
194-
var origin = endpoint.AllocatedEndpoint?.UriString;
195-
var targetOrigin = (endpoint.TargetPort != null)
196-
? $"{endpoint.UriScheme}://localhost:{endpoint.TargetPort}"
197-
: null;
181+
// Use explicitly defined allowed origins if configured.
182+
var allowedOrigins = configuration[KnownConfigNames.DashboardCorsAllowedOrigins];
198183

199-
if (origin != null)
200-
{
201-
corsOrigins.Add(origin);
202-
}
203-
if (targetOrigin != null)
204-
{
205-
corsOrigins.Add(targetOrigin);
206-
}
207-
}
184+
// If allowed origins are not configured then calculate allowed origins from endpoints.
185+
if (string.IsNullOrEmpty(allowedOrigins))
186+
{
187+
var model = context.ExecutionContext.ServiceProvider.GetRequiredService<DistributedApplicationModel>();
188+
allowedOrigins = GetAllowedOriginsFromResourceEndpoints(model);
208189
}
209190

210-
if (corsOrigins.Count > 0)
191+
if (!string.IsNullOrEmpty(allowedOrigins))
211192
{
212-
context.EnvironmentVariables[DashboardConfigNames.DashboardOtlpCorsAllowedOriginsKeyName.EnvVarName] = string.Join(',', corsOrigins);
193+
context.EnvironmentVariables[DashboardConfigNames.DashboardOtlpCorsAllowedOriginsKeyName.EnvVarName] = allowedOrigins;
213194
context.EnvironmentVariables[DashboardConfigNames.DashboardOtlpCorsAllowedHeadersKeyName.EnvVarName] = "*";
214195
}
215196
}
@@ -266,6 +247,44 @@ private void ConfigureAspireDashboardResource(IResource dashboardResource)
266247
}));
267248
}
268249

250+
private static string? GetAllowedOriginsFromResourceEndpoints(DistributedApplicationModel model)
251+
{
252+
var allResourceEndpoints = model.Resources
253+
.Where(r => !string.Equals(r.Name, KnownResourceNames.AspireDashboard, StringComparisons.ResourceName))
254+
.SelectMany(r => r.Annotations)
255+
.OfType<EndpointAnnotation>()
256+
.ToList();
257+
258+
var corsOrigins = new HashSet<string>(StringComparers.UrlHost);
259+
foreach (var endpoint in allResourceEndpoints)
260+
{
261+
if (endpoint.UriScheme is "http" or "https")
262+
{
263+
// Prefer allocated endpoint over EndpointAnnotation.Port.
264+
var origin = endpoint.AllocatedEndpoint?.UriString;
265+
var targetOrigin = (endpoint.TargetPort != null)
266+
? $"{endpoint.UriScheme}://localhost:{endpoint.TargetPort}"
267+
: null;
268+
269+
if (origin != null)
270+
{
271+
corsOrigins.Add(origin);
272+
}
273+
if (targetOrigin != null)
274+
{
275+
corsOrigins.Add(targetOrigin);
276+
}
277+
}
278+
}
279+
280+
if (corsOrigins.Count > 0)
281+
{
282+
return string.Join(',', corsOrigins);
283+
}
284+
285+
return null;
286+
}
287+
269288
private async Task WatchDashboardLogsAsync(CancellationToken cancellationToken)
270289
{
271290
var loggerCache = new ConcurrentDictionary<string, ILogger>(StringComparer.Ordinal);

src/Shared/KnownConfigNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ internal static class KnownConfigNames
1212
public const string DashboardFrontendBrowserToken = "DOTNET_DASHBOARD_FRONTEND_BROWSERTOKEN";
1313
public const string DashboardResourceServiceClientApiKey = "DOTNET_DASHBOARD_RESOURCESERVICE_APIKEY";
1414
public const string DashboardUnsecuredAllowAnonymous = "DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS";
15+
public const string DashboardCorsAllowedOrigins = "DOTNET_DASHBOARD_CORS_ALLOWED_ORIGINS";
1516
public const string ResourceServiceEndpointUrl = "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL";
1617
}

tests/Aspire.Hosting.Tests/Dashboard/DashboardResourceTests.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,10 @@ public async Task DashboardResourceServiceUriIsSet()
280280
Assert.Equal("http://localhost:5000", config.Single(e => e.Key == DashboardConfigNames.ResourceServiceUrlName.EnvVarName).Value);
281281
}
282282

283-
[Fact]
284-
public async Task DashboardResource_OtlpHttpEndpoint_CorsEnvVarSet()
283+
[Theory]
284+
[InlineData("*")]
285+
[InlineData(null)]
286+
public async Task DashboardResource_OtlpHttpEndpoint_CorsEnvVarSet(string? explicitCorsAllowedOrigins)
285287
{
286288
// Arrange
287289
using var builder = TestDistributedApplicationBuilder.Create(
@@ -297,7 +299,8 @@ public async Task DashboardResource_OtlpHttpEndpoint_CorsEnvVarSet()
297299
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
298300
{
299301
["ASPNETCORE_URLS"] = "http://localhost",
300-
["DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL"] = "http://localhost"
302+
["DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL"] = "http://localhost",
303+
["DOTNET_DASHBOARD_CORS_ALLOWED_ORIGINS"] = explicitCorsAllowedOrigins
301304
});
302305

303306
using var app = builder.Build();
@@ -315,12 +318,15 @@ public async Task DashboardResource_OtlpHttpEndpoint_CorsEnvVarSet()
315318

316319
var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dashboard, DistributedApplicationOperation.Run, app.Services).DefaultTimeout();
317320

318-
Assert.Equal("http://localhost:8081,http://localhost:58080", config.Single(e => e.Key == DashboardConfigNames.DashboardOtlpCorsAllowedOriginsKeyName.EnvVarName).Value);
321+
var expectedAllowedOrigins = !string.IsNullOrEmpty(explicitCorsAllowedOrigins) ? explicitCorsAllowedOrigins : "http://localhost:8081,http://localhost:58080";
322+
Assert.Equal(expectedAllowedOrigins, config.Single(e => e.Key == DashboardConfigNames.DashboardOtlpCorsAllowedOriginsKeyName.EnvVarName).Value);
319323
Assert.Equal("*", config.Single(e => e.Key == DashboardConfigNames.DashboardOtlpCorsAllowedHeadersKeyName.EnvVarName).Value);
320324
}
321325

322-
[Fact]
323-
public async Task DashboardResource_OtlpGrpcEndpoint_CorsEnvVarNotSet()
326+
[Theory]
327+
[InlineData("*")]
328+
[InlineData(null)]
329+
public async Task DashboardResource_OtlpGrpcEndpoint_CorsEnvVarNotSet(string? explicitCorsAllowedOrigins)
324330
{
325331
// Arrange
326332
using var builder = TestDistributedApplicationBuilder.Create(
@@ -336,7 +342,8 @@ public async Task DashboardResource_OtlpGrpcEndpoint_CorsEnvVarNotSet()
336342
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
337343
{
338344
["ASPNETCORE_URLS"] = "http://localhost",
339-
["DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"] = "http://localhost"
345+
["DOTNET_DASHBOARD_OTLP_ENDPOINT_URL"] = "http://localhost",
346+
["DOTNET_DASHBOARD_CORS_ALLOWED_ORIGINS"] = explicitCorsAllowedOrigins
340347
});
341348

342349
using var app = builder.Build();

0 commit comments

Comments
 (0)