diff --git a/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/AuthorizedHealthAuthorizationFilter.cs b/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/AuthorizedHealthAuthorizationFilter.cs
new file mode 100644
index 00000000..40017d40
--- /dev/null
+++ b/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/AuthorizedHealthAuthorizationFilter.cs
@@ -0,0 +1,15 @@
+//
+// Copyright (c) Allan Hardy. All rights reserved.
+//
+
+using System.Threading;
+using Microsoft.AspNetCore.Http;
+
+namespace App.Metrics.AspNetCore.Health
+{
+ public class AuthorizedHealthAuthorizationFilter : IHealthAuthorizationFilter
+ {
+ ///
+ public bool Authorized(HttpContext context, CancellationToken token = default) { return true; }
+ }
+}
diff --git a/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/IHealthAuthorizationFilter.cs b/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/IHealthAuthorizationFilter.cs
new file mode 100644
index 00000000..e588b1e9
--- /dev/null
+++ b/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/IHealthAuthorizationFilter.cs
@@ -0,0 +1,14 @@
+//
+// Copyright (c) Allan Hardy. All rights reserved.
+//
+
+using System.Threading;
+using Microsoft.AspNetCore.Http;
+
+namespace App.Metrics.AspNetCore.Health
+{
+ public interface IHealthAuthorizationFilter
+ {
+ bool Authorized(HttpContext context, CancellationToken token = default);
+ }
+}
\ No newline at end of file
diff --git a/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/NotAuthorizedHealthAuthorizationFilter.cs b/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/NotAuthorizedHealthAuthorizationFilter.cs
new file mode 100644
index 00000000..4e502061
--- /dev/null
+++ b/src/App.Metrics.AspNetCore.Health.Core/Internal/Authorization/NotAuthorizedHealthAuthorizationFilter.cs
@@ -0,0 +1,15 @@
+//
+// Copyright (c) Allan Hardy. All rights reserved.
+//
+
+using System.Threading;
+using Microsoft.AspNetCore.Http;
+
+namespace App.Metrics.AspNetCore.Health
+{
+ public class NotAuthorizedHealthAuthorizationFilter : IHealthAuthorizationFilter
+ {
+ ///
+ public bool Authorized(HttpContext context, CancellationToken token = default) { return false; }
+ }
+}
\ No newline at end of file
diff --git a/src/App.Metrics.AspNetCore.Health.Core/Internal/Extensions/AppMetricsMiddlewareHealthChecksLoggerExtensions.cs b/src/App.Metrics.AspNetCore.Health.Core/Internal/Extensions/AppMetricsMiddlewareHealthChecksLoggerExtensions.cs
index 62fc2353..cac5577c 100644
--- a/src/App.Metrics.AspNetCore.Health.Core/Internal/Extensions/AppMetricsMiddlewareHealthChecksLoggerExtensions.cs
+++ b/src/App.Metrics.AspNetCore.Health.Core/Internal/Extensions/AppMetricsMiddlewareHealthChecksLoggerExtensions.cs
@@ -33,6 +33,14 @@ public static void MiddlewareExecuting(this ILogger logger)
}
}
+ public static void MiddlewareAuthorizationInvalid(this ILogger logger)
+ {
+ if (logger.IsEnabled(LogLevel.Trace))
+ {
+ logger.LogTrace(AppMetricsEventIds.Middleware.MiddlewareAuthorizationInvalidId, $"Invalid authorization App Metrics Health Middleware {typeof(TMiddleware).FullName}");
+ }
+ }
+
private static class AppMetricsEventIds
{
public static class Middleware
@@ -40,6 +48,7 @@ public static class Middleware
public const int MiddlewareExecutedId = 1;
public const int MiddlewareExecutingId = 2;
public const int MiddlewareErrorId = 3;
+ public const int MiddlewareAuthorizationInvalidId = 4;
}
}
}
diff --git a/src/App.Metrics.AspNetCore.Health.Endpoints/Builder/HealthApplicationBuilderExtensions.cs b/src/App.Metrics.AspNetCore.Health.Endpoints/Builder/HealthApplicationBuilderExtensions.cs
index b6f426c3..8d57d85a 100644
--- a/src/App.Metrics.AspNetCore.Health.Endpoints/Builder/HealthApplicationBuilderExtensions.cs
+++ b/src/App.Metrics.AspNetCore.Health.Endpoints/Builder/HealthApplicationBuilderExtensions.cs
@@ -3,6 +3,7 @@
//
using System;
+using App.Metrics.AspNetCore.Health;
using App.Metrics.AspNetCore.Health.Endpoints;
using App.Metrics.AspNetCore.Health.Endpoints.Middleware;
using App.Metrics.Health;
@@ -112,7 +113,8 @@ private static void UseHealthMiddleware(
appBuilder =>
{
var responseWriter = HealthAspNetCoreHealthEndpointsServiceCollectionExtensions.ResolveHealthResponseWriter(app.ApplicationServices, formatter);
- appBuilder.UseMiddleware(responseWriter, endpointsOptionsAccessor.Value.Timeout);
+ var healthAuthorizationFilter = app.ApplicationServices.GetService() ?? new AuthorizedHealthAuthorizationFilter();
+ appBuilder.UseMiddleware(responseWriter, endpointsOptionsAccessor.Value.Timeout, healthAuthorizationFilter);
});
}
}
diff --git a/src/App.Metrics.AspNetCore.Health.Endpoints/Middleware/HealthCheckEndpointMiddleware.cs b/src/App.Metrics.AspNetCore.Health.Endpoints/Middleware/HealthCheckEndpointMiddleware.cs
index 46cc2f46..bde4cbf9 100644
--- a/src/App.Metrics.AspNetCore.Health.Endpoints/Middleware/HealthCheckEndpointMiddleware.cs
+++ b/src/App.Metrics.AspNetCore.Health.Endpoints/Middleware/HealthCheckEndpointMiddleware.cs
@@ -19,6 +19,7 @@ public class HealthCheckEndpointMiddleware
{
private readonly IRunHealthChecks _healthCheckRunner;
private readonly IHealthResponseWriter _healthResponseWriter;
+ private readonly IHealthAuthorizationFilter _healthAuthorizationFilter;
private readonly ILogger _logger;
private readonly TimeSpan _timeout;
@@ -28,12 +29,14 @@ public HealthCheckEndpointMiddleware(
ILoggerFactory loggerFactory,
IRunHealthChecks healthCheckRunner,
IHealthResponseWriter healthResponseWriter,
+ IHealthAuthorizationFilter healthAuthorizationFilter,
TimeSpan timeout)
// ReSharper restore UnusedParameter.Local
{
_healthCheckRunner = healthCheckRunner;
_logger = loggerFactory.CreateLogger();
_healthResponseWriter = healthResponseWriter ?? throw new ArgumentNullException(nameof(healthResponseWriter));
+ _healthAuthorizationFilter = healthAuthorizationFilter ?? new AuthorizedHealthAuthorizationFilter();
_timeout = timeout <= TimeSpan.Zero ? TimeSpan.FromSeconds(20) : timeout;
}
@@ -49,9 +52,18 @@ public async Task Invoke(HttpContext context)
{
try
{
+ if (!_healthAuthorizationFilter.Authorized(context))
+ {
+ _logger.MiddlewareAuthorizationInvalid();
+ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
+ await context.Response.WriteAsync("Invalid authorization.", cancellationToken: cancellationTokenSource.Token);
+ }
+ else
+ {
var healthStatus = await _healthCheckRunner.ReadAsync(cancellationTokenSource.Token);
await _healthResponseWriter.WriteAsync(context, healthStatus, cancellationTokenSource.Token);
+ }
}
catch (OperationCanceledException e)
{
diff --git a/test/App.Metrics.AspNetCore.Health.Integration.Facts/Middleware/HealthCheckEndpointAuthorizationMiddleware.cs b/test/App.Metrics.AspNetCore.Health.Integration.Facts/Middleware/HealthCheckEndpointAuthorizationMiddleware.cs
new file mode 100644
index 00000000..bbd8c59e
--- /dev/null
+++ b/test/App.Metrics.AspNetCore.Health.Integration.Facts/Middleware/HealthCheckEndpointAuthorizationMiddleware.cs
@@ -0,0 +1,31 @@
+//
+// Copyright (c) Allan Hardy. All rights reserved.
+//
+
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using App.Metrics.AspNetCore.Health.Integration.Facts.Startup;
+using FluentAssertions;
+using Xunit;
+
+namespace App.Metrics.AspNetCore.Health.Integration.Facts.Middleware
+{
+ public class HealthCheckEndpointAuthorizationMiddleware : IClassFixture>
+ {
+ public HealthCheckEndpointAuthorizationMiddleware(StartupTestFixture fixture)
+ {
+ Client = fixture.Client;
+ }
+
+ private HttpClient Client { get; }
+
+ [Fact]
+ public async Task Returns_correct_response_headers_not_authorized()
+ {
+ var result = await Client.GetAsync("/health");
+
+ result.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/App.Metrics.AspNetCore.Health.Integration.Facts/Startup/NotAuthorizedHealthTestStartup.cs b/test/App.Metrics.AspNetCore.Health.Integration.Facts/Startup/NotAuthorizedHealthTestStartup.cs
new file mode 100644
index 00000000..52c57013
--- /dev/null
+++ b/test/App.Metrics.AspNetCore.Health.Integration.Facts/Startup/NotAuthorizedHealthTestStartup.cs
@@ -0,0 +1,37 @@
+//
+// Copyright (c) Allan Hardy. All rights reserved.
+//
+
+using App.Metrics.AspNetCore.Health.Endpoints;
+using App.Metrics.Health;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace App.Metrics.AspNetCore.Health.Integration.Facts.Startup
+{
+ // ReSharper disable ClassNeverInstantiated.Global
+ public class NotAuthorizedHealthTestStartup : TestStartup
+ // ReSharper restore ClassNeverInstantiated.Global
+ {
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
+ {
+ app.UseHealthEndpoint();
+
+ SetupAppBuilder(app, env, loggerFactory);
+ }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton();
+
+ var appMetricsMiddlewareHealthCheckOptions = new HealthEndpointsOptions();
+
+ SetupServices(
+ services,
+ appMetricsMiddlewareHealthCheckOptions,
+ healthChecks: new[] { HealthCheckResult.Healthy() });
+ }
+ }
+}
\ No newline at end of file