CloudFormation permissions are surprisingly granular, allowing you to grant it just enough access to manage resources without over-provisioning.
Let’s say you’re deploying a new VPC with a couple of EC2 instances and an S3 bucket. Normally, you might let CloudFormation use the default AWSServiceRoleForCloudFormation role. This role has broad permissions, including AdministratorAccess, which is a huge security risk. If a malicious actor gains control of your AWS account and can trigger CloudFormation, they could potentially deploy anything.
Instead, we can create a dedicated service role specifically for this stack.
Here’s a CloudFormation template that creates an IAM role named MyVPCStackRole with the necessary permissions to create a VPC, EC2 instances, and an S3 bucket:
AWSTemplateFormatVersion: '2010-09-09'
Description: IAM Role for My VPC CloudFormation Stack
Resources:
MyVPCStackRole:
Type: AWS::IAM::Role
Properties:
RoleName: MyVPCStackRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: cloudformation.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: VPCResourceCreationPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:CreateVpc
- ec2:CreateSubnet
- ec2:CreateInternetGateway
- ec2:AttachInternetGateway
- ec2:CreateRouteTable
- ec2:CreateRoute
- ec2:AssociateRouteTable
- ec2:DescribeVpcs
- ec2:DescribeSubnets
- ec2:DescribeRouteTables
- ec2:DescribeInternetGateways
- ec2:DescribeSecurityGroups
- ec2:CreateSecurityGroup
- ec2:AuthorizeSecurityGroupIngress
- ec2:RevokeSecurityGroupIngress
- ec2:DeleteSecurityGroup
- ec2:DeleteRoute
- ec2:DeleteRouteTable
- ec2:DetachInternetGateway
- ec2:DeleteInternetGateway
- ec2:DeleteSubnet
- ec2:DeleteVpc
Resource: '*' # For VPC resources, '*' is often necessary as resource IDs are not known at creation time.
- Effect: Allow
Action:
- ec2:CreateTags
- ec2:DeleteTags
Resource: '*'
- Effect: Allow
Action:
- iam:PassRole # Required for EC2 instances to assume their own roles
Resource: !GetAtt EC2InstanceProfile.Arn # Assuming you'll create an EC2 instance profile
- PolicyName: S3ResourceCreationPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:CreateBucket
- s3:DeleteBucket
- s3:PutBucketPolicy # If you plan to attach policies to the bucket
- s3:GetBucketPolicy
- s3:DeleteBucketPolicy
Resource: !Sub 'arn:aws:s3:::${MyS3BucketName}-*' # Using a pattern for bucket names
# Add a policy for EC2 instances to be able to be launched
- PolicyName: EC2LaunchPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:RunInstances
- ec2:TerminateInstances
- ec2:CreateNetworkInterface
- ec2:DeleteNetworkInterface
- ec2:AttachNetworkInterface
- ec2:DetachNetworkInterface
- ec2:DescribeInstances
- ec2:DescribeImages
- ec2:DescribeInstanceTypes
- ec2:DescribeKeyPairs
- ec2:DescribeVpcAttribute
- ec2:DescribeSubnetAttribute
- ec2:DescribeSecurityGroups # To associate security groups
Resource: '*'
# Example of an S3 bucket that the role will manage
MyS3BucketName:
Type: String
Default: my-unique-app-bucket
# Example of an EC2 Instance Profile that might be used by the EC2 instances
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: MyEC2InstanceProfile
Path: /
Roles:
- !Ref MyVPCStackRole # This allows the EC2 instances to assume the role we created
When you deploy this template, CloudFormation creates the MyVPCStackRole. Then, when you deploy your VPC stack, you specify this MyVPCStackRole as the Stack’s service role. This tells CloudFormation to use these specific, limited permissions instead of its default broad ones.
The AssumeRolePolicyDocument is critical. It explicitly allows the cloudformation.amazonaws.com service principal to assume this role. Without it, CloudFormation can’t even pick up the role.
The Policies section contains the actual permissions. Notice we’re being specific: ec2:CreateVpc, ec2:CreateSubnet, s3:CreateBucket, etc. We’re not granting * for every action. For resources like VPCs and subnets, Resource: '*' is often a practical necessity because CloudFormation doesn’t know the exact ARNs of resources it’s about to create. However, for S3 buckets, we can be more precise by using a wildcard pattern like arn:aws:s3:::${MyS3BucketName}-* if we pre-define a naming convention for our buckets.
The iam:PassRole permission is crucial if your EC2 instances themselves need to assume an IAM role (e.g., for accessing other AWS services). You need to grant iam:PassRole on the instance profile’s role to the CloudFormation service role.
If you forget to include iam:PassRole in your service role’s policy when your stack attempts to launch EC2 instances that require an instance profile, the next error you’ll encounter is: The EC2 instance or spot instance request is not authorized to use the instance profile.