Introducing Jhoose Security – A module to manage your Content Security Policy

It has always been difficult to manage the CSP on a website, this new module for Optimizley aims to make the process easier giving control back to advanced editors.

Features

  • Interface to manage policies.
  • Global ‘report only’ mode, or specify for each policy.
  • Add ‘nonce’ to inline script or style tags.
  • Ability to specify paths that are excluded from outputting the policy header.

Administration

Once the module is installed you will see a new ‘Security’ menu item within the top menu.

Settings

This screen gives you access to the global settings of the module, allowing the module to be enabled/disabled or switched into ‘Report Only’ mode.

It is also possible to specify an endpoint for a reporting service.

Module Settings

View Policies

All security policies are listed, with a summary of the policy configuration. A user is then able to click on a policy to view the policy in greater detail or amend it as required.

List of all policies

Edit Policy

This screen allows for an individual policy to be managed by the user, these will be saved when the ‘OK’ button is pressed.

When changes are made it is recommended that they are tested in ‘Report Only’ mode to ensure that nothing is adversely impacted by the new configuration.

Edit individual policy

Installation

Install the package directly from the Optimizley Nuget repository. This will install the admin interface along with the middleware to add the CSP header to the response.

Github: https://github.com/andrewmarkham/contentsecuritypolicy

dotnet add package Jhoose.Security.Admin

Configuration

Startup.cs

services.AddJhooseSecurity(IConfiguration configuration, Action<SecurityOptions> options = null);

The Action<SecurityOptions> options is optional and if not specified then the default will be used.

  "JhooseSecurity": {
    "ExclusionPaths": [
      "/episerver"
    ]
  }

ExclusionPaths: Any request which starts with a path specified in this property will not include the CSP header.

app.UseJhooseSecurity();

Nonce Tag Helper

It is possible to get a nonce added to your inline <script> and <style> tags.

_ViewImports.cshtml

@addTagHelper *, Jhoose.Security.Core
<script nonce src="/assets/js/jquery.min.js"></script>

Managing the ‘Content Security Policy’ of your site

Introduction

The ‘Content Security Policy’ response header is used to enhance a website’s security. It allows control of how resources are loaded, whitelisting trusted domains.

Any security audit will highlight this as a key recommendation.

Content-Security-Policy: default-src 'self' ; script-src https://www.google-analytics.com https://ssl.google-analytics.com; img-src https://www.google-analytics.com; connect-src https://www.google-analytics.com;

Read More: https://content-security-policy.com/

Why

Content Security Policy is an extra layer of security that protects against certain types of attack vectors (XSS, Click Jacking), anything you can do to protect the end-user is beneficial.

It is common practice these days for developers to draw in 3rd party packages to deliver a feature and these packages will also draw in other packages. This means that malicious code can get drawn into your solution without your knowledge.

Read More : https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610

How

It is possible to be very granular when configuring the security policy. This gives you a high level of control of what can be loaded and where data can be sent.

Although the Content Security Policy can be added at a meta tag, it is typically delivered in a response header, additionally, it is also possible to enable specific inline scripts by adding a nonce value.

Testing your policy

Changes to the policy can inadvertently disable features on your site. YouTube videos can fail to load, analytics stops sending data, javascript components can fail. To help mitigate this you should either make any changes as ‘report only’ and monitor the output or configure the policy to report issues. This means that any issues are either reported within the browser console or are sent to a 3rd party reporting service.

Challenges

You have deployed your policy, it’s been tested and signed off, but then things start to fail. Unfortunately, things do change, a dependency can update the endpoints they use or more typically a new tool is added via Google Tag Manager.

Managing the content security policy can be challenging. A common approach is to create a Url Rewrite outbound rule. This means that when the policy needs to change you have to update your source code and redeploy your site, not very efficient.

Is there an easier way?

Read my next post where I will introduce the new Episerver / Optimizely module that simplifies the management of the Content Security Policy.

Next Post: http://jhoose.co.uk/2021/11/01/introducing-jhoose-security-a-module-to-manage-your-content-security-policy/

Clone images from your production website using an ImageResizer Plugin

Introduction

My requirement arose because I had updated the development environment to use a recent copy of production and due to various reasons it wasn’t feasible to get a copy of the images.

It is assumed that you are already using ImageResizer, and the EPiServerBlobReaderPlugin in your site.

Solution

As my solution used ImageResizer I wanted to see whether I could create a plugin that would allow me to download an asset if it was missing. It turns out that there is an ImageMissing event that you can attach to and this was perfect for my needs.

        public IPlugin Install(Config config)
        {
            config.Plugins.add_plugin(this);
            config.Pipeline.ImageMissing += Pipeline_ImageMissing;
            return this;
        }

        private void Pipeline_ImageMissing(IHttpModule sender, HttpContext context, IUrlEventArgs e)
        {

        }

This event allowed me to detect when an image is missing and to download and store in the BlobStorage.

        private void Pipeline_ImageMissing(IHttpModule sender, HttpContext context, IUrlEventArgs e)
        {
            var productionFile = $"{hostUrl}{e.VirtualPath}";
            var blobImage = GetBlobFile(e.VirtualPath, e.QueryString);

            using (var c = new HttpClient())
            {
                var fileStream = c.GetStreamAsync(productionFile).Result;

                 fileStream.CopyTo(blobImage.Blob.OpenWrite());
            }
        }

        private EPiServerBlobFile GetBlobFile(string virtualPath, NameValueCollection queryString)
        {
            var blobFile = new EPiServerBlobFile(virtualPath, queryString);

            return blobFile;
        }

This worked well until I change the configuration of the solution to store the assets within an Azure Storage Container.

It turns out that Episerver/Optimizely didn’t detect that the image was missing, I just saw a lot of 404 error messages when attempting to get the asset from Azure. To handle this, I had to develop another plugin that correctly checks the Azure Storage and then triggers the the ImageMissing Event.

        protected virtual CloudBlobContainer GetContainer()
        {
            return CloudStorageAccount.Parse(this.connectionString).CreateCloudBlobClient()
                .GetContainerReference(this.container);
        }

        public bool FileExists(string virtualPath, NameValueCollection queryString)
        {
            bool fileExists;

            try
            {
                var blobFile = new EPiServerBlobFile(virtualPath, queryString);

                if (blobFile.Blob is AzureBlob)
                {
                    var cloudBlobContainer = this.GetContainer();

                    fileExists = cloudBlobContainer.GetBlobReference(blobFile.Blob.ID.PathAndQuery).Exists();

                }
                else
                {
                    fileExists = blobFile.BlobExists;
                }
            }
            catch
            {
                fileExists = false;
            }

            return fileExists;
        }

The code above CloudBlobContainer and uses this to check if the file exists, if not then the ImageMissing event is fired to trigger the downloading of the asset and storing in the Azure Storage Blob.

Configuration

    <plugins>
      <add name="EPiServerAzureBlobReaderPlugin" />
      <add name="PatchImagePlugin" azureMode="true|false" hostUrl="https://source.of.images"/>
    </plugins>

You will need to replace the existing plugins with the new ones you have created. You should also use the full namespace rather than just the class name.

  • azureMode (true|false) = set to true when you are writing the assets to Azure Blob storage.
  • hostUrl = this is the hostname (including protocol) of the site you want to download the images from.

Conclusion

You can access the full example code from https://gist.github.com/andrewmarkham/473d36fb60229326a11ce32c1fcc9f14

I see this as a useful option to have when developing, especially when transferring the assets between environments is challenging.

I hope that the example proves useful, even if it demonstrates how to write a simple Plugin for ImageResizer.