Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Security Module (Lombiq Technologies: OCORE-91) #11538

Merged
merged 108 commits into from
May 20, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
b5bec6a
Add OrchardCore.Security module
hishamco Apr 13, 2022
5fb68e3
Add ReferrerPolicyMiddleware
hishamco Apr 14, 2022
f1b18f9
Split middleware and ApplicationExtension tests
hishamco Apr 14, 2022
4d4b972
Add UseSecurityHeaders() extension methods
hishamco Apr 14, 2022
0d644c5
Use Enum-like class instead
hishamco Apr 14, 2022
a279a51
Simplify UseSecurityHeaders() with SecurityHeadersBuilder
hishamco Apr 14, 2022
2282f58
Add UI to configure ReferrerPolicy
hishamco Apr 14, 2022
e362afd
Fix the build
hishamco Apr 14, 2022
820678e
Merge branch 'main' into hishamco/security-module
hishamco Apr 14, 2022
d08e3e0
Remove unnecessar using
hishamco Apr 14, 2022
99ede53
Fix broken test
hishamco Apr 14, 2022
e9c2c3a
Refactoring
hishamco Apr 14, 2022
530a992
Add SecurityHeadersBuilder extension methods
hishamco Apr 15, 2022
d70cc23
Add X-Frame-Options header
hishamco Apr 15, 2022
3c8f64b
XFrameOptions -> FrameOptions
hishamco Apr 15, 2022
f0a97ec
Introduce options builder for fluent configuration
hishamco Apr 15, 2022
17af2b5
Remove unnecessary ApplicationBuilder extension methods
hishamco Apr 15, 2022
233654d
Move SecurityHeaderNames to OC.Infrastructure.Abstractions
hishamco Apr 15, 2022
37e1308
Add PermissionsPolicy header
hishamco Apr 16, 2022
a167ea9
Add X-Content-Type-Options header
hishamco Apr 16, 2022
1aefe17
Refactoring
hishamco Apr 17, 2022
0dbd2fa
Add origin option for PermissonsPolicy
hishamco Apr 17, 2022
5538387
Add unit tests
hishamco Apr 18, 2022
ffc9f06
Remove ContentTypeOptions settings so it should be added always
hishamco Apr 18, 2022
a6fe830
Change settings hint
hishamco Apr 18, 2022
efc8125
Add Strict-Transport-Security header
hishamco Apr 18, 2022
10a5f1b
Bring UseSecurityHeaders() back
hishamco Apr 18, 2022
36770fb
Add fullscreen in self origin as default permission
hishamco Apr 19, 2022
fffa545
Refactoring
hishamco Apr 21, 2022
267c0d8
Address feedback
hishamco Apr 21, 2022
26f2c0c
Remove Order from startup
hishamco Apr 21, 2022
d98af9d
Add Options namespace
hishamco Apr 21, 2022
4adc16f
Remove unnecessary Values property from PermissionsPolicyOptions
hishamco Apr 21, 2022
307d1a8
Remove the checks from the middleware constructors
hishamco Apr 21, 2022
a94b85e
Use ArgumentNullException.ThrowIfNull()
hishamco Apr 21, 2022
ddf54ac
Remove IsAlwaysEnabled
hishamco Apr 21, 2022
a385a07
Remove HSTS
hishamco Apr 22, 2022
5df89f3
Options should be enum like classes
hishamco Apr 22, 2022
ad68337
Use single middleware
hishamco Apr 22, 2022
b3d1ad5
Address feedback
hishamco Apr 22, 2022
aa791f0
Remove unnecessary property
hishamco Apr 22, 2022
6f8433d
Ability to configure allowed permissions policy with fluent APIs
hishamco Apr 23, 2022
9692ed4
Make options configuration APIs consistent
hishamco Apr 23, 2022
48314d1
Group permissions policy options into a folder
hishamco Apr 23, 2022
9146d99
Add Content-Security-Policy header
hishamco Apr 23, 2022
211dc36
Remove unnecessary extension methods
hishamco Apr 24, 2022
54cb66d
Add fluent APIs for CSP
hishamco Apr 24, 2022
41d1c32
Remove options & options builders
hishamco Apr 24, 2022
4e4cf84
ContentSecurityPolicySourceValue -> ContentSecurityPolicyOriginValue
hishamco Apr 26, 2022
1a376e6
Convert ContentSecurityPolicy to string[]
hishamco Apr 26, 2022
33cbd00
Fix NRE
hishamco Apr 27, 2022
5906f00
Fix UI
hishamco Apr 27, 2022
24d180e
Add UI for CSP header
hishamco Apr 27, 2022
45d3b68
Fix issues in SecuritySettingsDisplayDriver
hishamco Apr 27, 2022
b7eb2ff
Check ContentSecurityPolicy length for simplicity
hishamco Apr 27, 2022
994f44e
Address feedback
hishamco May 7, 2022
fb1e286
Remove unnecessary options
hishamco May 7, 2022
fb69ca4
Refactoring
hishamco May 7, 2022
d8782a2
Add ConfigureSecuritySettings() extension method
hishamco May 7, 2022
92e5032
Fix NRE
hishamco May 7, 2022
2f8f7ce
Change policies separator to comma
hishamco May 7, 2022
fa2aa73
Fix security settings section
hishamco May 10, 2022
d36438c
Fix script issue
hishamco May 10, 2022
b052d20
Set PermissionsPolicy default to empty array of string
hishamco May 10, 2022
34bf774
Fix settings in ConfigureSecuritySettings()
hishamco May 10, 2022
26129c9
Use IOptions<SecuritySettings> to get post configured settings properly
hishamco May 10, 2022
eabf382
Move SecurityHeaderMiddleware to Services namespace
hishamco May 11, 2022
555cc36
Replace SecuritySettings by SecurityHeaderOptions
hishamco May 11, 2022
e5ac7b7
Use IOptionsSnapshot
hishamco May 11, 2022
a712dab
Set permission policy value when its set
hishamco May 11, 2022
4385a4c
Return SiteSettings back
hishamco May 11, 2022
c1e8db4
Fix settings when they come from appsettings
hishamco May 11, 2022
3f5b18e
Use IEquatable
hishamco May 11, 2022
f26aaf6
Display warning alert when the settings comes from appsettings
hishamco May 11, 2022
99659d5
Override FrameOptions by CSP if FrameAncestors exists
hishamco May 11, 2022
b992eb6
Add docs for the Orchard.Security module
hishamco May 11, 2022
3c4a606
Rename unit tests
hishamco May 11, 2022
9822a43
Include all permissons policy directives
hishamco May 11, 2022
a7fbf30
Fix broken tests
hishamco May 11, 2022
9632a29
Add section about ConfigureSecuritySettings()
hishamco May 11, 2022
105e4e7
Enable the module though all recipes
hishamco May 11, 2022
7d4ab3b
Add missing switch cases
hishamco May 11, 2022
cd014f5
Revert SecuritySettings changes bug fix
hishamco May 11, 2022
d7f31c1
Change alert type
hishamco May 11, 2022
ac6a224
Remove Frame Options
hishamco May 12, 2022
398d916
Remove readonly modifier
hishamco May 12, 2022
c98a9a7
Fix typo
hishamco May 13, 2022
b138eec
Link to configuration docs
hishamco May 13, 2022
6493df5
Rename unit tests
hishamco May 13, 2022
ed783df
IOptions<SecurityHeadersOptions> -> SecurityHeadersOptions
hishamco May 13, 2022
6e42b5e
Fix comments
hishamco May 13, 2022
9a6ff60
Add FromConfiguration to SecuritySettings
hishamco May 13, 2022
8ddb49e
No need to add policies with None origin
hishamco May 13, 2022
13836a9
Fix policies localized strings
hishamco May 13, 2022
b1a18de
Add link to mkdocs.ml
hishamco May 13, 2022
35dda92
Fix configuration docs path
hishamco May 14, 2022
e89a245
Rename unit tests
hishamco May 14, 2022
d9b6f57
Use dictionary for permissions policy
hishamco May 15, 2022
2b1ffd8
Apply Jean-Thierry change
hishamco May 16, 2022
26b2580
Fix tests
hishamco May 16, 2022
c51c6c8
Edit view fixes
jtkech May 17, 2022
1f4ff2a
Remove overrideAdminSettings parameter
hishamco May 17, 2022
43b78a9
Fixes Settings merging from both Configuration and Site settings
jtkech May 17, 2022
2d437e7
Fix docs to react to the dictionary changes
hishamco May 17, 2022
5bc5425
Rename unit tests
hishamco May 17, 2022
fa9eed7
Address feedback
hishamco May 17, 2022
9072844
Add docs for InitializePolicy
hishamco May 17, 2022
91dd721
Add missing ouble quotations
hishamco May 17, 2022
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
7 changes: 7 additions & 0 deletions OrchardCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OrchardCore.Themes", "Orcha
src\OrchardCore.Themes\Directory.Build.targets = src\OrchardCore.Themes\Directory.Build.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.Security", "src\OrchardCore.Modules\OrchardCore.Security\OrchardCore.Security.csproj", "{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}"
hishamco marked this conversation as resolved.
Show resolved Hide resolved
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1139,6 +1141,10 @@ Global
{D00CF459-396D-49C9-92E2-3FD3C2A59847}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D00CF459-396D-49C9-92E2-3FD3C2A59847}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D00CF459-396D-49C9-92E2-3FD3C2A59847}.Release|Any CPU.Build.0 = Release|Any CPU
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1337,6 +1343,7 @@ Global
{2BC850C3-9846-47E1-9068-AC0A8E5537AC} = {184139CF-C4AB-4FBE-AE19-54C8B3FE5C5E}
{85EF279B-8F35-476D-9BBD-F503F20712B5} = {184139CF-C4AB-4FBE-AE19-54C8B3FE5C5E}
{21F459C1-494E-41C9-B221-6C102774A47F} = {184139CF-C4AB-4FBE-AE19-54C8B3FE5C5E}
{B02C00A7-33C2-4FEE-9D0F-B14C349ADB68} = {A066395F-6F73-45DC-B5A6-B4E306110DCE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46A1D25A-78D1-4476-9CBF-25B75E296341}
Expand Down
37 changes: 37 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Security/AdminMenu.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using OrchardCore.Navigation;
using OrchardCore.Security.Drivers;

namespace OrchardCore.Security
{
public class AdminMenu : INavigationProvider
{
private readonly IStringLocalizer S;

public AdminMenu(IStringLocalizer<AdminMenu> localizer)
{
S = localizer;
}

public Task BuildNavigationAsync(string name, NavigationBuilder builder)
{
if (String.Equals(name, "admin", StringComparison.OrdinalIgnoreCase))
{
builder.Add(S["Security"], NavigationConstants.AdminMenuSecurityPosition, security => security
.AddClass("security").Id("security")
.Add(S["Settings"], settings => settings
.Add(S["Security Headers"], S["Security Headers"].PrefixPosition(), headers => headers
.Permission(SecurityPermissions.SecurityHeadersSettings)
.Action("Index", "Admin", new { area = "OrchardCore.Settings", groupId = SecurityHeadersSettingsDisplayDriver.SettingsGroupId })
.LocalNav()
)
)
);
}

return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using OrchardCore.DisplayManagement.Entities;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Environment.Shell;
using OrchardCore.Security.ViewModels;
using OrchardCore.Settings;

namespace OrchardCore.Security.Drivers
{
public class SecurityHeadersSettingsDisplayDriver : SectionDisplayDriver<ISite, SecurityHeadersOptions>
{
internal const string SettingsGroupId = "SecurityHeaders";

private readonly IShellHost _shellHost;
private readonly ShellSettings _shellSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;

public SecurityHeadersSettingsDisplayDriver(
IShellHost shellHost,
ShellSettings shellSettings,
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService)
{
_shellHost = shellHost;
_shellSettings = shellSettings;
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
}

public override async Task<IDisplayResult> EditAsync(SecurityHeadersOptions settings, BuildEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;

if (!await _authorizationService.AuthorizeAsync(user, SecurityPermissions.SecurityHeadersSettings))
{
return null;
}

return Initialize<SecurityHeadersOptions>("SecurityHeadersSettings_Edit", model =>
{
model.ReferrerPolicy = settings.ReferrerPolicy;
}).Location("Content:2").OnGroup(SettingsGroupId);
}

public override async Task<IDisplayResult> UpdateAsync(SecurityHeadersOptions section, BuildEditorContext context)
{
var user = _httpContextAccessor.HttpContext?.User;

if (!await _authorizationService.AuthorizeAsync(user, SecurityPermissions.SecurityHeadersSettings))
{
return null;
}

if (context.GroupId == SettingsGroupId)
{
var model = new SecurityHeadersOptions();

await context.Updater.TryUpdateModelAsync(model, Prefix);

section.ReferrerPolicy = model.ReferrerPolicy;

if (context.Updater.ModelState.IsValid)
{
await _shellHost.ReleaseShellContextAsync(_shellSettings);
}
}

return await EditAsync(section, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using OrchardCore.Security;
using OrchardCore.Security.Middlewares;

namespace Microsoft.AspNetCore.Builder
{
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseReferrerPolicy(this IApplicationBuilder app, string policyOption)
{
if (app is null)
{
throw new ArgumentNullException(nameof(app));
}

if (String.IsNullOrEmpty(policyOption))
{
throw new ArgumentException($"'{nameof(policyOption)}' cannot be null or empty.", nameof(policyOption));
}

return app.UseMiddleware<ReferrerPolicyMiddleware>(policyOption);
}

public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app)
{
if (app is null)
{
throw new ArgumentNullException(nameof(app));
}
hishamco marked this conversation as resolved.
Show resolved Hide resolved

return app.UseSecurityHeaders(config => { });
}

public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app, Action<SecurityHeadersBuilder> configureSecurityHeaders)
{
if (app is null)
{
throw new ArgumentNullException(nameof(app));
}

if (configureSecurityHeaders is null)
{
throw new ArgumentNullException(nameof(configureSecurityHeaders));
}

var options = new SecurityHeadersOptions();
var builder = new SecurityHeadersBuilder(options);

configureSecurityHeaders.Invoke(builder);

options = builder.Build();

return app.UseReferrerPolicy(options.ReferrerPolicy);
}
}
}
11 changes: 11 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Security/Manifest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using OrchardCore.Modules.Manifest;

[assembly: Module(
Name = "Security",
Author = ManifestConstants.OrchardCoreTeam,
Website = ManifestConstants.OrchardCoreWebsite,
Version = ManifestConstants.OrchardCoreVersion,
Description = "The security module adds the required security headers for the best practices.",
Category = "Security",
IsAlwaysEnabled = true
hishamco marked this conversation as resolved.
Show resolved Hide resolved
)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace OrchardCore.Security.Middlewares
{
public class ReferrerPolicyMiddleware
{
private readonly string _options;
private readonly RequestDelegate _next;

public ReferrerPolicyMiddleware(string options, RequestDelegate next)
{
_options = options;
_next = next ?? throw new ArgumentNullException(nameof(next));
}

public Task Invoke(HttpContext context)
{
context.Response.Headers[SecurityHeader.ReferrerPolicy] = _options;

return _next.Invoke(context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<!-- NuGet properties-->
<Title>OrchardCore Security</Title>
<Description>
$(OCCMSDescription)

The security module adds the required security headers for the best practices.
hishamco marked this conversation as resolved.
Show resolved Hide resolved
</Description>
<PackageTags>$(PackageTags) OrchardCoreCMS</PackageTags>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\OrchardCore\OrchardCore.DisplayManagement\OrchardCore.DisplayManagement.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Module.Targets\OrchardCore.Module.Targets.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Navigation.Core\OrchardCore.Navigation.Core.csproj" />
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Security/Permissions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OrchardCore.Security.Permissions;

namespace OrchardCore.Security
{
public class SecurityPermissions : IPermissionProvider
{
public static readonly Permission SecurityHeadersSettings = new("SecurityHeadersSettings", "Manage Security Headers Settings");

public Task<IEnumerable<Permission>> GetPermissionsAsync()
=> Task.FromResult(new[] { SecurityHeadersSettings }.AsEnumerable());

public IEnumerable<PermissionStereotype> GetDefaultStereotypes()
=> new[]
{
new PermissionStereotype
{
Name = "Administrator",
Permissions = new[] { SecurityHeadersSettings }
},
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace OrchardCore.Security
{
public static class ReferrerPolicyOptions
{
public static readonly string NoReferrer = "no-referrer";

public static readonly string NoReferrerWhenDowngrade = "no-referrer-when-downgrade";

public static readonly string Origin = "origin";

public static readonly string OriginWhenCrossOrigin = "origin-when-cross-origin";

public static readonly string SameOrigin = "same-origin";

public static readonly string StrictOrigin = "strict-origin";

public static readonly string StrictOriginWhenCrossOrigin = "strict-origin-when-cross-origin";

public static readonly string UnsafeUrl = "unsafe-url";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace OrchardCore.Security
{
public static class SecurityHeader
{
public const string ReferrerPolicy = "Referrer-Policy";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace OrchardCore.Security
{
public static class SecurityHeaderDefaults
{
// TODO: Set the default security headers values
hishamco marked this conversation as resolved.
Show resolved Hide resolved
public static readonly string ReferrerPolicy = ReferrerPolicyOptions.NoReferrer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;

namespace OrchardCore.Security
{
public class SecurityHeadersBuilder
{
private readonly SecurityHeadersOptions _options;

private string _referrerPolicy;

public SecurityHeadersBuilder(SecurityHeadersOptions options)
{
_options = options;
}

public SecurityHeadersBuilder AddReferrerPolicy(string policyOption)
{
if (String.IsNullOrEmpty(policyOption))
{
throw new ArgumentException($"'{nameof(policyOption)}' cannot be null or empty.", nameof(policyOption));
}

_referrerPolicy = policyOption;

return this;
}

public SecurityHeadersOptions Build()
{
_options.ReferrerPolicy = _referrerPolicy;

return _options;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace OrchardCore.Security
{
public class SecurityHeadersOptions
{
public string ReferrerPolicy { get; set; } = SecurityHeaderDefaults.ReferrerPolicy;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.Extensions.Options;

namespace OrchardCore.Security.Services
{
public class SecurityHeadersOptionsConfiguration : IConfigureOptions<SecurityHeadersOptions>
{
private readonly SecurityHeadersService _securityHeadersService;

public SecurityHeadersOptionsConfiguration(SecurityHeadersService securityHeadersService)
{
_securityHeadersService = securityHeadersService;
}

public void Configure(SecurityHeadersOptions options)
{
var securityHeadersSettings = _securityHeadersService.GetSettingsAsync()
.GetAwaiter().GetResult();

options.ReferrerPolicy = securityHeadersSettings.ReferrerPolicy;
}
}
}
Loading