diff --git a/docs/en/UI/AspNetCore/Security-Headers.md b/docs/en/UI/AspNetCore/Security-Headers.md index 8322644b1d..f683a9c332 100644 --- a/docs/en/UI/AspNetCore/Security-Headers.md +++ b/docs/en/UI/AspNetCore/Security-Headers.md @@ -19,13 +19,31 @@ ABP Framework allows you to add frequently used security headers into your appli Configure(options => { options.UseContentSecurityPolicyHeader = true; //false by default - options.ContentSecurityPolicyValue = "object-src 'none'; form-action 'self'; frame-ancestors 'none'"; + options.ContentSecurityPolicyValues["object-src"] = new string[] { "'none'" }; + options.ContentSecurityPolicyValues["form-action"] = new string[] { "'self'" }; + options.ContentSecurityPolicyValues["frame-ancestors"] = new string[] { "'self'" }; + options.ContentSecurityPolicyValues["script-src"] = new string[] { "'self'", "'unsafe-inline'", "'unsafe-eval'" }; + + //adding script-src nonce + options.UseContentSecurityPolicyScriptNonce = true; //false by default + + //ignore script nonce source for these paths + options.IgnoredScriptNoncePaths.Add("/my-page"); + + //ignore script nonce by Elsa Workflows and other selectors + options.IgnoredScriptNonceSelectors.Add(context => + { + var endpoint = context.GetEndpoint(); + return Task.FromResult(endpoint?.Metadata.GetMetadata()?.RouteTemplate == "/{YOURHOSTPAGE}"); + }); //adding additional security headers options.Headers["Referrer-Policy"] = "no-referrer"; }); ``` +> Using the script nonce feature will automatically add the nonce value to your script tags. There is no need to add it manually. However, if you still need to add it manually, you can use 'Html.GetScriptNonce()' to add the nonce value or 'Html.GetScriptNonceAttribute()' to add the nonce attribute value. + > If the header is the same, the additional security headers you defined take precedence over the default security headers. In other words, it overrides the default security headers' values. ## Security Headers Middleware diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs index 8715dd2f29..260f28f812 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs @@ -49,6 +49,9 @@ public class AbpTagHelperScriptService : AbpTagHelperResourceService var deferText = (defer || Options.DeferScriptsByDefault || Options.DeferScripts.Any(x => file.StartsWith(x, StringComparison.OrdinalIgnoreCase))) ? "defer" : string.Empty; - output.Content.AppendHtml($"{Environment.NewLine}"); + var nonceText = (viewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString)) + ? $"nonce=\"{nonceString}\"" + : string.Empty; + output.Content.AppendHtml($"{Environment.NewLine}"); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs index 5869699f1e..0f68433f4c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs @@ -8,19 +8,23 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Security; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers; public class AbpTagHelperStyleService : AbpTagHelperResourceService { + protected AbpSecurityHeadersOptions SecurityHeadersOptions; public AbpTagHelperStyleService( IBundleManager bundleManager, IOptions options, - IWebHostEnvironment hostingEnvironment) : base( + IWebHostEnvironment hostingEnvironment, + IOptions securityHeadersOptions) : base( bundleManager, options, hostingEnvironment) { + SecurityHeadersOptions = securityHeadersOptions.Value; } protected override void CreateBundle(string bundleName, List bundleItems) @@ -48,7 +52,9 @@ public class AbpTagHelperStyleService : AbpTagHelperResourceService if (preload || Options.PreloadStylesByDefault || Options.PreloadStyles.Any(x => file.StartsWith(x, StringComparison.OrdinalIgnoreCase))) { - output.Content.AppendHtml($"{Environment.NewLine}"); + output.Content.AppendHtml(SecurityHeadersOptions.UseContentSecurityPolicyScriptNonce + ? $"{Environment.NewLine}" + : $"{Environment.NewLine}"); } else { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/ScriptNonceTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/ScriptNonceTagHelper.cs new file mode 100644 index 0000000000..6c29c81073 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/ScriptNonceTagHelper.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers; + +[HtmlTargetElement("script")] +[HtmlTargetElement("body")] +public class ScriptNonceTagHelper : AbpTagHelper +{ + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (ViewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString)) + { + output.Attributes.Add("nonce", nonceString); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs index 2127b3f43a..2ac299959a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs @@ -35,6 +35,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; )] public class SharedThemeGlobalScriptContributor : BundleContributor { + public override void ConfigureBundle(BundleConfigurationContext context) { context.Files.AddRange(new[] @@ -48,6 +49,6 @@ public class SharedThemeGlobalScriptContributor : BundleContributor "/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/sweetalert2/abp-sweetalert2.js", "/libs/abp/aspnetcore-mvc-ui-theme-shared/toastr/abp-toastr.js" - }); + }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js index a9625d1f1f..b30e816c1d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js @@ -753,6 +753,10 @@ }); } + abp.dom.initializers.initializeAbpCspStyles = function ($abpCspStyles){ + $abpCspStyles.attr("rel", "stylesheet"); + } + abp.dom.onNodeAdded(function (args) { abp.dom.initializers.initializeToolTips(args.$el.findWithSelf('[data-toggle="tooltip"]')); abp.dom.initializers.initializePopovers(args.$el.findWithSelf('[data-toggle="popover"]')); @@ -760,6 +764,7 @@ abp.dom.initializers.initializeForms(args.$el.findWithSelf('form'), true); abp.dom.initializers.initializeScript(args.$el); abp.dom.initializers.initializeAutocompleteSelects(args.$el.findWithSelf('.auto-complete-select')); + abp.dom.initializers.initializeAbpCspStyles($("link[abp-csp-style]")); abp.dom.initializers.initializeDateRangePickers(args.$el); }); @@ -783,6 +788,7 @@ abp.dom.initializers.initializeForms($('form')); abp.dom.initializers.initializeAutocompleteSelects($('.auto-complete-select')); $('[data-auto-focus="true"]').first().findWithSelf('input,select').focus(); + abp.dom.initializers.initializeAbpCspStyles($("link[abp-csp-style]")); }); })(jQuery); diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs index dff987b6b3..134889324e 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreConsts.cs @@ -4,4 +4,5 @@ public static class AbpAspNetCoreConsts { public const string DefaultApiPrefix = "api"; public const string DefaultIntegrationServiceApiPrefix = "integration-api"; + public const string ScriptNonceKey = "ScriptNonce"; } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeaderNonceHelper.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeaderNonceHelper.cs new file mode 100644 index 0000000000..64db12f3a7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeaderNonceHelper.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Volo.Abp.AspNetCore.Security; + +public static class AbpSecurityHeaderNonceHelper +{ + public static string GetScriptNonce(this IHtmlHelper htmlHelper) + { + if (htmlHelper.ViewContext.HttpContext.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) && nonce is string nonceString && !string.IsNullOrEmpty(nonceString)) + { + return nonceString; + } + + return string.Empty; + } + + public static IHtmlContent GetScriptNonceAttribute(this IHtmlHelper htmlHelper) + { + var nonce = htmlHelper.GetScriptNonce(); + return nonce == string.Empty ? HtmlString.Empty : new HtmlString($"nonce=\"{nonce}\""); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs index 29e05b9bdc..5e20edff17 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; @@ -11,6 +12,8 @@ namespace Volo.Abp.AspNetCore.Security; public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency { public IOptions Options { get; set; } + protected const string ScriptSrcKey = "script-src"; + protected const string DefaultValue = "object-src 'none'; form-action 'self'; frame-ancestors 'none'"; public AbpSecurityHeadersMiddleware(IOptions options) { @@ -28,20 +31,104 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency /*The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a ,