Skip to content
Merged
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
@@ -0,0 +1,6 @@
namespace OrchardCoreContrib.HealthChecks;

public class HealthChecksAccessOptions
{
public HashSet<string> AllowedIPs { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrchardCore.Environment.Shell.Configuration;

namespace OrchardCoreContrib.HealthChecks;

public class HealthChecksIPRestrictionMiddleware(
RequestDelegate next,
IShellConfiguration shellConfiguration,
IOptions<HealthChecksOptions> healthChecksOptions,
IOptions<HealthChecksAccessOptions> healthChecksAccessOptions,
ILogger<HealthChecksIPRestrictionMiddleware> logger)
{
private readonly HealthChecksOptions _healthChecksOptions = healthChecksOptions.Value;
private readonly HashSet<string> _allowedIPs =
shellConfiguration.GetSection($"{Constants.ConfigurationKey}:AllowedIPs").Get<string[]>()?.ToHashSet(StringComparer.OrdinalIgnoreCase)
?? [];

public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.Equals(_healthChecksOptions.Url))
{
var remoteIP = context.Connection.RemoteIpAddress?.ToString();
if (!_allowedIPs.Contains(remoteIP))
if (!healthChecksAccessOptions.Value.AllowedIPs.Contains(remoteIP))
{
logger.LogWarning("Unauthorized IP {IP} tried to access {HealthCheckEndpoint}.", remoteIP, _healthChecksOptions.Url);

Expand Down
6 changes: 3 additions & 3 deletions src/OrchardCoreContrib.HealthChecks/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@
[assembly: Feature(
Id = "OrchardCoreContrib.HealthChecks.IPRestriction",
Name = "Health Checks IP Restriction",
Description = "Restricts access to health check endpoints by IP address.",
Description = "Restricts access to health checks endpoints by IP address.",
Dependencies = [ "OrchardCoreContrib.HealthChecks" ]
)]

[assembly: Feature(
Id = "OrchardCoreContrib.HealthChecks.RateLimiting",
Name = "Health Checks Rate Limiting",
Description = "Limits requests to health check endpoints to prevent DOS attacks.",
Description = "Limits requests to health checks endpoints to prevent DOS attacks.",
Dependencies = ["OrchardCoreContrib.HealthChecks"]
)]

[assembly: Feature(
Id = "OrchardCoreContrib.HealthChecks.BlockingRateLimiting",
Name = "Health Checks Blocking Rate Limiting",
Description = "Adds blocking behavior to the health check rate limiter. Clients exceeding the limit are temporarily blocked to prevent DoS attacks.",
Description = "Adds blocking behavior to the health checks rate limiter. Clients exceeding the limit are temporarily blocked to prevent DoS attacks.",
Dependencies = new[] { "OrchardCoreContrib.HealthChecks.RateLimiting" }
)]
9 changes: 5 additions & 4 deletions src/OrchardCoreContrib.HealthChecks/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@ private static async Task WriteResponse(HttpContext context, HealthReport report
}

[Feature("OrchardCoreContrib.HealthChecks.IPRestriction")]
public class IPRestrictionStartup : StartupBase
public class IPRestrictionStartup(IShellConfiguration shellConfiguration) : StartupBase
{
public override int Order => 10;

public override void ConfigureServices(IServiceCollection services)
=> services.Configure<HealthChecksAccessOptions>(shellConfiguration.GetSection($"{Constants.ConfigurationKey}:Access"));

public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
=> app.UseMiddleware<HealthChecksIPRestrictionMiddleware>();
}
Expand All @@ -83,9 +86,7 @@ public class RateLimitingStartup(IShellConfiguration shellConfiguration) : Start
public override int Order => 30;

public override void ConfigureServices(IServiceCollection services)
{
services.Configure<HealthChecksRateLimitingOptions>(shellConfiguration.GetSection($"{Constants.ConfigurationKey}:RateLimiting"));
}
=> services.Configure<HealthChecksRateLimitingOptions>(shellConfiguration.GetSection($"{Constants.ConfigurationKey}:RateLimiting"));

public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
=> app.UseMiddleware<HealthChecksRateLimitingMiddleware>();
Expand Down
4 changes: 3 additions & 1 deletion src/OrchardCoreContrib.Modules.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
"OrchardCoreContrib_HealthChecks": {
"Url": "/health",
"ShowDetails": true,
"AllowedIPs": [ "127.0.0.1", "::1" ],
"Access": {
"AllowedIPs": [ "127.0.0.1", "::1" ]
},
"RateLimiting": {
"PermitLimit": 5,
"Window": "00:00:10",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ private IConfigurationRoot AddHealthChecksConfiguration()
var newConfiguration = new Dictionary<string, string>
{
{ $"{Constants.ConfigurationKey}:{nameof(HealthChecksOptions.Url)}", "/health" },
{ $"{Constants.ConfigurationKey}:AllowedIPs:0", "127.0.0.1" },
{ $"{Constants.ConfigurationKey}:AllowedIPs:1", "::1" }
{ $"{Constants.ConfigurationKey}:Access:AllowedIPs:0", "127.0.0.1" },
{ $"{Constants.ConfigurationKey}:Access:AllowedIPs:1", "::1" }
};

return new ConfigurationBuilder()
Expand Down
Loading