Use the Conformity Knowledge Base AI to help improve your Cloud Posture

Enforce Infrastructure as Code using IAM Policies

Trend Cloud One™ – Conformity is a continuous assurance tool that provides peace of mind for your cloud infrastructure, delivering over 1000 automated best practice checks.

Risk Level: Medium (should be achieved)

Enforce Infrastructure as Code (IaC) by limiting the ability to create and manage AWS cloud resources unless they are deployed via Amazon CloudFormation. With the introduction of a new, powerful IAM policy condition named "aws:CalledVia", you can now grant your IAM principals the ability to deploy cloud resources only through CloudFormation, without allowing direct access to specific AWS services.

This rule can help you work with the AWS Well-Architected Framework.

Security

A best practice is to increase the access security to your cloud resources as you move towards production. With "aws:CalledVia" IAM policy condition, you can now limit user access to staging and production environments by forcing them to deploy resources using Amazon CloudFormation templates only (i.e. Infrastructure as Code), without granting them access to the AWS resources themselves. By enforcing the use of "aws:CalledVia" condition, you can be confident that your team members won't make changes outside CloudFormation to get your application to run properly in staging and production, but they can access application logs, stats, and double-check configurations. For example, using the new policy condition, you can grant your IAM users the ability to launch EC2 instances, but only through Amazon CloudFormation, without granting direct access to the Amazon EC2 service.


Audit

To determine if your IAM users are forced to deploy resources via AWS CloudFormation only in order to implement Infrastructure as Code (IaC), perform the following actions:

Using AWS Console

01 Sign in to the AWS Management Console.

02 Navigate to Amazon IAM console at https://console.aws.amazon.com/iam/.

03 In the navigation panel, under Access management, choose Users.

04 Click on the name of the Amazon IAM user that you want to examine.

05 Select the Permissions tab to access the identity-based policies attached to the selected user.

06 In the Permissions policies section, click on the Expand button (right arrow icon) available next to each managed/inline policy, and choose {} JSON to show the policy document in JSON format.

07 Within the {} JSON policy document box, search for the policy statement with the following combination of elements: "NotAction": "cloudformation:*", "Effect": "Deny", and "Condition": {"StringNotEquals": {"aws:CalledViaFirst": "cloudformation.amazonaws.com"}. Repeat this step for each managed or inline policy attached to the selected IAM user. If none of the verified policies have the specified combination of elements, which makes use of "aws:CalledViaFirst" condition key, the selected Amazon IAM user is not forced to deploy AWS resources via CloudFormation only, therefore Infrastructure as Code (IaC) is not implemented using IAM policies.

08 Repeat steps no. 4 – 6 for each IAM user available within your AWS cloud account.

Using AWS CLI

01 Run list-users command (OSX/Linux/UNIX) with custom query filters to list the names of all IAM users available within your AWS account:

aws iam list-users
  --output table
  --query 'Users[*].UserName'

02 The command output should return a table with the requested IAM user identifiers:

----------------------
|     ListUsers      |
+--------------------+
|  cc-prod-manager   |
|  cc-stack-admin    |
|  cc-ec2-developer  |
+--------------------+ 

03 Run list-attached-user-policies command (OSX/Linux/UNIX) using the name of the Amazon IAM user that you want to examine as the identifier parameter and custom filtering to list the Amazon Resource Names (ARNs) of the managed IAM policies attached to the selected user:

aws iam list-attached-user-policies
  --user-name cc-prod-manager
  --query 'AttachedPolicies[*].PolicyArn'

04 The command output should return the ARNs of the managed policies attached to the selected IAM user:

[
	"arn:aws:iam::123456789012:policy/cc-admin-access",
	"arn:aws:iam::123456789012:policy/cc-stack-access"
]

05 Run get-policy-version command (OSX/Linux/UNIX) using the ARN of the IAM policy that you want to examine as the identifier parameter and custom query filters to describe the policy document in JSON format. Repeat this step for each managed IAM policy attached to the selected IAM user:

aws iam get-policy-version
  --policy-arn arn:aws:iam::123456789012:policy/cc-admin-access
  --version-id v1
  --query 'PolicyVersion.Document'

06 The command output should return the requested IAM policy document:

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "AdminAccess",
			"Action": "*",
			"Resource": "*",
			"Effect": "Allow"
		}
	]
}

07 Run list-user-policies command (OSX/Linux/UNIX) using the name of the Amazon IAM user that you want to examine as the identifier parameter and custom filtering to list the names of the inline policies embedded within the selected IAM user:

aws iam list-user-policies
  --user-name cc-prod-manager
  --query 'PolicyNames'

08 The command output should return the names of the inline policies embedded within the user:

[
	"cc-inline-ec2-policy",
	"cc-custom-api-access"
]

09 Run get-user-policy command (OSX/Linux/UNIX) using the name of the inline policy that you want to examine as the identifier parameter, returned at the previous step, to describe the IAM policy document in JSON format. Repeat this step for each inline policy attached to the selected IAM user:

aws iam get-user-policy
  --user-name cc-prod-manager
  --policy-name cc-inline-ec2-policy
  --query 'PolicyDocument'

10 The command output should return the requested IAM policy document:

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Action": "ec2:*",
			"Effect": "Allow",
			"Resource": "*"
		},
		{
			"Effect": "Allow",
			"Action": "elasticloadbalancing:*",
			"Resource": "*"
		},
		{
			"Effect": "Allow",
			"Action": "cloudwatch:*",
			"Resource": "*"
		},
		{
			"Effect": "Allow",
			"Action": "autoscaling:*",
			"Resource": "*"
		}
	]
}

11 Search for the following combination of elements: "NotAction": "cloudformation:*", "Effect": "Deny", and "Condition": {"StringNotEquals": {"aws:CalledViaFirst": "cloudformation.amazonaws.com"} within the policy documents returned by the get-policy-version command output at step no. 6 and get-user-policy command output at step no. 10. If the verified policies do not contain the specified combination of elements, which makes use of "aws:CalledViaFirst" condition key, the selected Amazon IAM user is not forced to deploy AWS resources via CloudFormation only, therefore Infrastructure as Code (IaC) is not implemented using IAM policies.

12 Repeat steps no. 3 – 11 for each Amazon IAM user available in your AWS cloud account.

Remediation/Resolution

To enforce Infrastructure as Code (IaC) via Amazon IAM policies using "aws:CalledVia" condition element, perform the following actions:

Note: As an example, this conformity rule demonstrates how to enforce Infrastructure as Code by limiting the IAM user ability to create, modify, and delete Amazon EC2 resources unless they are deployed via AWS CloudFormation.

Using AWS CloudFormation

01 CloudFormation template (JSON):

{
	"AWSTemplateFormatVersion": "2010-09-09",
	"Resources": {
		"IAMUser": {
			"Type": "AWS::IAM::User",
			"Properties": {
				"UserName": "cc-iac-iam-manager"
			}
		},
		"IAMUserPolicy": {
			"Type": "AWS::IAM::Policy",
			"Properties": {
				"PolicyName": "enforce-iac-iam-policy",
				"PolicyDocument": {
					"Version": "2012-10-17",
					"Statement": [
						{
							"NotAction": [
								"cloudformation:*",
								"ec2:Get*",
								"ec2:Describe*",
								"iam:Describe*",
								"iam:List*",
								"iam:Get*",
								"s3:Put*",
								"sns:List*"
							],
							"Effect": "Deny",
							"Resource": "*",
							"Condition": {
								"StringNotEquals": {
									"aws:CalledViaFirst": "cloudformation.amazonaws.com"
								}
							}
						}
					]
				},
				"Users": [
					{
						"Ref": "IAMUser"
					}
				]
			}
		}
	}
}

02 CloudFormation template (YAML):

AWSTemplateFormatVersion: '2010-09-09'
	Resources:
	IAMUser:
		Type: AWS::IAM::User
		Properties:
		UserName: cc-iac-iam-manager
	IAMUserPolicy:
		Type: AWS::IAM::Policy
		Properties:
		PolicyName: enforce-iac-iam-policy
		PolicyDocument:
			Version: '2012-10-17'
			Statement:
			- NotAction:
				- cloudformation:*
				- ec2:Get*
				- ec2:Describe*
				- iam:Describe*
				- iam:List*
				- iam:Get*
				- s3:Put*
				- sns:List*
				Effect: Deny
				Resource: '*'
				Condition:
				StringNotEquals:
					aws:CalledViaFirst: cloudformation.amazonaws.com
		Users:
			- !Ref 'IAMUser'

Using Terraform (AWS Provider)

01 Terraform configuration file (.tf):

	terraform {
		required_providers {
			aws = {
				source  = "hashicorp/aws"
				version = "~> 4.0"
			}
		}

		required_version = ">= 0.14.9"
	}

	provider "aws" {
		profile = "default"
		region  = "us-east-1"
	}

	resource "aws_iam_user" "iam-user" {
		name = "cc-iac-iam-manager"
	}

	resource "aws_iam_policy" "iam-policy" {
		name   = "enforce-iac-iam-policy"
		policy = <<EOF
		{
			"Version": "2012-10-17",
			"Statement": [
				{
					"NotAction": [
						"cloudformation:*",
						"ec2:Get*",
						"ec2:Describe*",
						"iam:Describe*",
						"iam:List*",
						"iam:Get*",
						"s3:Put*",
						"sns:List*"
					],
					"Effect": "Deny",
					"Resource": "*",
					"Condition": {
						"StringNotEquals": {
							"aws:CalledViaFirst": "cloudformation.amazonaws.com"
						}
					}
				}
			]
		}
		EOF
	}

	resource "aws_iam_policy_attachment" "iam-user-attachment" {
		name       = "cc-iam-user-attachment"
		users      = [aws_iam_user.iam-user.name]
		policy_arn = aws_iam_policy.iam-policy.arn
	}

Using AWS Console

01 Sign in to the AWS Management Console.

02 Navigate to Amazon IAM console at https://console.aws.amazon.com/iam/.

03 In the navigation panel, under Access management, choose Policies.

04 Click on the Create policy button from the console top menu to initiate the setup process.

05 On the Create policy configuration page, select the JSON tab, and paste the following policy document. The IAM policy listed below implements the following combination of elements: "NotAction": "cloudformation:*", "Effect": "Deny", and "Condition": {"StringNotEquals": {"aws:CalledViaFirst": "cloudformation.amazonaws.com"}, to force the associated IAM user to deploy EC2 resources using CloudFormation stacks only, and thus implement Infrastructure as Code (IaC) using IAM. If the associated user tries to create an EC2 instance using the AWS EC2 console, the user will get denied as the EC2 resource deployment is not made using CloudFormation. The following IAM policy makes use of "Deny" and "NotAction" elements to allow working with other managed/inline policies that are attached to the specified IAM user. This effect will supersede any other actions that are allowed for the IAM user. The actions listed for the "NotAction" element, allows the principal to use all CloudFormation permissions to be able to create, update, and delete stacks, browse EC2 resources using "ec2:Get*" and "ec2:Describe*", and support the usage of CloudFormation via AWS management console with "s3:Put*", "sns:List*", and IAM read-related actions:

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "EnforceIACUsingIAM",
			"NotAction": [
				"cloudformation:*",
				"ec2:Get*",
				"ec2:Describe*",
				"iam:Describe*",
				"iam:List*",
				"iam:Get*",
				"s3:Put*",
				"sns:List*"
			],
			"Effect": "Deny",
			"Resource": "*",
			"Condition": {
				"StringNotEquals": {
					"aws:CalledViaFirst": "cloudformation.amazonaws.com"
				}
			}
		}
	]
}

06 Select Next: Tags and use the Add tag button to configure tags for the new IAM policy.

07 Select Next: Review and provide a unique name and a description for your new policy in the Nameand Description boxes.

08 Choose Create policy to create your new Amazon IAM policy.

09 In the navigation panel, under Access management, choose Users.

10 Click on the name of the Amazon IAM user that you want to reconfigure.

11 Select the Permissions tab and choose Add permissions to attach the new IAM policy.

12 On the Add permissions to <iam-user-name> configuration page, choose the Attach existing policies directly tab, and use the Search box to find and select the managed IAM policy created at the previous steps. Once the policy is selected, choose Next: Review, review the policy information, then choose Add permissions to attach your policy to the selected IAM user.

13 Repeat steps no. 10 – 12 to enforce Infrastructure as Code (IaC) via Amazon IAM policies for other IAM users available within your AWS cloud account.

Using AWS CLI

01 Define the required set of permissions that forces the associated IAM user to deploy Amazon EC2 resources using AWS CloudFormation stacks only, and thus implement Infrastructure as Code (IaC) using Amazon IAM. If the associated user tries to create an EC2 instance using the Amazon EC2 console, the user will get denied as the EC2 resource deployment is not made using CloudFormation. The following IAM policy makes use of "Deny" and "NotAction" elements to allow working with other managed/inline policies that are attached to the specified IAM user. This effect will supersede any other actions that are allowed for the IAM user. The actions listed for the "NotAction" element, allows the principal to use all CloudFormation permissions to be able to create, update, and delete stacks, browse Amazon EC2 resources using "ec2:Get*" and "ec2:Describe*", and support the usage of CloudFormation via AWS management console with "s3:Put*", "sns:List*", and IAM read-related actions. Save the following IAM policy to a JSON document named enforce-iac-policy.json:

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "EnforceIACUsingIAM",
			"NotAction": [
				"cloudformation:*",
				"ec2:Get*",
				"ec2:Describe*",
				"iam:Describe*",
				"iam:List*",
				"iam:Get*",
				"s3:Put*",
				"sns:List*"
			],
			"Effect": "Deny",
			"Resource": "*",
			"Condition": {
				"StringNotEquals": {
					"aws:CalledViaFirst": "cloudformation.amazonaws.com"
				}
			}
		}
	]
}

02 Run create-policy command (OSX/Linux/UNIX) using the policy document defined at the previous step, to create the customer-managed policy that would enforce Infrastructure as Code (IaC):

aws iam create-policy
  --policy-name enforce-iac-managed-policy
  --policy-document file://enforce-iac-policy.json

03 The command output should return the metadata for the new managed policy:

{
	"Policy": {
		"PolicyName": "enforce-iac-managed-policy",
		"PermissionsBoundaryUsageCount": 0,
		"CreateDate": "2020-08-24T10:00:00Z",
		"AttachmentCount": 0,
		"IsAttachable": true,
		"PolicyId": "ABCDABCDABCDABCDABCD",
		"DefaultVersionId": "v1",
		"Path": "/",
		"Arn": "arn:aws:iam::123456789012:policy/enforce-iac-managed-policy",
		"UpdateDate": "2020-08-24T10:00:00Z"
	}
}

04 Run attach-user-policy command (OSX/Linux/UNIX) using the name of the Amazon IAM user that you want to reconfigure as the identifier parameter, to attach the managed IAM policy created at the previous steps:

aws iam attach-user-policy
  --user-name cc-prod-manager
  --policy-arn arn:aws:iam::123456789012:policy/enforce-iac-managed-policy

References

Publication date Sep 6, 2023