By Mark Faiers, Sr. Consultant at Contino
By Ajay Ravi, Sr. Partner Solutions Architect at AWS

Contino-Logo-1
Contino-APN-Badge-1.1
Connect with Contino-1

In today’s, agile, application development world, the focus is on speed and flexibility. Traditionally, implementing good security has been neither fast nor flexible.

Furthermore, security is generally seen as something that can’t be easily automated or applied consistently. Sometimes it’s even viewed as a blocker to code being released.

Contino is an AWS Premier Consulting Partner that helps enterprise customers in their cloud transformation by delivering programs which migrate and modernize their workloads on Amazon Web Services (AWS).

Contino makes extensive use of infrastructure as code (IaC) and, in doing so, see numerous security issues that create risk and technical debt due to engineers not having access to codified best practices.

This post looks at how these challenges can be overcome to improve security and increase development velocity.

We’ll focus on AWS Identity and Access Management (IAM) permissions and how the principle of least privilege can be best achieved when using AWS CloudFormation, and more specifically CloudFormation Modules, to provision resources on AWS.

In order to bring these concepts to life, we have included a sample demonstration of applying least privilege IAM policies to Amazon DynamoDB tables across multiple AWS accounts and AWS Regions.

CloudFormation Modules

AWS announced CloudFormation Modules in November 2020. These are a way to package resource configurations for inclusion across stack templates, in a transparent, manageable, and repeatable way.

CloudFormation Modules can encapsulate common service configurations and best practices as modular, customizable building blocks for you to include in your stack templates.

They enable you to include resource configurations that incorporate best practices, expert domain knowledge, and accepted guidelines (for areas such as security, compliance, governance, and industry regulations) in your templates, without having to acquire deep knowledge of the intricacies of the resource implementation.

Benefits of CloudFormation Modules include:

  • Predictability: A module has a predefined schema which is registered in the CloudFormation registry. This ensures that each time a module is included in your template, the same configuration will be applied to the resources created.
  • Reusability: The same module can be used across multiple templates, regions, and accounts.
  • Traceability: CloudFormation retains knowledge of which resources in a stack were provisioned from a module, enabling you to easily understand the source of resource changes.
  • Manageability: Once you have registered a module, you can manage it through the CloudFormation registry.

By the end of this post, you will have learned what advantages CloudFormation Modules bring, and how they can benefit security by making it easier to build and maintain workloads that adhere to best practices of least privilege IAM policies.

Why is This Good?

Developers learn early in their careers that code reuse is good; it keeps things consistent, reduces redundant code, and saves vast amounts of time by not trying to solve problems that have already been solved.

Infrastructure code is no different. Contino recently worked on a project that inherited legacy CloudFormation code. At one point in the project, a requirement was introduced to better conform to the best practices from the AWS Well-Architected Framework, specifically the Security pillar. This was a change that needed to be applied to all Amazon DynamoDB tables across the project.

Because these DynamoDB tables had been defined separately in many different places, applying these requirements was a fairly repetitive task where most of the code was the same but only a few parameters needed to be changed.

Additionally, we found that although this was recognized by the project team as an important activity, due to the manual activity involved in updating all the CloudFormation templates with many lines of code, there was inertia amongst the engineers to apply the best practices.

Had this code been encapsulated in a CloudFormation Module, this would have been a single, simple change that could have saved many developer hours and allowed the developers to focus on new application features.

Why is this Great?

If code reuse was the only benefit that CloudFormation Modules brought, then it would be a good feature. What makes it a great feature is the potential to enhance security.

When applied to IAM policies, least privilege policies can be defined once and re-used across multiple teams and projects. No more wildcards in IAM policies because the developer didn’t know the exact permissions needed by a role and was under pressure to get a release out.

When applying security guardrails, the name of the game is consistency. So, if you have the capability to apply least privilege IAM policies through very minimal code across multiple regions and accounts, executing on your enterprise security strategy becomes much easier.

Solution Overview

The diagram below shows the architecture that we’ll create in the demonstration that follows later in this post.

To maintain brevity and focus on the bits that are important to convey the key design messages around CloudFormation Modules, we’ve kept the workload part of the diagram simple showing an IAM policy and a DynamoDB table.

In real world scenarios, this part of the architecture would usually involve many AWS services interconnected with each other calling upon multiple IAM policies.

Contino-CloudFormation-Modules-1

Figure 1 – Solution architecture.

This architecture shows two AWS accounts (Account A and Account B) that have DynamoDB tables deployed in multiple AWS regions (Region A and Region B).

The scenario we have considered here is where applications need to have scan access restricted to one or more DynamoDB tables instead of providing blanket access to all DynamoDB tables in the account, thereby better conforming to the security principle of least privilege.

The CloudFormation Module is the construct within CloudFormation which allows us to define the CloudFormation template that is reused across accounts and regions.

This CloudFormation Module is stored as an artifact in an Amazon Simple Storage Service (Amazon S3) bucket within the region where it’s created and is then exported to the regions and accounts it’s needed in using the ModuleVersion resource, as we’ll see later.

The CloudFormation template for the workload uses this Module by calling it with specific parameters described later in this post.

Prerequisites

The following prerequisites are required to follow the example below:

  • Programmatic access to at least one AWS account. If you have access to multiple AWS accounts, all the better.
  • You should have the CloudFormation template for a workload that includes DynamoDB tables in multiple regions. It’s OK if this workload is deployed in a single region to begin with.
  • The CloudFormation CLI should be installed locally.

Modules in Action

Creating a new module involves only a couple of commands, starting with:

`cfn init`

This followed by `m` when prompted whether you want to create a Resource, or a Module.

You will then be prompted for a module type, which takes the form:

<Organization>::<Service>::<Name>::MODULE

For this example, we’ll use the value ‘MYORG::IAM::DynamoDBScannerPolicyV1::MODULE’

Contino-CloudFormation-Modules-2

Figure 2 – CloudFormation Module creation using CLI.

This will create:

  • A ‘fragments’ folder where the CloudFormation code lives; it is initialized with some sample code.
  • A .rpdk-config file containing, as the name suggests, module configuration.
  • A rpdk.log file containing logs generated when CloudFormation commands are run.

From here, you only need to define a CloudFormation template, including Resources, Parameters, and Outputs as you normally would.

In this example, we’ll create an IAM policy that allows only the `dynamodb:Scan` operation to be performed on a single dynamodb table.

{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "An IAM Policy that only allow scan access to a specified DynamoDB table", "Parameters": { "TableArn": { "Description": "ARN of the DynamoDB table to allow access to", "Type": "String" }, "NamePrefix": { "Description": "Prefix to attach to the policy name", "Type": "String" } }, "Resources": { "IAMPolicy": { "Type": "AWS::IAM::ManagedPolicy", "Properties": { "ManagedPolicyName": { "Fn::Sub" : [ "${Prefix}-DynamoDBScannerPolicy", { "Prefix": {"Ref": "NamePrefix"}}] }, "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:Scan" ], "Resource": { "Ref": "TableArn" } } ] } } } }
}

It contains two parameters: ‘TableArn’ to specify which DynamoDB table the policy will allow scan access to, and ‘NamePrefix’ to provide a unique name for each policy so that it can be differentiated if the module is used more than once in the same account.

Once you are happy with the template you can simply run:

‘cfn submit’

This command validates your resource schema and uploads the module to CloudFormation. To validate this, you can log into the AWS Management Console, navigate to the CloudFormation service, select Registry from the left menu and then the Modules tab.

Contino-CloudFormation-Modules-3

Figure 3 – CloudFormation registry on the AWS Console.

You will be informed once the deployment of the module is complete.

Behind the Scenes

If you check the CloudFormation console at this stage, you’ll notice a new CloudFormation stack has been created for you.

This contains two Amazon S3 buckets, one for storing the module’s access logs and one for storing the module artifact, more about that later. It also contains an AWS Key Management Service (KMS) key that’s used for encrypting the artifact. Again, this will be required later.

Using the Module

At this point, you are able to use the Module in your CloudFormation templates in the same way you would any other resource, with the module type being used in place of the resource type.

In our example, we’ll use this module to create a policy that allows scan access to an existing DynamoDB table:

AWSTemplateFormatVersion: '2010-09-09'
Description: 'module client'
Resources: ScannerRole: Type: myorg::IAM::DynamoDBScannerPolicy::MODULE Properties: TableArn: arn:aws:dynamodb:us-east-1:123456789012:table/table1 NamePrefix: client

Fantastic—only eight lines of code and no need to write any IAM policies! This policy is now ready to be attached to a Role/User/Group that needs scan access to the particular DynamoDB table specified.

Such modules could be created for any number of common operations, such as reading from a particular S3 bucket, and is particularly powerful when used to provide least privilege IAM roles in heavily decomposed (such as serverless) applications that need a very small and specific set of IAM permissions.

Supporting Multiple Accounts and Regions

You may have noticed that the Module is only available in the account and region in which it was published. At this point, if you attempt to use it in any other account or region, it will fail.

Whilst a single region and single account may be sufficient for many personal AWS users, it certainly won’t be for enterprises.

To scale the use of modules, it’s possible to utilize another feature of CloudFormation called StackSets. This allows you to, in a single operation, run a CloudFormation template in multiple regions and accounts.

To make this possible, AWS has introduced the AWS::CloudFormation::ModuleVersion resource. This defines a module, which consists of a name and a pointer to an S3 object; this is what was created earlier when you ran ‘cfn submit’. Here’s an example of such a resource.

A couple of important things should be noted at this stage.

First, the IAM role that provisions the module needs to have the ‘s3:ListBucket’ permission on the bucket the module artifact is stored in, and ‘s3:GetObject’ permission on the artifact object itself.

Secondly, the same IAM role needs the ‘kms:Decrypt’ permission on the KMS key created earlier. This is so that it can decrypt the module artifact.

As long as these permissions are defined, the process of rolling the module out across regions/account is simple:

  • Navigate to the AWS CloudFormation console.
  • Select StackSets from the left-hand menu and select Create Stack.
  • Leave the first option as “Template is Ready.” and for the second option choose “Upload a Template file.” Click Choose File and upload the template containing the ModuleVersion as defined above.

Contino-CloudFormation-Modules-4

Figure 4 – CloudFormation stack creation.

  • Click Next and on the following screen give the StackSet a name. Select the accounts/organizational units (OUs) and regions you would like the stack deployed into.
  • Select the number of stacks to deploy concurrently and failure tolerance, and then provision the StackSet.
  • Once the StackSet has finished provisioning all stacks, you are free to use the module in any account/OU and region configured in the StackSet.

Conclusion

In this post, we showed you how to create a repeatable way of configuring least privilege IAM policies for an example scenario around providing applications access to specific Amazon DynamoDB tables using AWS CloudFormation Modules.

We also showed you how CloudFormation Modules can be reused across multiple regions and accounts.

We believe this to be a huge step forward for CloudFormation. Modules and StackSets, if taken full advantage of, could be used to define security standards as code across entire organizations, dramatically improving the organization’s security posture whilst also saving numerous developer hours.

.
Contino-APN-Blog-CTA-1
.


Contino – AWS Partner Spotlight

Contino is an AWS Premier Consulting Partner that focuses on enterprise DevOps and cloud transformation.

Contact Contino | Partner Overview

*Already worked with Contino? Rate the Partner

*To review an AWS Partner, you must be a customer that has worked with them directly on a project.