| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- commit 77c9bc38f914b78f53fd5828c68c4ceca44bd841
- Author: Nate Barbettini <[email protected]>
- Date: Mon May 7 08:18:04 2018 -0700
- Add logging to HstsMiddleware (#327)
- diff --git a/src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs b/src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs
- index 252ae44c1e8..da5aa3af4b6 100644
- --- a/src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs
- +++ b/src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs
- @@ -6,6 +6,8 @@ using System.Collections.Generic;
- using System.Globalization;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Http;
- +using Microsoft.AspNetCore.HttpsPolicy.Internal;
- +using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Options;
- using Microsoft.Extensions.Primitives;
- using Microsoft.Net.Http.Headers;
- @@ -24,13 +26,15 @@ namespace Microsoft.AspNetCore.HttpsPolicy
- private readonly RequestDelegate _next;
- private readonly StringValues _strictTransportSecurityValue;
- private readonly IList<string> _excludedHosts;
- + private readonly ILogger _logger;
-
- /// <summary>
- /// Initialize the HSTS middleware.
- /// </summary>
- /// <param name="next"></param>
- /// <param name="options"></param>
- - public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options)
- + /// <param name="loggerFactory"></param>
- + public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options, ILoggerFactory loggerFactory)
- {
- if (options == null)
- {
- @@ -46,6 +50,7 @@ namespace Microsoft.AspNetCore.HttpsPolicy
- var preload = hstsOptions.Preload ? Preload : StringSegment.Empty;
- _strictTransportSecurityValue = new StringValues($"max-age={maxAge}{includeSubdomains}{preload}");
- _excludedHosts = hstsOptions.ExcludedHosts;
- + _logger = loggerFactory.CreateLogger<HstsMiddleware>();
- }
-
- /// <summary>
- @@ -55,11 +60,21 @@ namespace Microsoft.AspNetCore.HttpsPolicy
- /// <returns></returns>
- public Task Invoke(HttpContext context)
- {
- - if (context.Request.IsHttps && !IsHostExcluded(context.Request.Host.Host))
- + if (!context.Request.IsHttps)
- {
- - context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue;
- + _logger.SkippingInsecure();
- + return _next(context);
- }
-
- + if (IsHostExcluded(context.Request.Host.Host))
- + {
- + _logger.SkippingExcludedHost(context.Request.Host.Host);
- + return _next(context);
- + }
- +
- + context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue;
- + _logger.AddingHstsHeader();
- +
- return _next(context);
- }
-
- diff --git a/src/Microsoft.AspNetCore.HttpsPolicy/internal/HstsLoggingExtensions.cs b/src/Microsoft.AspNetCore.HttpsPolicy/internal/HstsLoggingExtensions.cs
- new file mode 100644
- index 00000000000..5162ccb9f5e
- --- /dev/null
- +++ b/src/Microsoft.AspNetCore.HttpsPolicy/internal/HstsLoggingExtensions.cs
- @@ -0,0 +1,37 @@
- +// Copyright (c) .NET Foundation. All rights reserved.
- +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
- +
- +using System;
- +using Microsoft.Extensions.Logging;
- +
- +namespace Microsoft.AspNetCore.HttpsPolicy.Internal
- +{
- + internal static class HstsLoggingExtensions
- + {
- + private static readonly Action<ILogger, Exception> _notSecure;
- + private static readonly Action<ILogger, string, Exception> _excludedHost;
- + private static readonly Action<ILogger, Exception> _addingHstsHeader;
- +
- + static HstsLoggingExtensions()
- + {
- + _notSecure = LoggerMessage.Define(LogLevel.Debug, 1, "The request is insecure. Skipping HSTS header.");
- + _excludedHost = LoggerMessage.Define<string>(LogLevel.Debug, 2, "The host '{host}' is excluded. Skipping HSTS header.");
- + _addingHstsHeader = LoggerMessage.Define(LogLevel.Trace, 3, "Adding HSTS header to response.");
- + }
- +
- + public static void SkippingInsecure(this ILogger logger)
- + {
- + _notSecure(logger, null);
- + }
- +
- + public static void SkippingExcludedHost(this ILogger logger, string host)
- + {
- + _excludedHost(logger, host, null);
- + }
- +
- + public static void AddingHstsHeader(this ILogger logger)
- + {
- + _addingHstsHeader(logger, null);
- + }
- + }
- +}
- diff --git a/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs b/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs
- index 08df78f7c2c..0cb5f5755c9 100644
- --- a/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs
- +++ b/test/Microsoft.AspNetCore.HttpsPolicy.Tests/HstsMiddlewareTests.cs
- @@ -12,6 +12,8 @@ using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.Http;
- using Microsoft.AspNetCore.TestHost;
- using Microsoft.Extensions.DependencyInjection;
- +using Microsoft.Extensions.Logging;
- +using Microsoft.Extensions.Logging.Testing;
- using Microsoft.Net.Http.Headers;
- using Xunit;
-
- @@ -131,7 +133,16 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- [InlineData("[::1]")]
- public async Task DefaultExcludesCommonLocalhostDomains_DoesNotSetHstsHeader(string host)
- {
- + var sink = new TestSink(
- + TestSink.EnableWithTypeName<HstsMiddleware>,
- + TestSink.EnableWithTypeName<HstsMiddleware>);
- + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- +
- var builder = new WebHostBuilder()
- + .ConfigureServices(services =>
- + {
- + services.AddSingleton<ILoggerFactory>(loggerFactory);
- + })
- .Configure(app =>
- {
- app.UseHsts();
- @@ -149,6 +160,13 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Empty(response.Headers);
- +
- + var logMessages = sink.Writes.ToList();
- +
- + Assert.Single(logMessages);
- + var message = logMessages.Single();
- + Assert.Equal(LogLevel.Debug, message.LogLevel);
- + Assert.Equal($"The host '{host}' is excluded. Skipping HSTS header.", message.State.ToString(), ignoreCase: true);
- }
-
- [Theory]
- @@ -157,9 +175,16 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- [InlineData("[::1]")]
- public async Task AllowLocalhostDomainsIfListIsReset_SetHstsHeader(string host)
- {
- + var sink = new TestSink(
- + TestSink.EnableWithTypeName<HstsMiddleware>,
- + TestSink.EnableWithTypeName<HstsMiddleware>);
- + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- +
- var builder = new WebHostBuilder()
- .ConfigureServices(services =>
- {
- + services.AddSingleton<ILoggerFactory>(loggerFactory);
- +
- services.AddHsts(options =>
- {
- options.ExcludedHosts.Clear();
- @@ -182,6 +207,13 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Single(response.Headers);
- +
- + var logMessages = sink.Writes.ToList();
- +
- + Assert.Single(logMessages);
- + var message = logMessages.Single();
- + Assert.Equal(LogLevel.Trace, message.LogLevel);
- + Assert.Equal("Adding HSTS header to response.", message.State.ToString());
- }
-
- [Theory]
- @@ -190,9 +222,16 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
- [InlineData("EXAMPLE.COM")]
- public async Task AddExcludedDomains_DoesNotAddHstsHeader(string host)
- {
- + var sink = new TestSink(
- + TestSink.EnableWithTypeName<HstsMiddleware>,
- + TestSink.EnableWithTypeName<HstsMiddleware>);
- + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- +
- var builder = new WebHostBuilder()
- .ConfigureServices(services =>
- {
- + services.AddSingleton<ILoggerFactory>(loggerFactory);
- +
- services.AddHsts(options => {
- options.ExcludedHosts.Add(host);
- });
- @@ -214,6 +253,91 @@ namespace Microsoft.AspNetCore.HttpsPolicy.Tests
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Empty(response.Headers);
- +
- + var logMessages = sink.Writes.ToList();
- +
- + Assert.Single(logMessages);
- + var message = logMessages.Single();
- + Assert.Equal(LogLevel.Debug, message.LogLevel);
- + Assert.Equal($"The host '{host}' is excluded. Skipping HSTS header.", message.State.ToString(), ignoreCase: true);
- + }
- +
- + [Fact]
- + public async Task WhenRequestIsInsecure_DoesNotAddHstsHeader()
- + {
- + var sink = new TestSink(
- + TestSink.EnableWithTypeName<HstsMiddleware>,
- + TestSink.EnableWithTypeName<HstsMiddleware>);
- + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- +
- + var builder = new WebHostBuilder()
- + .ConfigureServices(services =>
- + {
- + services.AddSingleton<ILoggerFactory>(loggerFactory);
- + })
- + .Configure(app =>
- + {
- + app.UseHsts();
- + app.Run(context =>
- + {
- + return context.Response.WriteAsync("Hello world");
- + });
- + });
- + var server = new TestServer(builder);
- + var client = server.CreateClient();
- + client.BaseAddress = new Uri("http://example.com:5050");
- + var request = new HttpRequestMessage(HttpMethod.Get, "");
- +
- + var response = await client.SendAsync(request);
- +
- + Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- + Assert.Empty(response.Headers);
- +
- + var logMessages = sink.Writes.ToList();
- +
- + Assert.Single(logMessages);
- + var message = logMessages.Single();
- + Assert.Equal(LogLevel.Debug, message.LogLevel);
- + Assert.Equal("The request is insecure. Skipping HSTS header.", message.State.ToString());
- + }
- +
- + [Fact]
- + public async Task WhenRequestIsSecure_AddsHstsHeader()
- + {
- + var sink = new TestSink(
- + TestSink.EnableWithTypeName<HstsMiddleware>,
- + TestSink.EnableWithTypeName<HstsMiddleware>);
- + var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- +
- + var builder = new WebHostBuilder()
- + .ConfigureServices(services =>
- + {
- + services.AddSingleton<ILoggerFactory>(loggerFactory);
- + })
- + .Configure(app =>
- + {
- + app.UseHsts();
- + app.Run(context =>
- + {
- + return context.Response.WriteAsync("Hello world");
- + });
- + });
- + var server = new TestServer(builder);
- + var client = server.CreateClient();
- + client.BaseAddress = new Uri("https://example.com:5050");
- + var request = new HttpRequestMessage(HttpMethod.Get, "");
- +
- + var response = await client.SendAsync(request);
- +
- + Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- + Assert.Contains(response.Headers, x => x.Key == HeaderNames.StrictTransportSecurity);
- +
- + var logMessages = sink.Writes.ToList();
- +
- + Assert.Single(logMessages);
- + var message = logMessages.Single();
- + Assert.Equal(LogLevel.Trace, message.LogLevel);
- + Assert.Equal("Adding HSTS header to response.", message.State.ToString());
- }
- }
- }
|