Operationalizing the AlienVault Sensor CloudFormation Template - Part 1

This is part 1 in a series of articles. To follow along via code, visit the Github repository.

I recently needed to review the AlienVault Sensor deployment for AWS and, well, it left me wanting more. Many companies are smart to offer infrastructure-as-code for their appliances. It provides customers with a near one-click deployment model. It also provides customers with insight into what permissions, servers, and resources the appliance will require. The issue with vendor-supplied code is that it needs to apply to a generic customer base; a sort of lowest common denominator scenario where insecure defaults are needed to fit various environments and customers with various skill levels.

So I was tasked with reviewing the AlienVault template and I found numerous operational and security issues. While I could fix all of these issues and provide a Github repository with the result, I realized that this loses the reasoning behind the decisions. These decisions are often specific to the individual organization, but I want to create a series of articles that explains, in long-form, the decisions behind these fixes.

In the first part, I am going to start easy with two improvements.

  1. Ensure that the template can be downloaded in a repeatable fashion while still ensuring we know what has changed.
  2. Use YAML for the template.

"Make" Downloads Repeatable

The problem that I often see with security teams is that they download a template off of a vendor's website, deploy it, then promptly forget about where the template comes from. When we talk about supply chain security, this is a risk for three reasons.

The first risk comes from the fact that there is no documented place where the template came from. It could be a legitimate URL today, and a phishing website tomorrow. By documenting the URL, we are ensuring that our future selves can know where we downloaded the template.

The second risk comes from the fact that the website could go away, or be offline for any length of time. This would break deployments that rely on the template.

The last risk arises because we no longer understand what was the original state of the template. We could download the template tomorrow and have it change, and nobody would know what changed unless they pulled the template from the CloudFormation stack. And then you'd need to determine whether the stack has changed at any time.

Why waste time with these risks when you can automate it away using the same techniques that software developers use, namely version control and Makefiles.

We setup a new git repository, public or private depending on your needs, and commit the template into it. Then we create a simple Makefile that can download the template at any time.

.DEFAULT_GOAL: help

# Taken from https://cybersecurity.att.com/documentation/usm-anywhere/deployment-guide/aws/deploying-aws-sensor.htm 
download.location := https://s3.amazonaws.com/downloads.alienvault.cloud/usm-anywhere/sensor-images
output.file := usm-anywhere-sensor-aws-vpc.template

.PHONY: help
help: ## show this message
  @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: download-original
download-original: ## Download the original CFN template from AT&T
  curl -o $(output.file) "$(download.location)/$(output.file)"

Commit this file and push to your repository. I'll note that the help: target is just a convenience method that provides self-documenting Makefile when you type make.

This automation now mitigates all the aforementioned risks, plus it allows you to do this:

make download-original

And a either the template is unchanged (provable with git diff) or a new template is available and the changes can be seen (again, provable with git diff). This actually helped when writing this blog post, because a new template was created between my first commit and the second commit. This tells me that AT&T updates their template often, which is good to keep in mind – something which I'll discuss in the next article.

For now, we now have repeatable downloads of the template with change-tracking. Yay!

Use YAML for the Template

The CloudFormation template that AlienVault provides is in JSON format. Originally, CloudFormation templates could only be specified in JSON but in 2017, YAML support was introduced. The fact that AT&T supplied a template in JSON makes me wonder how long ago their template was designed. Regardless of where you sit on the endless YAML versus JSON debate, YAML has three objective benefits for our purposes here:

  1. It requires less lines of code to accomplish the same result.
  2. It allows comments.
  3. It provides features that are not supported in JSON templates.

The next article will talk about the third benefit, so let's start looking at the first two benefits by quickly familiarizing ourselves with the structure of the JSON template.

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AWS CloudFormation Template for the USM Anywhere Sensor. This template will create an instance of the USM Anywhere Sensor AMI with appropriate security restrictions. The AMI will create a read-only instance role. You can access the USM Sensor by going to the CloudFormation Template Resource tab and clicking the link in the URL field that directs you to the AWS Console Instance details page. Then, from the lower instance Description tab, enter the IP address of the instance your browser.",
  "Conditions": {
    "trafficMirroringEnabled": {
      "Fn::Equals": [
        "Yes",
        {
          "Ref": "TrafficMirroring"
        }
      ]
    },
    "publicIPEnabled": {
      "Fn::Equals": [
        "Yes",
        {
          "Ref": "PublicIP"
        }
      ]
    }
  },
  "Parameters": {
    "SSHLocation": {
      "Description": "The IP address range that can be used to access the USM Anywhere Sensor that you are deploying in your AWS Account through the CLI. For security considerations, 0.0.0.0/0 is not recommended, so please restrict to a smaller IP range if possible.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "0.0.0.0/0",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "Must be a valid IP CIDR range of the form x.x.x.x/x."
    },
...

There are a various things that stand out to my software developer mind when seeing JSON templates.

  1. Strings are just run-on lines, making it hard to read.
  2. All. Those. Braces.
  3. The file length. At 689 lines, that's a lot to read and comprehend.
  4. No comments to explain confusing choices made by the template designer.

Let's convert it to YAML. This can be done manually, which is slow but results in perfect fidelity, or it can be done automatically, which is fast but requires tweaks. When I'm learning about a template the first time, perhaps to review it for risks, I actually choose the manual method and I advise inexperienced people to do this too. However, that doesn't look great in blog form, so let's start with the fast method.

Download the template with make download-original. Now write a new Makefile target that will convert JSON into YAML. Warning: this is an ugly Ruby one-liner, but it does its job well.

yaml.file := $(output.file).yml

.PHONY: convert-to-yaml
convert-to-yaml: ## Convert JSON template to YAML
  @ruby -e "require 'json'; require 'yaml'; puts YAML.dump(JSON.parse(File.read('$(output.file)')))" > $(yaml.file)

Now just type make convert-to-yaml, et voila! You will end up with a YAML file, and it is almost perfect. At 487 lines of code it is 30% smaller than the JSON template, and without all the braces it is much easier to read.

---
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation Template for the USM Anywhere Sensor. This template
  will create an instance of the USM Anywhere Sensor AMI with appropriate security
  restrictions. The AMI will create a read-only instance role. You can access the
  USM Sensor by going to the CloudFormation Template Resource tab and clicking the
  link in the URL field that directs you to the AWS Console Instance details page.
  Then, from the lower instance Description tab, enter the IP address of the instance
  your browser.
Conditions:
  trafficMirroringEnabled:
    Fn::Equals:
    - 'Yes'
    - Ref: TrafficMirroring
  publicIPEnabled:
    Fn::Equals:
    - 'Yes'
    - Ref: PublicIP
Parameters:
  SSHLocation:
    Description: The IP address range that can be used to access the USM Anywhere
      Sensor that you are deploying in your AWS Account through the CLI. For security
      considerations, 0.0.0.0/0 is not recommended, so please restrict to a smaller
      IP range if possible.
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid IP CIDR range of the form x.x.x.x/x.
...

Now let's add some more manual tweaks which, admittedly, are more stylistic in nature, but they improve the readability of the template.

Add some spacing between the top-level keys (e.g. Parameters, Resources, Metadata, Outputs). This makes it easier to see the logical blocks within the file, similar to how spacing between paragraphs makes articles like this easier to read.

Convert blocks of text into multi-line strings with the YAML special character >. It looks nicer and you get to control the line length to 80 characters (my preference, but it helps when working in SSH terminals). Thus, this block:

Description: AWS CloudFormation Template for the USM Anywhere Sensor. This template
  will create an instance of the USM Anywhere Sensor AMI with appropriate security
  restrictions. The AMI will create a read-only instance role. You can access the
  USM Sensor by going to the CloudFormation Template Resource tab and clicking the
  link in the URL field that directs you to the AWS Console Instance details page.
  Then, from the lower instance Description tab, enter the IP address of the instance
  your browser.

becomes:

Description: >
  AWS CloudFormation Template for the USM Anywhere Sensor. This template
  will create an instance of the USM Anywhere Sensor AMI with appropriate
  security restrictions. The AMI will create a read-only instance role. You
  can access the USM Sensor by going to the CloudFormation Template Resource
  tab and clicking the link in the URL field that directs you to the AWS
  Console Instance details page.  Then, from the lower instance Description
  tab, enter the IP address of the instance your browser.

This increases the file size to 512 lines (still a 25% reduction over the JSON template) but a bit of whitespace never hurt anyone, and the increased readability it worth the expense.

That's it for now. In the next part, I will focus on improving readability of the template by using the latest CloudFormation features.