Skip to content

Commit 091a71d

Browse files
keegan-carusoKeegan Caruso
andauthored
Restrict hosts (#3579)
Implemented host filtering to restrict connections to `localhost` outside of development environments. Updated `Program.cs` to include `AddHostFiltering` and `UseHostFiltering`. Removed `AllowedHosts` from `appsettings.json` as it is now hardcoded. Updated `README.md` to clarify this behavior. Co-authored-by: Keegan Caruso <keegancaruso@microsoft.com>
1 parent fcf864e commit 091a71d

File tree

4 files changed

+81
-9
lines changed

4 files changed

+81
-9
lines changed

src/Microsoft.Identity.Web.Sidecar/Program.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,24 @@ public static void Main(string[] args)
3232
options.AllowWebApiToBeAuthorizedByACL = true;
3333
});
3434

35+
if (!builder.Environment.IsDevelopment())
36+
{
37+
// When not in a development environment, only allow connecting over
38+
// localhost.
39+
// AddHostFiltering is needed because this is using SlimBuilder which doesn't include
40+
// that middleware by default.
41+
builder.Services.AddHostFiltering(options =>
42+
{
43+
options.AllowedHosts = ["localhost"];
44+
});
45+
}
46+
3547
ConfigureDataProtection(builder);
3648

3749
// Add the agent identities and downstream APIs
3850
builder.Services.AddAgentIdentities()
3951
.AddDownstreamApis(builder.Configuration.GetSection("DownstreamApis"));
4052

41-
// Health checks:
42-
// Tag checks that should participate in readiness with "ready".
4353
builder.Services.AddHealthChecks();
4454

4555
ConfigureAuthN(builder);
@@ -55,13 +65,16 @@ public static void Main(string[] args)
5565

5666
// Single endpoint for both liveness and readiness
5767
// as no checks are performed as part of startup.
58-
// httpGet: path: /health
5968
app.MapHealthChecks("/healthz");
6069

6170
if (app.Environment.IsDevelopment())
6271
{
6372
app.MapOpenApi();
6473
}
74+
else
75+
{
76+
app.UseHostFiltering();
77+
}
6578

6679
app.AddValidateRequestEndpoints();
6780
app.AddAuthorizationHeaderRequestEndpoints();

src/Microsoft.Identity.Web.Sidecar/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Settings are supplied via `appsettings.json`, environment variables, or any stan
4141

4242
`AllowWebApiToBeAuthorizedByACL` will be set to true by the application. No action is required from the user to configure this.
4343

44+
`AllowedHosts` is not used by the sidecar. It will always be `localhost` outside of development environments.
45+
4446
*Important sections*
4547

4648
- **AzureAd**: Standard Microsoft.Identity.Web web API registration; client credentials are optional if only delegated flows are required.

src/Microsoft.Identity.Web.Sidecar/appsettings.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/AzureAD/microsoft-identity-web/refs/heads/master/JsonSchemas/microsoft-identity-web.json",
33
/*
4-
The following identity settings need to be configured
5-
before the project can be successfully executed.
6-
For more info see https://aka.ms/dotnet-template-ms-identity-platform
7-
*/
4+
The following identity settings need to be configured
5+
before the project can be successfully executed.
6+
For more info see https://aka.ms/dotnet-template-ms-identity-platform
7+
*/
88
"AzureAd": {
99
"Instance": "https://login.microsoftonline.com/",
1010
"TenantId": "", // f645ad92-e38d-4d1a-b510-d1b09a74a8ca
@@ -34,8 +34,7 @@ For more info see https://aka.ms/dotnet-template-ms-identity-platform
3434
"Default": "Information",
3535
"Microsoft.AspNetCore": "Warning"
3636
}
37-
},
38-
"AllowedHosts": "*"
37+
}
3938
}
4039

4140

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Net;
5+
using Microsoft.Extensions.Hosting;
6+
using Xunit;
7+
8+
namespace Sidecar.Tests;
9+
10+
public class HostFilteringTests(SidecarApiFactory factory) : IClassFixture<SidecarApiFactory>
11+
{
12+
readonly SidecarApiFactory _factory = factory;
13+
const string EnvironmentKey = "environment"; // WebHostDefaults.EnvironmentKey constant value
14+
15+
[Fact]
16+
public async Task HostFiltering_NotApplied_In_Development()
17+
{
18+
// Development environment (default for factory) -> no host filtering.
19+
var devFactory = _factory.WithWebHostBuilder(b => { /* Development by default */ });
20+
var client = devFactory.CreateClient();
21+
22+
var request = new HttpRequestMessage(HttpMethod.Get, "/healthz")
23+
{
24+
Headers = { Host = "notlocalhost" }
25+
};
26+
var response = await client.SendAsync(request);
27+
28+
Assert.True(response.IsSuccessStatusCode, "Expected success without host filtering in Development environment.");
29+
}
30+
31+
[Fact]
32+
public async Task HostFiltering_Blocks_Disallowed_Host_In_Production()
33+
{
34+
// Force Production by setting environment variable before building client.
35+
var prodFactory = _factory.WithWebHostBuilder(b =>
36+
{
37+
b.UseSetting(EnvironmentKey, Environments.Production);
38+
});
39+
var client = prodFactory.CreateClient();
40+
41+
// Allowed host (localhost) should succeed.
42+
var okResponse = await client.GetAsync("/healthz");
43+
Assert.True(okResponse.IsSuccessStatusCode, "Expected success for localhost host.");
44+
45+
// Disallowed host should be rejected.
46+
var badRequest = new HttpRequestMessage(HttpMethod.Get, "/healthz")
47+
{
48+
Headers = { Host = "notlocalhost" }
49+
};
50+
var badResponse = await client.SendAsync(badRequest);
51+
52+
var content = await badResponse.Content.ReadAsStringAsync();
53+
54+
Assert.Contains("Invalid Hostname", content, StringComparison.OrdinalIgnoreCase);
55+
56+
Assert.Equal(HttpStatusCode.BadRequest, badResponse.StatusCode);
57+
}
58+
}

0 commit comments

Comments
 (0)