Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[pack] Using Job Host scoped middleware for hsts configuration (#4506)
- Loading branch information
Showing
9 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
src/WebJobs.Script.WebHost/Configuration/HostHstsOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using Microsoft.AspNetCore.HttpsPolicy; | ||
|
||
namespace Microsoft.Azure.WebJobs.Script.WebHost.Configuration | ||
{ | ||
public class HostHstsOptions : HstsOptions | ||
{ | ||
public bool IsEnabled { get; set; } | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
src/WebJobs.Script.WebHost/Configuration/HostHstsOptionsSetup.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using Microsoft.Azure.WebJobs.Script.Configuration; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Azure.WebJobs.Script.WebHost.Configuration | ||
{ | ||
internal class HostHstsOptionsSetup : IConfigureOptions<HostHstsOptions> | ||
{ | ||
private readonly IConfiguration _configuration; | ||
|
||
public HostHstsOptionsSetup(IConfiguration configuration) | ||
{ | ||
_configuration = configuration; | ||
} | ||
|
||
public void Configure(HostHstsOptions options) | ||
{ | ||
IConfigurationSection jobHostSection = _configuration.GetSection(ConfigurationSectionNames.JobHost); | ||
var hstsSection = jobHostSection.GetSection(ConfigurationSectionNames.Hsts); | ||
hstsSection.Bind(options); | ||
} | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/WebJobs.Script.WebHost/Middleware/HstsConfigurationMiddleware.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.HttpsPolicy; | ||
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.Azure.WebJobs.Script.Middleware | ||
{ | ||
public class HstsConfigurationMiddleware : IJobHostHttpMiddleware | ||
{ | ||
private RequestDelegate _invoke; | ||
|
||
public HstsConfigurationMiddleware(IOptions<HostHstsOptions> hostHstsOptions) | ||
{ | ||
RequestDelegate contextNext = async context => | ||
{ | ||
if (context.Items.Remove(ScriptConstants.HstsMiddlewareRequestDelegate, out object requestDelegate) && requestDelegate is RequestDelegate next) | ||
{ | ||
await next(context); | ||
} | ||
}; | ||
|
||
if (hostHstsOptions.Value.IsEnabled) | ||
{ | ||
var hstsMiddleware = new HstsMiddleware(contextNext, hostHstsOptions); | ||
_invoke = hstsMiddleware.Invoke; | ||
} | ||
else | ||
{ | ||
_invoke = contextNext; | ||
} | ||
} | ||
|
||
public async Task Invoke(HttpContext context, RequestDelegate next) | ||
{ | ||
context.Items.Add(ScriptConstants.HstsMiddlewareRequestDelegate, next); | ||
await _invoke(context); | ||
} | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
test/WebJobs.Script.Tests/Configuration/HostHstsOptionsSetupTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
using Microsoft.Azure.WebJobs.Script.Config; | ||
using Microsoft.Azure.WebJobs.Script.Configuration; | ||
using Microsoft.Azure.WebJobs.Script.ExtensionBundle; | ||
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Hosting; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.WebJobs.Script.Tests; | ||
using Moq; | ||
using Xunit; | ||
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames; | ||
|
||
namespace Microsoft.Azure.WebJobs.Script.Tests.Configuration | ||
{ | ||
public class HostHstsOptionsSetupTests | ||
{ | ||
private readonly TestEnvironment _environment = new TestEnvironment(); | ||
private readonly TestLoggerProvider _loggerProvider = new TestLoggerProvider(); | ||
private readonly string _hostJsonFile; | ||
private readonly string _rootPath; | ||
private readonly ScriptApplicationHostOptions _options; | ||
|
||
public HostHstsOptionsSetupTests() | ||
{ | ||
_rootPath = Path.Combine(Environment.CurrentDirectory, "ScriptHostTests"); | ||
Environment.SetEnvironmentVariable(AzureWebJobsScriptRoot, _rootPath); | ||
|
||
if (!Directory.Exists(_rootPath)) | ||
{ | ||
Directory.CreateDirectory(_rootPath); | ||
} | ||
|
||
_options = new ScriptApplicationHostOptions | ||
{ | ||
ScriptPath = _rootPath | ||
}; | ||
|
||
_hostJsonFile = Path.Combine(_rootPath, "host.json"); | ||
if (File.Exists(_hostJsonFile)) | ||
{ | ||
File.Delete(_hostJsonFile); | ||
} | ||
} | ||
|
||
[Theory] | ||
[InlineData(@"{ | ||
'version': '2.0', | ||
}")] | ||
[InlineData(@"{ | ||
'version': '2.0', | ||
'http' : { | ||
'hsts' : { | ||
'isEnabled' : true | ||
} | ||
} | ||
}")] | ||
public void MissingOrValidHstsConfig_DoesNotThrowException(string hostJsonContent) | ||
{ | ||
File.WriteAllText(_hostJsonFile, hostJsonContent); | ||
var configuration = BuildHostJsonConfiguration(); | ||
|
||
HostHstsOptionsSetup setup = new HostHstsOptionsSetup(configuration); | ||
HostHstsOptions options = new HostHstsOptions(); | ||
var ex = Record.Exception(() => setup.Configure(options)); | ||
Assert.Null(ex); | ||
} | ||
|
||
[Fact] | ||
public void ValidHstsConfig_BindsToOptions() | ||
{ | ||
string hostJsonContent = @"{ | ||
'version': '2.0', | ||
'http': { | ||
'hsts': { | ||
'isEnabled': true, | ||
'maxAge': '10' | ||
} | ||
} | ||
}"; | ||
File.WriteAllText(_hostJsonFile, hostJsonContent); | ||
var configuration = BuildHostJsonConfiguration(); | ||
|
||
HostHstsOptionsSetup setup = new HostHstsOptionsSetup(configuration); | ||
HostHstsOptions options = new HostHstsOptions(); | ||
setup.Configure(options); | ||
Assert.Equal(options.MaxAge, new TimeSpan(10, 0, 0, 0)); | ||
} | ||
|
||
private IConfiguration BuildHostJsonConfiguration(IEnvironment environment = null) | ||
{ | ||
environment = environment ?? new TestEnvironment(); | ||
|
||
var loggerFactory = new LoggerFactory(); | ||
loggerFactory.AddProvider(_loggerProvider); | ||
|
||
var configSource = new HostJsonFileConfigurationSource(_options, environment, loggerFactory); | ||
|
||
var configurationBuilder = new ConfigurationBuilder() | ||
.Add(configSource); | ||
|
||
return configurationBuilder.Build(); | ||
} | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
test/WebJobs.Script.Tests/Middleware/HstsConfigurationMiddlewareTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Net; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Azure.WebJobs.Script.Config; | ||
using Microsoft.Azure.WebJobs.Script.Middleware; | ||
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration; | ||
using Microsoft.Extensions.Options; | ||
using Xunit; | ||
|
||
namespace Microsoft.Azure.WebJobs.Script.Tests.Middleware | ||
{ | ||
public class HstsConfigurationMiddlewareTests | ||
{ | ||
[Fact] | ||
public async Task Invoke_hstsEnabled_AddsResponseHeader() | ||
{ | ||
var hstsOptions = new OptionsWrapper<HostHstsOptions>(new HostHstsOptions() { IsEnabled = true }); | ||
|
||
bool nextInvoked = false; | ||
RequestDelegate next = (ctxt) => | ||
{ | ||
nextInvoked = true; | ||
ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; | ||
return Task.CompletedTask; | ||
}; | ||
|
||
var middleware = new HstsConfigurationMiddleware(hstsOptions); | ||
|
||
var httpContext = new DefaultHttpContext(); | ||
httpContext.Request.IsHttps = true; | ||
await middleware.Invoke(httpContext, next); | ||
Assert.True(nextInvoked); | ||
Assert.Equal(httpContext.Response.Headers["Strict-Transport-Security"].ToString(), "max-age=2592000"); | ||
} | ||
|
||
[Fact] | ||
public async Task Invoke_hstsDisabled_DoesNotAddResponseHeader() | ||
{ | ||
var hstsOptions = new OptionsWrapper<HostHstsOptions>(new HostHstsOptions() { IsEnabled = false }); | ||
|
||
bool nextInvoked = false; | ||
RequestDelegate next = (ctxt) => | ||
{ | ||
nextInvoked = true; | ||
ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; | ||
return Task.CompletedTask; | ||
}; | ||
|
||
var middleware = new HstsConfigurationMiddleware(hstsOptions); | ||
|
||
var httpContext = new DefaultHttpContext(); | ||
httpContext.Request.IsHttps = true; | ||
await middleware.Invoke(httpContext, next); | ||
Assert.True(nextInvoked); | ||
Assert.Equal(httpContext.Response.Headers.Count, 0); | ||
} | ||
|
||
[Fact] | ||
public async Task Invoke_hstsEnabled_AddsResponseHeaderWithCorrectValue() | ||
{ | ||
bool nextInvoked = false; | ||
RequestDelegate next = (ctxt) => | ||
{ | ||
nextInvoked = true; | ||
ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; | ||
return Task.CompletedTask; | ||
}; | ||
|
||
var options = new HostHstsOptions() | ||
{ | ||
IsEnabled = true, | ||
MaxAge = new TimeSpan(10, 0, 0, 0) | ||
}; | ||
var hstsOptions = new OptionsWrapper<HostHstsOptions>(options); | ||
|
||
var middleware = new HstsConfigurationMiddleware(hstsOptions); | ||
|
||
var httpContext = new DefaultHttpContext(); | ||
httpContext.Request.IsHttps = true; | ||
|
||
await middleware.Invoke(httpContext, next); | ||
Assert.True(nextInvoked); | ||
Assert.Equal(httpContext.Response.Headers["Strict-Transport-Security"].ToString(), "max-age=864000"); | ||
} | ||
|
||
[Fact] | ||
public async Task Invoke_hstsDisabledByDefault() | ||
{ | ||
var hstsOptions = new OptionsWrapper<HostHstsOptions>(new HostHstsOptions()); | ||
|
||
bool nextInvoked = false; | ||
RequestDelegate next = (ctxt) => | ||
{ | ||
nextInvoked = true; | ||
ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; | ||
return Task.CompletedTask; | ||
}; | ||
|
||
var middleware = new HstsConfigurationMiddleware(hstsOptions); | ||
|
||
var httpContext = new DefaultHttpContext(); | ||
httpContext.Request.IsHttps = true; | ||
await middleware.Invoke(httpContext, next); | ||
Assert.True(nextInvoked); | ||
Assert.Equal(httpContext.Response.Headers.Count, 0); | ||
} | ||
} | ||
} |