Securing AWS CloudFormation Stacks with Fugue

Launched in 2011, AWS CloudFormation was a game changer because it was one of the first template-based, infrastructure-as-code (IaC) tools that provided the ability to express the full cloud infrastructure stack as configuration files. It wasn’t limited to the OS layer like traditional configuration management tools.

 

However, organizations that operate on AWS under strict security rules and compliance regimes (i.e., HIPAA, PCI, NIST 800-53) need to make sure their infrastructure is created in accordance with the applicable security and regulatory policies—and stays aligned in the face of constant change.

 

The Risk of Cloud Misconfigurations, Drift, and Policy Violations

 

IaC tools like CloudFormation (CF) were not designed to address security and compliance comprehensively, and they can't prevent configuration drift, which is quite commmon and introduces significant risk of data leaks and other security breaches. To address this, organizations have relied on separate cloud monitoring tools. But these tools create a lot of alert noise, and we waste time sifting through this noise and risk missing critical, actionable information. Not to mention, we’re stuck managing multiple sources of truth!

 

The threats to our cloud infrastructure are automated, constantly probing networks for misconfigured resources to exploit. Without automated remediation of any and all misconfigurations across your entire infrastructure stack, your Mean Time to Remediation (MTTR) is measured in hours or days or even weeks. The situation is ripe for a data breach or other security incident. It’s a disaster waiting to happen.

 

The problem, until now, is that it’s been nearly impossible to switch from one IaC tool to another without debilitating technical overhead—we’re stuck with what we originally chose for our existing cloud environments.

 

That’s why we added transitioning capability to Fugue for your existing CF stacks. Fugue can take over for CF—automatically folding in security, compliance, and remediation features—without any service disruption. It becomes easy to validate policy compliance, identify unapproved changes, and fix violations and misconfigurations, ensuring they never happen again.

 

From Infrastructure-as-Code to Cloud Security Automation

 

This example should take about 10 minutes if you have Fugue running in one of your AWS accounts, or about a half an hour if you need to install Fugue first. Click here to get started with Fugue. It's free.

 

In this example, we’re going to:

 

  1. Create a CF stack (for a demo application that includes VPC, ASG, DDB, IAM, ELB—note that Fugue covers many other AWS services not used in this example).
  2. Use Fugue to transcribe the running infrastructure into a Fugue composition expressed in Ludwig.
  3. Import the infrastructure into a Fugue process (a running instance of a composition).
  4. Update the CF stack with a “retain” deletion policy and delete the stack.
  5. Enjoy continuous monitoring and automated identification and remediation of any configuration drift the moment it occurs! Focus on other things!

 

Let's Get Started!

 

new-cf-to-fugue-flow-diagram.png

 

Create a CloudFormation Stack

Note: Beginning with CloudFormation isn’t a requirement to start using Fugue. Recommended best practice, in fact, is to begin with Fugue compositions. But many shops are currently using CF for AWS to provision resources; the approach here is a way to easily and quickly transition running infrastructure to Fugue's management for continuous security and compliance automation.

 

First, let’s create a CF stack to launch an application with the CF template DemoApp.json. (Resources are tagged, but run the template in a clean region for the sake of example. We use us-west-2 here.)

<code><span class="hljs-typename">aws --<span class="hljs-variable">region</span> <span class="hljs-variable">us-west-2</span> <span class="hljs-variable">cloudformation</span> <span class="hljs-variable">create-stack</span>  --<span class="hljs-variable">template-body</span> <span class="hljs-variable">file</span>:</span>//DemoApp.json   --capabilities CAPABILITY_IAM   --stack-name DemoApp</code>

Now, let’s verify that the application is working by describing the load balancer and visiting the subsequent URL shown in the output from this command.

<code><span class="hljs-typename">aws --<span class="hljs-variable">region</span> <span class="hljs-variable">us-west-2</span> <span class="hljs-variable">elb</span> <span class="hljs-variable">describe-load-balancers</span> | <span class="hljs-variable">jq</span> -<span class="hljs-variable">r '.LoadBalancerDescriptions</span>[] | <span class="hljs-variable">select</span>(.L<span class="hljs-variable">oadBalancerName</span> | <span class="hljs-variable">startswith</span>("D<span class="hljs-variable">emoApp</span>")) | .DNSN<span class="hljs-variable">ame</span>'</span></code>

 

Transcribe Your Infrastructure with Fugue

Most AWS resources can be tagged; some cannot. For those we can’t tag, we need to let the Fugue Transcriber know what their Physical Resource IDs are, so we’ll grab those with the command below and enter them into the Transcriber filter file, called filter.yaml.

<code><span class="hljs-typename">aws --<span class="hljs-variable">region</span> <span class="hljs-variable">us-west-2</span> <span class="hljs-variable">cloudformation</span> <span class="hljs-variable">describe-stack-resources</span> --<span class="hljs-variable">stack-name</span> D<span class="hljs-variable">emoApp</span> | <span class="hljs-variable">jq</span> -<span class="hljs-variable">r '.StackResources</span>[] | <span class="hljs-variable">select</span>(.R<span class="hljs-variable">esourceType</span> == "AWS:</span>:<span class="hljs-record">AutoScaling::L<span class="hljs-variable">aunchConfiguration</span><span class="hljs-string">" or .ResourceType == "</span>AWS::IAM::I<span class="hljs-variable">nstanceProfile</span><span class="hljs-string">" or .ResourceType == "</span>AWS::IAM::R<span class="hljs-variable">ole</span><span class="hljs-string">")"</span></span></code>

From the output, get the PhysicalResourceID for the launch configuration, instance profile, and role, and enter those into the appropriate field in filter.yaml. Notice that we already inserted the TableName, demo-app-table, because we knew that value, as well as the Tag, Migrate, which is the tag we used for all taggable AWS resources in the stack. Note: Don’t use any quotation marks in the input you provide; the only ones needed are already provided and shown in the last - include line for Tags.) Save the changes you make to this file.

<code><span class="hljs-typename">- <span class="hljs-variable">include</span>:</span> <span class="hljs-record">aws-autoscaling-launch-configurations:L<span class="hljs-variable">aunchConfigurationName</span> ~=</span><span class="hljs-typename">- <span class="hljs-variable">include</span>:</span> <span class="hljs-record">aws-dynamodb-tables:T<span class="hljs-variable">ableName</span> ~= <span class="hljs-variable">demo-app-table</span></span><span class="hljs-typename">- <span class="hljs-variable">include</span>:</span> <span class="hljs-record">aws-iam-instance-profiles:I<span class="hljs-variable">nstanceProfileName</span> ~=</span><span class="hljs-typename">- <span class="hljs-variable">include</span>:</span> <span class="hljs-record">aws-iam-roles:R<span class="hljs-variable">oleName</span> ~=</span><span class="hljs-typename">- <span class="hljs-variable">include</span>:</span> <span class="hljs-string">"*:Tags[].Key == Migrate"</span></code>

Now, let’s transcribe the infrastructure into Ludwig, Fugue’s language for integrating infrastructure-as-code and policy-as-code.

<code><span class="hljs-typename">fugue-transcriber --<span class="hljs-variable">region</span> <span class="hljs-variable">us-west-2</span> --<span class="hljs-variable">filter-file</span> <span class="hljs-variable">filter</span>.<span class="hljs-variable">yaml</span> D<span class="hljs-variable">emoApp</span>.<span class="hljs-variable">lw</span></span></code>

 

A Quick Note About Ludwig

Ludwig is a straightforward, domain-specific language designed for coding cloud infrastructure. It’s used by Fugue to automatically build, update, and continuously maintain cloud infrastructure.

 

It’s easy to learn, yet includes helpful features like type safety, compilation with detailed error messaging, and the capacity to write custom functions for any security validation or policy desired.

 

With Ludwig, you have the simplicity of declarative bindings and, if and when you want it, the power of a full functional programming language.


 

Now we have our transcribed Ludwig. Make one edit to the file. We don’t need the external references to EC2 instances attached to the ELB because they are already accounted for via the ASG.

 

  • Remove the four external EC2 instances attached to the ELB.
  • Save the change to DemoApp.lw and ensure it compiles.
<code><span class="hljs-typename">lwc D<span class="hljs-variable">emoApp</span>.<span class="hljs-variable">lw</span></span></code>

cf-to-fugue-composer-diagram-cropped.png

With Fugue, you get dynamically-generated infrastructure diagrams, like this beauty right here.

 

Import Your Infrastructure into Fugue

Once the Ludwig file is edited, we’re ready to import the infrastructure into a managed Fugue process.

<code><span class="hljs-typename">fugue <span class="hljs-variable">run</span> D<span class="hljs-variable">emoApp</span>.<span class="hljs-variable">lw</span> -<span class="hljs-variable">a DemoApp</span> --<span class="hljs-variable">import</span></span></code>

Let’s verify that the import was a SUCCESS.

<code>fugue <span class="hljs-keyword">status</span></code>

It can take a minute or two for Fugue to complete the import. Do not proceed to the next step until you’ve verified that your fugue status is SUCCESS.

 

Delete the CloudFormation Stack

Once you’ve verified that the Fugue import was successful, you can now update your CF stack with a “retain” deletion policy and delete the stack. From this point forward, Fugue will continuously monitor the state of your infrastructure (roughly every 30 seconds) and automatically revert any configuration drift or unauthorized change to the known good state expressed in the Fugue composition (i.e., the Ludwig file DemoApp.lw).

 

Note: It is very important not to delete the CF stack until Fugue has successfully imported the infrastructure. Otherwise, you may end up in a situation where the CF stack is deleted and Fugue was not able to import the resources, in which case nothing would be managing the resources.

 

Add DeletionPolicy attribute "retain" to CF template

Add a DeletionPolicy to every resource in the CloudFormation JSON file. This creates a new file called DemoAppRetain.json.

<code><span class="hljs-typename">cat D<span class="hljs-variable">emoApp</span>.<span class="hljs-variable">json</span> | <span class="hljs-variable">jq</span> '.R<span class="hljs-variable">esources</span>[].D<span class="hljs-variable">eletionPolicy</span> = "R<span class="hljs-variable">etain</span>"' &gt; D<span class="hljs-variable">emoAppRetain</span>.<span class="hljs-variable">json</span></span></code>

 

Update CF stack with DeletionPolicy attribute "retain"

<code><span class="hljs-typename">aws --<span class="hljs-variable">region</span> <span class="hljs-variable">us-west-2</span> <span class="hljs-variable">cloudformation</span> <span class="hljs-variable">update-stack</span>  --<span class="hljs-variable">capabilities</span> CAPABILITY_IAM  --<span class="hljs-variable">template-body</span> <span class="hljs-variable">file</span>:</span>//DemoAppRetain.json   --stack-name DemoApp</code>

Ensure the update is complete. The following should return UPDATE_COMPLETE.

<code><span class="hljs-typename">aws --<span class="hljs-variable">region</span> <span class="hljs-variable">us-west-2</span> <span class="hljs-variable">cloudformation</span> <span class="hljs-variable">describe-stacks</span>  --<span class="hljs-variable">stack-name</span> D<span class="hljs-variable">emoApp</span> | <span class="hljs-variable">jq</span> '.S<span class="hljs-variable">tacks</span>[] | <span class="hljs-variable">select</span>(.S<span class="hljs-variable">tackName</span> == "D<span class="hljs-variable">emoApp</span>") | .S<span class="hljs-variable">tackStatus</span>'</span></code>

 

Delete the CF stack

<code><span class="hljs-typename">aws --<span class="hljs-variable">region</span> <span class="hljs-variable">us-west-2</span> <span class="hljs-variable">cloudformation</span> <span class="hljs-variable">delete-stack</span> --<span class="hljs-variable">stack-name</span> D<span class="hljs-variable">emoApp</span></span></code>

Look in the AWS Console to confirm that the stack is deleted.

 

Verify DemoApp still works

Describe the load balancer and visit the URL returned by this command:

<code><span class="hljs-typename">aws --<span class="hljs-variable">region</span> <span class="hljs-variable">us-west-2</span> <span class="hljs-variable">elb</span> <span class="hljs-variable">describe-load-balancers</span> | <span class="hljs-variable">jq</span> -<span class="hljs-variable">r '.LoadBalancerDescriptions</span>[] | <span class="hljs-variable">select</span>(.L<span class="hljs-variable">oadBalancerName</span> | <span class="hljs-variable">startswith</span>("D<span class="hljs-variable">emoApp</span>")) | .DNSN<span class="hljs-variable">ame</span>'</span></code>

Your app is still running uninterrupted. Fugue has fully taken over for CloudFormation and is now securely managing, monitoring, and enforcing your infrastructure. Should any drift or unapproved change occur, Fugue's automated remediation will detect and fix the problem.

 

Clean Up

 

If you go into the AWS Console and deliberately manipulate the DemoApp's infrastructure—for example, try adding or changing an inbound security group rule or deleting the load balancer—you'll observe Fugue's automatic remediation of the change(s). Just refresh the AWS Console view until you see the change you made disappear and the original configurations, i.e., the code declarations in the DemoApp.lw file, honored with no deviation. When you're satistfied after examining the running infrastructure, go ahead and use the fugue kill command with the alias process name:

<code><span class="hljs-typename">fugue <span class="hljs-variable">kill</span> D<span class="hljs-variable">emoApp</span></span></code>

Run a fugue status after a few moments and make sure the process is gone. First, it will be in a Killing state, then it will disappear.

 

Going Beyond this Example

 

Here are some recommendations for taking next steps:

 

  • Try this out with your own CF stacks by spinning up a CF stack in a test environment and tagging all resources possible. AWS offers advice and resources around tagging; check out this AWS page on tagging and AWS documentation for resources you use. Also note that tags starting with aws: are reserved by AWS. We prepend fugue-transcriber to tags that start with aws:. We do this because you can't create tags with those names. You also don't want to name your stack with anything that starts with Fugue.
  • Fugue service coverage is expansive, but do check in with our Support Team if you have questions about service coverage and migration to Fugue.

 

View this example on Github.

 

Jonathan Sabo, Drew Wright, Racquel Yerbury, and Chris Clark all contributed to bringing this example and article to life.

 

Secure Your Cloud

Find security and compliance violations in your cloud infrastructure and ensure they never happen again.