How to apply Content-Security-Policy (CSP) headers using Azure Front Door

How to apply Content-Security-Policy (CSP) headers using Azure Front Door

Content-Security-Policy (CSP) is a security standard that helps prevent cross-site scripting (XSS) and other code injection attacks by allowing website owners to specify which content sources can be trusted and loaded by their web pages. The CSP policy is defined in an HTTP header sent from the server to the client's web browser, and it can include directives that restrict the types of content allowed, such as images, scripts, stylesheets, and fonts.

Let's have a look at the most basic implementation using ASP.NET middleware.

app.Use(async (context, next) => {
    context.Response.Headers.Add("Content-Security-Policy",
        "script-src 'self' cdn.jsdelivr.net;" +
        "style-src 'self' fonts.googleapis.com cdn.jsdelivr.net;" +
        "font-src 'self' fonts.gstatic.com;"
    );

    await next();
});

This implementation is simple, but it might be difficult to read and maintain when the amount of elements is increased.

To solve this problem we can use NetEscapades.AspNetCore.SecurityHeaders package. The package is quite useful and can help to work with different security headers, not only with CSP. The code above can be transformed.

var policyCollection = new HeaderPolicyCollection()
    .AddContentSecurityPolicy(builder =>
    {
        builder.AddScriptSrc()
            .Self()
            .From("cdn.jsdelivr.net");

        builder.AddStyleSrc()
            .Self()
            .From("fonts.googleapis.com")
            .From("cdn.jsdelivr.net");

        builder.AddFontSrc()
            .Self()
            .From("fonts.gstatic.com");
    });

app.UseSecurityHeaders(policyCollection);

This code is better to read and most likely it is what you need in 99% of cases. The only problem here is we need to change the application code every time we need to modify headers. What goes with running the deployment pipeline and provisioning new updates just because of changes in a configuration. Usually, it is not a problem due to we don't need to perform such kinds of changes every week or month.

If in some cases you don't want to work with headers in the application code they can be applied through Web Application Firewall (WAF). There are both individual and part of cloud providers' services. I will show how to use WAF which is a part of Azure Front Door.

We have an assumption that Front Door is provisioned and all necessary configuration has been applied. If not please find more information on how to do it here.

Open the Front Door profile and go to the menu "Settings" -> "Rule sets".

On Rule sets page, click Add button to add new rule sets.

The rule sets configuration page allows to add of various rules to customize HTTP requests. Let's add our rules for Content Security Policy. For that, we need to click Add Rule button.

Enter the descriptive name something like cspRule.

We skip adding conditions because we would like to apply CSP headers for each request.

The next step is to add an action to modify the response header. Click Add an action button and choose Modify response header from the menu.

Because we would like to set up CSP headers only through Frontdoor, it is better to take precautionary steps and clear all possible headers which might be set on the backend side. To do so we need to choose the Delete operator and as a header name enter 'Content-Security-Policy'.

After that add 3 other Append actions with header name 'Content-Security-Policy' and the following header values:

script-src 'self' cdn.jsdelivr.net;

style-src 'self' fonts.googleapis.com cdn.jsdelivr.net;

font-src 'self' fonts.gstatic.com;

Click Save button.

Right now we have the ruleset with the rule to modify CSP headers what we need to do is to apply this ruleset to the route. To do so open Front Door manager and choose the route.

There is a section "Rules" at the bottom of the page. Choose the earlier created ruleset and click Update button.

After all these steps Front Door will take care of applying the necessary Content Security Policy headers.

If you prefer to use IaC instead of working with UI there is the Bicep script for these rules.

resource frontdoor 'Microsoft.Cdn/profiles@2022-11-01-preview' = {
  name: 'sitefrontdoor'
  // The rest of configuration omitted
}

resource siteEndpoint 'Microsoft.Cdn/profiles/afdEndpoints@2021-06-01' = {
  parent: frontdoor
  name: 'siteEndpoint'
  // The rest of configuration omitted
}

resource cspRuleset 'Microsoft.Cdn/profiles/ruleSets@2022-11-01-preview' = {
  name: 'cspRuleset'
  parent: frontdoor
}

resource cspRule 'Microsoft.Cdn/profiles/ruleSets/rules@2022-11-01-preview' = {
  name: 'cspRule'
  parent: cspRuleset
  properties: {
    order: 1
    // We would like to apply this rule to all requests thus there are no conditions
    conditions: []
    actions: [
      // As a first action delete all CSP headers that might have set on backend site.
      // It is mostly precautions to make sure that we set headers only through Frontdoor.
      {
        name: 'ModifyResponseHeader'
        parameters: {
          headerAction: 'Delete'
          headerName: 'Content-Security-Policy'
          typeName: 'DeliveryRuleHeaderActionParameters'
        }
      }
      {
        name: 'ModifyResponseHeader'
        parameters: {
          headerAction: 'Append'
          headerName: 'Content-Security-Policy'
          typeName: 'DeliveryRuleHeaderActionParameters'
          value: 'script-src \'self\' cdn.jsdelivr.net;'
        }
      }
      {
        name: 'ModifyResponseHeader'
        parameters: {
          headerAction: 'Append'
          headerName: 'Content-Security-Policy'
          typeName: 'DeliveryRuleHeaderActionParameters'
          value: 'style-src \'self\' fonts.googleapis.com cdn.jsdelivr.net;'
        }
      }
      {
        name: 'ModifyResponseHeader'
        parameters: {
          headerAction: 'Append'
          headerName: 'Content-Security-Policy'
          typeName: 'DeliveryRuleHeaderActionParameters'
          value: 'font-src \'self\' fonts.gstatic.com;'
        }
      }
    ]
  }
}

resource siteRoute 'Microsoft.Cdn/profiles/afdendpoints/routes@2021-06-01' = {
  parent: siteEndpoint
  name: 'siteRoute'
  properties: {
    ruleSets: [
      {
        id: cspRuleset.id
      }
    ]
    // The rest of configuration omitted
  }
}

Image credits: "right arrow sign on wall" by Nik