Embed IAM Best Practices With AWS CloudFormation Hooks

Maarten Geelen
Contino Engineering
10 min readFeb 11, 2022

--

AWS CloudFormation Hooks

Introduction

AWS has just launched a new addition to the CloudFormation service called Hooks, enabling you to embed custom resource validation rules into the CloudFormation engine.

This prevents users across your entire AWS estate from deploying resources outside of your guardrails and standards. Hooks can be triggered on resource creation, updates and deletion, giving you the freedom to augment the entire lifecycle of any resource.

By the end of this blog you will be able to deploy our CloudFormation Hook to embed IAM best practices, and have learned how CloudFormation Hooks can shift left your IAM security analysis with the use of AWS IAM Access Analyzer.

What Are the Benefits of Using CloudFormation Hooks?

At the simplest level, a hook is a Lambda function automatically invoked in the background of a CloudFormation stack execution.

This gives you full control to validate the configuration and make AWS API calls before the resource configuration continues and opens up a wide range of use cases where CloudFormation Hooks can elevate your cloud governance and company policy adoption.

Plus, it provides a much lower TCO approach that is embedded directly into CloudFormation, making it impossible to circumvent this when using the service, enforcing security integrity across the cloud estate.

Then Vs Now: How Is This Better Than Before?

Previously, teams have had to embed and manage tooling in their pipelines which can be circumvented, hand-crafted CloudFormation modules and service catalogue products, or write custom resources — All options that fail to provide the same guarantees and require significantly more time investment.

Example Use Case for CloudFormation Hooks

Imagine the scenario where your company policy requires S3 buckets be secured with KMS keys for Server Side Encryption.

CloudFormation Hooks can validate the AWS::S3::Bucket resource definition and block the creation of an unencrypted S3 Bucket or manipulation of the Server Side Encryption configuration during an update execution.

This allows you to be confident that the policy is being enforced, actively preventing misconfigured S3 buckets from ever existing.

Introducing Contino’s Own CloudFormation Hook With AWS

Here at Contino, we work with some of the biggest enterprises in the world on implementing a least-privilege identity perimeter that accelerates cloud driven innovation instead of stifling it.

Given the inherent complexity of IAM on AWS — and with IAM policy writing being a decentralised activity — we need to be able to validate policies at scale for common anti-patterns. To support this endeavour, we have published a CloudFormation Hook that validates your IAM policies against AWS IAM Access Analyzer and stops the CloudFormation execution if AWS recommended standards are not followed.

The journey to better IAM practices starts today.

What Is AWS IAM Access Analyzer?

Before we dive into our CloudFormation Hook, here’s a quick overview of AWS IAM Access Analyzer and how we use it.

AWS IAM Access Analyzer is an AWS service that identifies security risks to resources and data — in particular IAM resources. It supports a variety of use cases; in our use case we are utilising the validate-policy API.

AWS IAM Access Analyzer validates your policy against IAM policy grammar and best practices. It returns a findings report across four different sections:

  • Security: potential security risks with the policy
  • Errors: policy format errors that prevent policy from functioning
  • General: best practices but issues are not security risks
  • Suggestions: AWS recommendations that don’t impact policy permissions

If any findings are reported by AWS IAM Access Analyzer, they are detailed in multiple languages along with what specific policy is affected, what causes the issue and how to resolve it.

How Does Our CloudFormation Hook Work?

We have published a CloudFormation Hook that shifts left the security review of IAM Least Privilege by validating your IAM resource policies.

The hook utilises the validate policy API from AWS IAM Access Analyzer. It will execute before the Create and Update actions of a CloudFormation deployment.

We called it the “Contino::AccessAnalyzer::PolicyValidation’’ CloudFormation Hook.

It validates the policies for the following IAM resource types:

  • AWS::IAM::Policy
  • AWS::IAM::Role
  • AWS::IAM::User
  • AWS::IAM::Group
  • AWS::IAM::ManagedPolicy

Each of these resources have their own variation of defining the policy document permissions within the CloudFormation syntax, which we have incorporated:

  • Policy document
  • Inline policies
  • Assume role policies

How Do We Handle AWS IAM Access Analyzer Findings?

Whenever a policy document of a resource has any findings reported by AWS IAM Access Analyzer, we output the full finding detail in the CloudWatch logs for the developers to analyse and adapt. The findings report includes description of the issue, type of finding and documentation link to more detailed information provided by AWS. Within our Hook we have parameterised each category, and you have the flexibility to mute each of the categories as you see fit for your use case.

An example snippet of a findings report:

{
"findingDetails": "Using wildcards (*) in the action and the resource can allow creation of unintended service-linked roles because it allows iam:CreateServiceLinkedRole permissions on all resources. We recommend that you specify resource ARNs instead.",
"findingType": "WARNING",
"issueCode": "CREATE_SLR_WITH_STAR_IN_ACTION_AND_RESOURCE",
}

A full list of potential findings by the AWS IAM Access Analyzer validate policy API.

How to Get Started With Contino’s Access Analyzer Policy Validation Hook?

To activate our hook, you can log into the AWS Management Console

Before we can activate the CloudFormation hook from the Public CloudFormation Registry, we first need to deploy supporting hook infrastructure resources. This consists of 2 IAM roles, one to provide access for CloudFormation hooks to the AWS Services we need to execute our hook. Another to provide access to write to CloudWatch Logs.

Stage 1 — Deploy CloudFormation Hooks Infrastructure resources

Create a new file named cfn-hook-infrastructure-resources.yaml and copy the following CloudFormation template or clone it from our GitHub repository:

AWSTemplateFormatVersion: "2010-09-09"

Description: >
This CloudFormation template creates 2 IAM roles to be assumed by CloudFormation during Hook operations on behalf of the customer.
- IAM role to perform Validate Policy action with IAM Access Analyzer.
- IAM role to write execution logs to CloudWatch Logs.

Resources:

CfnHookExecutionRole:
Type: AWS::IAM::Role
Properties:
MaxSessionDuration: 8400
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- resources.cloudformation.amazonaws.com
- hooks.cloudformation.amazonaws.com
Action: sts:AssumeRole
Path: "/"
Policies:
- PolicyName: ContinoAccessAnalyzerPolicyValidation
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "access-analyzer:ValidatePolicy"
Resource: "*"

CfnHookLogAndMetricsDeliveryRole:
Type: AWS::IAM::Role
Properties:
MaxSessionDuration: 8400
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- resources.cloudformation.amazonaws.com
- hooks.cloudformation.amazonaws.com
Action: sts:AssumeRole
Path: "/"
Policies:
- PolicyName: LogAndMetricsDeliveryRolePolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:DescribeLogGroups"
- "logs:DescribeLogStreams"
- "logs:PutLogEvents"
- "cloudwatch:ListMetrics"
- "cloudwatch:PutMetricData"
Resource: "*"

Outputs:

CfnHookExecutionRoleArn:
Description: Hook Execution IAM Role Arn
Value: !GetAtt CfnHookExecutionRole.Arn

CfnHookLogAndMetricsDeliveryRoleArn:
Description: Hook Logs and Metrics IAM Role Arn
Value: !GetAtt CfnHookLogAndMetricsDeliveryRole.Arn

Navigate to the CloudFormation service in the Frankfurt region (eu-central-1).

First we will provision the infrastructure CloudFormation stack we just saved to a template file locally.

  • Create a stack with new resources from the top right dropdown.
Figure 1 — Create Stack
  • Select template is ready, upload a template file, select the CloudFormation template file we saved locally. And select next.
Figure 2 — Upload a template file
  • We need to specify a stack name, enter cfn-hook-infrastructure-resources , and click next.
  • We will use all the default settings of the stack options, scroll to the bottom of the page and select next.
  • On the review page, we need to select I acknowledge that AWS CloudFormation might create IAM resources. at the bottom of the page. And click Create Stack
Figure 3 — Acknowledge IAM Resources
  • Now our stack is being deployed, to get an updated stack progress update we might need to click the refresh button.
Figure 4 — Infrastructure stack successfully deployed
  • If the stack fails to deploy (e.g. insufficient permissions), delete the stack and repeat this step from the beginning.

Our CloudFormation template has successfully deployed. We will save the Stack outputs as we will need them at a later stage.

  • Select Outputs from the CloudFormation console. Save both output ARNs (Amazon Resource Names).
Figure 5 — Stack Outputs

We will also edit the termination protection to avoid accidentally deleting this infrastructure stack in our next steps. Under stack actions at the top of the page, select edit termination protection. Set to Enabled.

Figure 6 — Stack protection

Stage 2 — Activate CloudFormation Hook

Navigate to the CloudFormation Registry, select Public extensions, and filter by Hooks & Third party. In the search field enter Contino. And select our hook Contino::AccessAnalyzer::PolicyValidation. If you can not find our hook, ensure you are in the Frankfurt eu-central-1 region.

Figure 7 — CloudFormation Registry
  • At the right top click activate, and configure the Hook.

We need to configure the hook, to specify which role it can use for execution and configure the logging details.

  • Fill in the execution role from the infrastructure stack output we captured before.
  • Enter the logging configuration in a JSON format with 2 fields. Enter the logging role from the infrastructure stack output we captured before.
{
"logRoleArn": "<Your CfnHookLogAndMetricsDeliveryRoleArn>",
"logGroupName": "cfn-hook-contino-accessanalyzer-policyvalidation-logs"
}
  • The rest of the configuration we will leave default.
  • Activate Extension at the bottom of the page.
Figure 8 — Activate Hook

Now we have activated the CloudFormation hook in your account. We can configure the hook to customize the functionality. Such as mute any of the findings categories or changing the language of the reports.

  • Set configuration alias to default
  • Set the following configuration JSON, as testing default, you can update this at a later stage to customize the functionality.
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {
"languageLocale": "EN",
"accessAnalyzerMuteGeneralFindings": false,
"accessAnalyzerMuteSuggestionFindings": false,
"accessAnalyzerMuteSecurityFindings": false,
"accessAnalyzerMuteErrorFindings": false
}
}
}
}
  • Select configure extension.
Figure 9 — Set hook configuration

Now we have successfully deployed the CloudFormation Hook. Let’s do some tests and see it in action.

Stage 3 — Testing of the CloudFormation Hook

We will go through a use case of creating an IAM Managed Policy for users to have read access to DynamoDb tables. Save the following CloudFormation template to a new file named hook-policy-validation.yaml

AWSTemplateFormatVersion: "2010-09-09"

Description: Creates an IAM Managed Policy for Hook testing.

Resources:

rTestManagedPolicyHookDeployment:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
Description: Managed Policy for Read Access DynamoDb, all tables
Path: /
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Resource: "*"
  • Go to CloudFormation Stacks, create a new stack, with new resources. For a guided step-by-step on creating new Stacks, see stage 1.
  • As we did before, selecting Template is ready, Upload a template file, choose the saved template.
  • Enter a stack name hook-policy-validation. Next, leaving everything as default, next, acknowledge, select create stack.

CloudFormation Hook messages are also visualised in the CloudFormation console. To enable the Hook view in the console, while on the events tab click the settings button on the right. Enable the hook invocation toggle. And refresh the events.

Figure 10 — Access viewing settings
Figure 11 — Enable hook invocations column

Upon refresh of the CloudFormation Events, when we hover over the hook message. We can see that our Hook failed the execution, let’s find out why this happened.

Figure 12 — Hook failed CloudFormation execution
  • Go to the CloudWatch console, select Log Groups from the left menu and search cfn-hook. Or your custom log name that you’ve set in the configuration for the hook.
Figure 13 — Find CloudWatch Logs for the hoo
  • Open the log group and select the latest log message.

We can see that our hook has identified one finding from AWS IAM Access Analyzer validate policy API. Our policy seems to be malformed. AWS IAM Access Analyzer is notifying us that we are missing an Action element in our policy statement. The finding also provides a link to the documentation of the error.

Figure 14 — Missing statement in policy

We want to assign permissions following the least privilege method. For this example use case, we wanted users to be able to have read permissions on DynamoDb tables. Update our CloudFormation template as follows.

rTestManagedPolicyHookDeployment:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
Description: Managed Policy for Read Access DynamoDb, all tables
Path: /
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- 'dynamodb:BatchGet*'
- 'dynamodb:Describe*'
- 'dynamodb:Get*'
- 'dynamodb:List*'
- 'dynamodb:Query'
- 'dynamodb:Scan'
Resource: "*"
  • Go back to the CloudFormation console, delete our failed stack.
  • Create a new stack, same steps as before in stage 1.

Our hook successfully executed and set the hook’s complete success status, allowing CloudFormation to continue with the stack deployment.

Figure 16 — Hook successfully passed

All CloudFormation templates, hook configurations are available on our GitHub repository.

In Summary

In this post, we showed you how to utilise our published IAM Policy Validation hook that can shift left your IAM security review and embed IAM best practices in your organisation with the use of AWS IAM Access Analyzer.

We also showed you how CloudFormation Hooks can be beneficial for a wide range of use cases. We believe this is a huge step forward for CloudFormation and, if taken full advantage of, could be transformational for your organisation to increase the security posture of deployments.

Contino is an AWS Premier Tier Services Partner. We have been awarded the AWS DevOps, AWS Financial Services, AWS Energy, AWS Data & Analytics, and AWS Migration Competencies. To find out more about our AWS CloudFormation Hook or if you’d like support across any other areas of your cloud journey, get in touch!

--

--

Maarten Geelen
Contino Engineering

Principal Consultant at Contino, AWS, DevOps, IaC, CloudFormation enthusiast.