How We Made Managing CSP Less Annoying
At REI, the Cybersecurity Engineering teams constantly work hand in hand with our application and infrastructure teams. The outdoor analogy that sometimes comes to mind is that the Cybersecurity Engineering team are the belayers, and the application and infrastructure teams are the climbers. When it comes to securing our applications at REI, we want to ensure that developers can continue to climb to new heights, with our Cybersecurity engineers guiding and assisting along the way.
The Journey to Content Security Policy (CSP)
In the beginning of 2022, both the Cybersecurity Engineering team and the Site Reliability Engineering (SRE) team saw that CSP could help solve several of our questions:
- How can we better prevent potential data skimming attacks?
How Does CSP Work?
Content Security Policy, or CSP for short, declares to the client browser a set of approved sources that can run on a web page. Another way to think about CSP is that it’s a way to make an allow-list of things that your client browser has permissions to load and run.
CSP is implemented using a
Content-Security-Policy HTTP response header, and it can help answer questions like:
- Where can I get images from?
- Where can I get stylesheets from?
- Can the page be embedded in another site’s frame?
CSP is about security, and one of the main reasons why you would want to implement CSP is that it’s meant to detect, reduce, and/or eliminate the risk of threats such as cross-site scripting, packet sniffing, and other data injection attacks.
At a high level, when a web browser goes to www.rei.com, they receive a response from our web application that includes a response header of
Content-Security-Policy along with a value of directives. These directives have many functions and basically gives instructions to the client browser on what can be loaded. If something is not specified in a directive, it will be blocked by the client browser.
There are a lot more details into exactly what each directive does and, to make things more complicated, some browsers may interpret or respect directives differently. As we were writing the policy, we found that the Mozilla Developer Network’s CSP page very helpful.
Things We Learned Along the Way
We also needed a way to collect and analyze the data. By using the
report-uri directive we were able to send any CSP violations to a collection endpoint for further analysis. As you can imagine, there can be a lot of reports coming from client browsers going to our site so having a way to organize and visualize the data was really important. We ultimately accomplished this by using a third party vendor that specialized in organizing all the CSP data for us.
After all that, we finally had a workable CSP to push into our production environment. The next step was to find a process to maintain our CSP.
One of the challenges with how we did CSP initially was that our policy directives were not in a formal source control system. This meant that whenever we had to make any changes to our CSP directives, we had to do the following:
- Copy the one-line CSP values directly from our CDN configuration
- Paste that line onto a text editor
- Make the changes while still maintaining the single-line layout
- Paste the modified line back into the CDN configuration
- Hope that we didn’t break CSP
To introduce source control, we simply copied and pasted the single-line CSP header from our configuration and into a text file that we checked into git. This solution worked okay at first, but after a few times having to update the CSP we quickly understood that we need a better process — specifically around managing the single-lined CSP header.
So, we converted the one-line into YAML! YAML is a data serialization language, and we chose that over other alternatives mainly due to YAML’s readability factor. The format of a YAML file is more human-readable and easier to manage than something like JSON.
Let’s take this CSP header for example:
frame-ancestors 'self' https://www.coolwebsite.com; default-src 'self' https://*.rei.com; script-src 'self' 'unsafe-eval' blob: https://www.coolwebsite.com https://*.another-website.com; img-src data: *; report-uri https://cspissues.com
After giving it the YAML treatment, the line above would look like this:
frame-ancestors: - 'self' - 'https://www.coolwebsite.com' default-src: - 'self' - 'https://*.rei.com' script-src: - 'self' - 'unsafe-eval' - 'blob:' - 'https://www.coolwebsite.com' - 'https://*.another-website.com' img-src: - 'data:' - '*'
This version is easier on the eyes don’t you think? We converted our gigantic CSP header into YAML and developed a script that converts the YAML file to the single-lined CSP header, and Voila! Our new process with these improvements now looks like this:
- Make a change to the CSP YAML file and create a pull request
- Once approved, merge the pull request and generate the single-lined CSP header value using the script
- Replace current CSP header defined in our CDN configuration with the newly created single-lined CSP header value
- Go on a hike to celebrate!
We now have a more readable, source control friendly, easier to manage version of CSP that even non-technical personnel can make changes if they wish. This collaboration between the Cybersecurity Engineering team and SRE is a perfect example of people from different expertise coming together to do more than they can on their own, fufulling one of REI’s core values: “We go further together!”