Skip to content

Commit

Permalink
Merge pull request #273 from Lombiq/issue/NEST-501
Browse files Browse the repository at this point in the history
NEST-501: Extending CSP providers with X (Twitter) and more CDN configuration
  • Loading branch information
wAsnk authored Jul 18, 2024
2 parents c90748b + cbe4097 commit bce6104
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 19 deletions.
1 change: 1 addition & 0 deletions Lombiq.HelpfulLibraries.AspNetCore/Docs/Security.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- `EmbeddedMediaContentSecurityPolicyProvider`: An optional policy provider that permits additional host names used by usual media embedding sources (like YouTube) for the `frame-scr` directive.
- `IContentSecurityPolicyProvider`: Interface for services that update the dictionary that will be turned into the `Content-Security-Policy` header value.
- `ServiceCollectionExtensions`: Extensions methods for `IServiceCollection`, e.g. `AddContentSecurityPolicyProvider()` is a shortcut to register `IContentSecurityPolicyProvider` in dependency injection.
- `XWidgetsContentSecurityPolicyProvider`: An optional content security policy provider that provides configuration to allow the usage of X (Twitter) social widgets.

There is a similar section for security extensions related to Orchard Core [here](../../Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,53 @@ public class CdnContentSecurityPolicyProvider : IContentSecurityPolicyProvider
/// <summary>
/// Gets the sources that will be added to the <see cref="StyleSrc"/> directive.
/// </summary>
public static ConcurrentBag<string> PermittedStyleSources { get; } = new(
public static ConcurrentBag<string> PermittedStyleSources { get; } =
[
"fonts.googleapis.com",
"fonts.gstatic.com", // #spell-check-ignore-line
"cdn.jsdelivr.net", // #spell-check-ignore-line
"fastly.jsdelivr.net", // #spell-check-ignore-line
"cdnjs.cloudflare.com", // #spell-check-ignore-line
"fastly.jsdelivr.net", // #spell-check-ignore-line
"fonts.cdnfonts.com", // #spell-check-ignore-line
"fonts.googleapis.com",
"fonts.gstatic.com", // #spell-check-ignore-line
"maxcdn.bootstrapcdn.com", // #spell-check-ignore-line
]);
"unpkg.com", // #spell-check-ignore-line
];

/// <summary>
/// Gets the sources that will be added to the <see cref="ScriptSrc"/> directive.
/// </summary>
public static ConcurrentBag<string> PermittedScriptSources { get; } = new(
public static ConcurrentBag<string> PermittedScriptSources { get; } =
[
"cdn.jsdelivr.net", // #spell-check-ignore-line
"cdnjs.cloudflare.com", // #spell-check-ignore-line
"code.jquery.com",
"fastly.jsdelivr.net", // #spell-check-ignore-line
"maxcdn.bootstrapcdn.com", // #spell-check-ignore-line
]);
"unpkg.com", // #spell-check-ignore-line
];

/// <summary>
/// Gets the sources that will be added to the <see cref="FontSrc"/> directive.
/// </summary>
public static ConcurrentBag<string> PermittedFontSources { get; } = new(
public static ConcurrentBag<string> PermittedFontSources { get; } =
[
"cdn.jsdelivr.net", // #spell-check-ignore-line
"cdnjs.cloudflare.com", // #spell-check-ignore-line
"fonts.cdnfonts.com", // #spell-check-ignore-line
"fonts.googleapis.com",
"fonts.gstatic.com", // #spell-check-ignore-line
]);
];

/// <summary>
/// Gets the sources that will be added to the <see cref="FrameSrc"/> directive.
/// </summary>
public static ConcurrentBag<string> PermittedFrameSources { get; } = [];

/// <summary>
/// Gets the sources that will be added to the <see cref="ImgSrc"/> directive.
/// </summary>
public static ConcurrentBag<string> PermittedImgSources { get; } = [];

public ValueTask UpdateAsync(IDictionary<string, string> securityPolicies, HttpContext context)
{
var any = false;
Expand Down Expand Up @@ -82,6 +92,12 @@ public ValueTask UpdateAsync(IDictionary<string, string> securityPolicies, HttpC
CspHelper.MergeValues(securityPolicies, FrameSrc, PermittedFrameSources);
}

if (!PermittedImgSources.IsEmpty)
{
any = true;
CspHelper.MergeValues(securityPolicies, ImgSrc, PermittedImgSources);
}

if (any)
{
var allPermittedSources = PermittedStyleSources.Concat(PermittedScriptSources).Concat(PermittedFontSources);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
namespace Lombiq.HelpfulLibraries.AspNetCore.Security;
namespace Lombiq.HelpfulLibraries.AspNetCore.Security;

/// <summary>
/// The <c>Content-Security-Policy</c> directives defined in the <a href="https://www.w3.org/TR/CSP2/#directives">W3C
/// Recommendation</a>.
/// The <c>Content-Security-Policy</c> directives defined in the <see href="https://www.w3.org/TR/CSP2/#directives">W3C
/// Recommendation</see> (also see the <see
/// href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives">MDN page</see>).
/// </summary>
public static class ContentSecurityPolicyDirectives
{
Expand All @@ -15,13 +16,20 @@ public static class ContentSecurityPolicyDirectives
public const string FrameAncestors = "frame-ancestors";
public const string FrameSrc = "frame-src";
public const string ImgSrc = "img-src";
public const string ManifestSrc = "manifest-src";
public const string MediaSrc = "media-src";
public const string ObjectSrc = "object-src";
public const string PluginTypes = "plugin-types";
public const string ReportTo = "report-to";
public const string ReportUri = "report-uri";
public const string Sandbox = "sandbox";
public const string ScriptSrc = "script-src";
public const string ScriptSrcAttr = "script-src-attr";
public const string ScriptSrcElem = "script-src-elem";
public const string StyleSrc = "style-src";
public const string StyleSrcAttr = "style-src-attr";
public const string UpgradeInsecureRequests = "upgrade-insecure-requests";
public const string StyleSrcElem = "style-src-elem";
public const string WorkerSrc = "worker-src";

public static class CommonValues
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using static Lombiq.HelpfulLibraries.AspNetCore.Security.ContentSecurityPolicyDirectives;

namespace Lombiq.HelpfulLibraries.AspNetCore.Security;

/// <summary>
/// An optional content security policy provider that provides configuration to allow the usage of X (Twitter) social
/// widgets.
/// </summary>
public class XWidgetsContentSecurityPolicyProvider : IContentSecurityPolicyProvider
{
private const string PlatformDotTwitter = "platform.twitter.com";

public ValueTask UpdateAsync(IDictionary<string, string> securityPolicies, HttpContext context)
{
CspHelper.MergeValues(securityPolicies, FrameSrc, PlatformDotTwitter);
CspHelper.MergeValues(securityPolicies, ImgSrc, PlatformDotTwitter, "syndication.twitter.com");
CspHelper.MergeValues(securityPolicies, StyleSrc, PlatformDotTwitter);
CspHelper.MergeValues(securityPolicies, ScriptSrc, PlatformDotTwitter);

return ValueTask.CompletedTask;
}
}
10 changes: 6 additions & 4 deletions Lombiq.HelpfulLibraries.OrchardCore/Docs/Security.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Lombiq Helpful Libraries - Orchard Core Libraries - Security

## Extensions

- `SecurityOrchardCoreBuilderExtensions`: Adds `BuilderExtensions` extensions. For example, the `ConfigureSecurityDefaultsWithStaticFiles()` that provides some default security configuration for Orchard Core.

There is a similar section for security extensions related to ASP.NET Core [here](../../Lombiq.HelpfulLibraries.AspNetCore/Docs/Security.md). All of the services mentioned in both documents are included in the `ConfigureSecurityDefaults()` and `ConfigureSecurityDefaultsWithStaticFiles()` extensions.

These extensions provide additional security and can resolve issues reported by the [ZAP security scanner](https://github.com/Lombiq/UI-Testing-Toolbox/blob/dev/Lombiq.Tests.UI/Docs/SecurityScanning.md).

## Extensions

- `SecurityOrchardCoreBuilderExtensions`: Adds `BuilderExtensions` extensions. For example, the `ConfigureSecurityDefaultsWithStaticFiles()` that provides some default security configuration for Orchard Core.

## Attributes

- `ContentSecurityPolicyAttribute`: You can add the `[ContentSecurityPolicy(value, name)]` attribute to any MVC action's method. This way you can grant per-action content security policy permissions, right there in the controller. These attributes are handled by the `ContentSecurityPolicyAttributeContentSecurityPolicyProvider`.
Expand All @@ -19,3 +19,5 @@ These extensions provide additional security and can resolve issues reported by
- `ReCaptchaContentSecurityPolicyProvider`: Provides various directives for the `Content-Security-Policy` header, allowing using ReCaptcha captchas. Is automatically enabled when the `OrchardCore.ReCaptcha` feature is enabled.
- `ResourceManagerContentSecurityPolicyProvider`: An abstract base class for implementing content security policy providers that trigger when the specified resource is included.
- `VueContentSecurityPolicyProvider`: An implementation of `ResourceManagerContentSecurityPolicyProvider` that adds `script-src: unsafe-eval` permission to the page if it uses the `vuejs` resource. This includes any Vue.js app in stock Orchard Core, apps you create in your view files, and SFCs created with the Lombiq.VueJs module. This is necessary, because without `unsafe-eval` Vue.js only supports templates that are pre-compiled into JS code.

You can configure optional or custom content security policy providers by implementing the `IContentSecurityPolicyProvider` interface and registering them in the DI container with `AddContentSecurityPolicyProvider<TProvider>()`, e.g. `services.AddContentSecurityPolicyProvider<XWidgetsContentSecurityPolicyProvider>();` in a `Startup` class. You can also register providers for the whole app (i.e. all tenants) from the root `Program` class via `OrchardCoreBuilder.ApplicationServices`.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ public class GoogleAnalyticsContentSecurityPolicyProvider : IContentSecurityPoli
{
private const string HttpContextItemKey = nameof(GoogleAnalyticsContentSecurityPolicyProvider);

public static bool AlwaysEnabled { get; set; }

public async ValueTask UpdateAsync(IDictionary<string, string> securityPolicies, HttpContext context)
{
var googleAnalyticsIsEnabled = context.Items.ContainsKey(HttpContextItemKey);
var googleAnalyticsIsEnabled = AlwaysEnabled || context.Items.ContainsKey(HttpContextItemKey);

if (!googleAnalyticsIsEnabled)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand All @@ -31,7 +31,7 @@
</ItemGroup>

<ItemGroup>
<AdditionalFiles Include="package.json"/>
<AdditionalFiles Include="package.json" />
</ItemGroup>

</Project>

0 comments on commit bce6104

Please sign in to comment.