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

NEST-501: Extending CSP providers with X (Twitter) and more CDN configuration #273

Merged
merged 11 commits into from
Jul 18, 2024
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>