Skip to content

Commit da3808a

Browse files
Add Content Security Policy headers for MvpSite and Sugcon2024 (#589)
* Add Content Security Policy headers for MvpSite and Sugcon2024 - Add CSP middleware to MvpSite ASP.NET Core app with allowlisted domains for Bootstrap, jQuery, Font Awesome, Google Analytics, Moosend, Sitecore Edge, and Content Hub CDNs - Add CSP headers to Sugcon2024 Next.js config with allowlisted domains for Google Analytics, Google Fonts, Font Awesome, YouTube embeds, and Sitecore Edge/XM Cloud - Add supplementary security headers: X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy for both sites * Moved to IApplicationBuilder extensions --------- Co-authored-by: Ivan Lieckens <ivan.lieckens@sitecore.com>
1 parent f2a82b5 commit da3808a

5 files changed

Lines changed: 131 additions & 4 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,7 @@ packages/
5252
xmcloud.plugin.pre-release.json
5353

5454
# developer configs
55-
appsettings.Development.json
55+
appsettings.Development.json
56+
57+
# Mac Files
58+
*DS_Store

headapps/MvpSite/MvpSite.Rendering/Extensions/ApplicationBuilderExtensions.cs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using MvpSite.Rendering.Middleware;
1+
using System.Text;
2+
using Microsoft.Extensions.Primitives;
3+
using MvpSite.Rendering.Middleware;
24

35
namespace MvpSite.Rendering.Extensions;
46

@@ -21,4 +23,70 @@ public static IApplicationBuilder UseMvpProfileRouting(this IApplicationBuilder
2123
app.UseMiddleware<MvpProfileRoutingMiddleware>();
2224
return app;
2325
}
26+
27+
public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app)
28+
{
29+
IApplicationBuilder result = app.Use(static async (context, next) =>
30+
{
31+
StringBuilder cspBuilder = new();
32+
cspBuilder.Append("default-src 'self'; ");
33+
34+
cspBuilder.Append("script-src 'self' 'unsafe-inline' 'unsafe-eval' ");
35+
cspBuilder.Append("https://www.googletagmanager.com ");
36+
cspBuilder.Append("https://www.google-analytics.com ");
37+
cspBuilder.Append("https://cdn.stat-track.com ");
38+
cspBuilder.Append("https://code.jquery.com ");
39+
cspBuilder.Append("https://cdn.jsdelivr.net ");
40+
cspBuilder.Append("https://stackpath.bootstrapcdn.com ");
41+
cspBuilder.Append("https://cdnjs.cloudflare.com ");
42+
cspBuilder.Append("https://www.w3.org ");
43+
cspBuilder.Append("https://edge.sitecorecloud.io; ");
44+
45+
cspBuilder.Append("style-src 'self' 'unsafe-inline' ");
46+
cspBuilder.Append("https://stackpath.bootstrapcdn.com ");
47+
cspBuilder.Append("https://cdnjs.cloudflare.com ");
48+
cspBuilder.Append("https://fonts.googleapis.com; ");
49+
50+
cspBuilder.Append("img-src 'self' data: ");
51+
cspBuilder.Append("https://www.googletagmanager.com ");
52+
cspBuilder.Append("https://www.google-analytics.com ");
53+
cspBuilder.Append("https://*.sitecorecloud.io ");
54+
cspBuilder.Append("https://delivery-sitecore.sitecorecontenthub.cloud; ");
55+
56+
cspBuilder.Append("font-src 'self' ");
57+
cspBuilder.Append("https://fonts.gstatic.com ");
58+
cspBuilder.Append("https://cdnjs.cloudflare.com; ");
59+
60+
cspBuilder.Append("connect-src 'self' ");
61+
cspBuilder.Append("https://www.google-analytics.com ");
62+
cspBuilder.Append("https://www.googletagmanager.com ");
63+
cspBuilder.Append("https://cdn.stat-track.com ");
64+
cspBuilder.Append("https://edge.sitecorecloud.io ");
65+
cspBuilder.Append("https://*.sitecorecloud.io; ");
66+
67+
cspBuilder.Append("frame-src 'self'; ");
68+
69+
cspBuilder.Append("frame-ancestors 'self' ");
70+
cspBuilder.Append("https://*.sitecorecloud.io; ");
71+
72+
cspBuilder.Append("base-uri 'self'; ");
73+
cspBuilder.Append("form-action 'self'; ");
74+
cspBuilder.Append("object-src 'none'");
75+
76+
context.Response.Headers.ContentSecurityPolicy = new StringValues(cspBuilder.ToString());
77+
78+
// ReSharper disable once StringLiteralTypo - specific value for X-Content-Type-Options header
79+
context.Response.Headers.XContentTypeOptions = new StringValues("nosniff");
80+
81+
context.Response.Headers.XFrameOptions = new StringValues("SAMEORIGIN");
82+
83+
context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
84+
85+
context.Response.Headers.Append("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
86+
87+
await next(context);
88+
});
89+
90+
return result;
91+
}
2492
}

headapps/MvpSite/MvpSite.Rendering/Program.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
// Values can originate in appsettings.json, from environment variables, and more.
2020
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/
2121
MvpSiteSettings? sitecoreSettings = builder.Configuration.GetSection(MvpSiteSettings.Key).Get<MvpSiteSettings>();
22-
PagesOptions? pagesSettings = builder.Configuration.GetSection(PagesOptions.Key).Get<PagesOptions>() ?? new PagesOptions();
22+
PagesOptions pagesSettings = builder.Configuration.GetSection(PagesOptions.Key).Get<PagesOptions>() ?? new PagesOptions();
2323
ArgumentNullException.ThrowIfNull(sitecoreSettings);
2424

2525
if (string.IsNullOrWhiteSpace(sitecoreSettings.EdgeContextId))
@@ -101,17 +101,23 @@
101101
// Configure the HTTP request pipeline
102102
// When running behind HTTPS termination, set the request scheme according to forwarded protocol headers.
103103
// Also set the Request IP, so that it can be passed on to the Sitecore Layout Service for tracking and personalization.
104-
app.UseForwardedHeaders(new ForwardedHeadersOptions()
104+
app.UseForwardedHeaders(new ForwardedHeadersOptions
105105
{
106106
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
107107

108+
// ReSharper disable RedundantEmptyObjectOrCollectionInitializer - KnownNetworks and KnownProxies isn't empty by default
108109
// ReSharper disable once CommentTypo - Actual product name
109110
// Allow forwarding of headers from Traefik in development & NGINX in k8s
110111
KnownNetworks = { },
111112
KnownProxies = { }
113+
114+
// ReSharper restore RedundantEmptyObjectOrCollectionInitializer - KnownNetworks and KnownProxies isn't empty by default
112115
});
113116
app.UseSession();
114117

118+
// Security headers
119+
app.UseSecurityHeaders();
120+
115121
if (!app.Environment.IsDevelopment())
116122
{
117123
app.UseExceptionHandler("/Error");
@@ -172,11 +178,14 @@
172178
"error",
173179
new { controller = "Default", action = "Error" });
174180

181+
// ReSharper disable StringLiteralTypo - is a well-known name for a health check endpoint
175182
app.MapControllerRoute(
176183
"healthz",
177184
"healthz",
178185
new { controller = "Default", action = "Healthz" });
179186

187+
// ReSharper restore StringLiteralTypo - is a well-known name for a health check endpoint
188+
180189
// Map Okta sign-in route.
181190
app.MapOktaSigninRoute();
182191

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4f433b8_002Dabcd_002D4e55_002Da08f_002D82e78cef0f0c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_CONSTANT" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=sitecorecloud/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

headapps/Sugcon2024/next.config.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,49 @@ const nextConfig: NextConfig = {
1212
// Disable the X-Powered-By header. Follows security best practices.
1313
poweredByHeader: false,
1414

15+
// Security headers including Content Security Policy
16+
headers: async () => {
17+
return [
18+
{
19+
source: '/(.*)',
20+
headers: [
21+
{
22+
key: 'Content-Security-Policy',
23+
value: [
24+
"default-src 'self'",
25+
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://edge.sitecorecloud.io",
26+
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdnjs.cloudflare.com",
27+
"img-src 'self' data: blob: https://edge.sitecorecloud.io https://*.sitecorecloud.io https://*.sugcon.events https://www.googletagmanager.com https://www.google-analytics.com",
28+
"font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com",
29+
"connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com https://edge.sitecorecloud.io https://edge-platform.sitecorecloud.io https://*.sitecorecloud.io",
30+
"frame-src 'self' https://www.youtube.com",
31+
"frame-ancestors 'self' https://*.sitecorecloud.io https://pages.sitecorecloud.io",
32+
"base-uri 'self'",
33+
"form-action 'self'",
34+
"object-src 'none'",
35+
].join('; '),
36+
},
37+
{
38+
key: 'X-Content-Type-Options',
39+
value: 'nosniff',
40+
},
41+
{
42+
key: 'X-Frame-Options',
43+
value: 'SAMEORIGIN',
44+
},
45+
{
46+
key: 'Referrer-Policy',
47+
value: 'strict-origin-when-cross-origin',
48+
},
49+
{
50+
key: 'Permissions-Policy',
51+
value: 'camera=(), microphone=(), geolocation=()',
52+
},
53+
],
54+
},
55+
];
56+
},
57+
1558
// use this configuration to ensure that only images from the whitelisted domains
1659
// can be served from the Next.js Image Optimization API
1760
// see https://nextjs.org/docs/app/api-reference/components/image#remotepatterns
@@ -27,6 +70,7 @@ const nextConfig: NextConfig = {
2770
hostname: 'xmc-*.**',
2871
port: '',
2972
},
73+
3074
{
3175
protocol: 'https',
3276
hostname: '**.sugcon.events',

0 commit comments

Comments
 (0)