home / skills / giuseppe-trisciuoglio / developer-kit / aws-cloudformation-iam
/plugins/developer-kit-aws/skills/aws-cloudformation/aws-cloudformation-iam
This skill helps you implement AWS CloudFormation IAM resources with least privilege, cross-account access, and structured templates for secure infrastructure.
npx playbooks add skill giuseppe-trisciuoglio/developer-kit --skill aws-cloudformation-iamReview the files below or copy the command above to add this skill to your agents.
---
name: aws-cloudformation-iam
description: Provides AWS CloudFormation patterns for IAM users, roles, policies, and managed policies. Use when creating IAM resources with CloudFormation, implementing least privilege access, configuring cross-account access, setting up identity centers, managing permissions boundaries, and organizing template structure with Parameters, Outputs, Mappings, Conditions for secure infrastructure deployments.
category: aws
tags: [aws, cloudformation, iam, security, authentication, authorization, roles, policies, infrastructure, iaac]
version: 1.0.0
allowed-tools: Read, Write, Bash
---
# AWS CloudFormation IAM Security
## Overview
Create production-ready IAM infrastructure using AWS CloudFormation templates. This skill covers users, roles, policies, managed policies, permission boundaries, and best practices for implementing least privilege access.
## When to Use
Use this skill when:
- Creating new IAM users with CloudFormation
- Configuring IAM roles for AWS services
- Defining inline policies and managed policies
- Implementing cross-account access with STS
- Creating permission boundaries
- Organizing templates with Parameters, Outputs, Mappings, Conditions
- Implementing cross-stack references for IAM resources
- Configuring IAM Identity Center (SSO)
- Managing service control policies (SCP)
## Instructions
Follow these steps to create IAM infrastructure with CloudFormation:
1. **Define User Parameters**: Specify user names and access types
2. **Create IAM Roles**: Define trust policies for services or users
3. **Write Policies**: Implement least privilege with specific actions
4. **Set Up Permission Boundaries**: Limit maximum permissions for users
5. **Configure Managed Policies**: Attach AWS managed or custom policies
6. **Implement Cross-Account Access**: Use STS for temporary credentials
7. **Create Groups**: Organize users for easier permission management
8. **Enable MFA**: Require MFA for console access
For complete examples, see the [EXAMPLES.md](references/examples.md) file.
## Examples
The following examples demonstrate common IAM patterns:
### Example 1: IAM Role for Lambda
```yaml
LambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-lambda-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
```
### Example 2: Policy with Least Privilege
```yaml
DynamoDBPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub "${AWS::StackName}-dynamodb-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
Resource: !GetAtt Table.Arn
Roles:
- !Ref LambdaRole
```
### Example 3: Permission Boundary
```yaml
DeveloperBoundary:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Permission boundary for developers
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Deny
Action:
- iam:*
Resource: "*"
DeveloperUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub "${AWS::StackName}-developer"
PermissionsBoundary: !Ref DeveloperBoundary
```
For complete production-ready examples, see [EXAMPLES.md](references/examples.md).
## CloudFormation Template Structure
### Standard Format Base Template
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: IAM infrastructure with users, roles, and policies
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: User Configuration
Parameters:
- UserName
- UserPermissionsBoundary
- Label:
default: Role Configuration
Parameters:
- RoleName
- AssumeRolePolicyService
Parameters:
UserName:
Type: String
Default: app-user
Description: Name of the IAM user
MinLength: 1
MaxLength: 64
UserPermissionsBoundary:
Type: String
Description: IAM policy ARN for permissions boundary
Default: ""
RoleName:
Type: String
Default: app-execution-role
Description: Name of the IAM role
AssumeRolePolicyService:
Type: String
Default: lambda.amazonaws.com
Description: Service that can assume the role
AllowedValues:
- lambda.amazonaws.com
- ec2.amazonaws.com
- ecs-tasks.amazonaws.com
- eks.amazonaws.com
- states.amazonaws.com
Mappings:
EnvironmentConfig:
dev:
MaxSessionDuration: 3600
PolicyArns: []
staging:
MaxSessionDuration: 7200
PolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
production:
MaxSessionDuration: 43200
PolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
- arn:aws:iam::aws:policy/SecurityAudit
Conditions:
HasPermissionsBoundary: !Not [!Equals [!Ref UserPermissionsBoundary, ""]]
IsProduction: !Equals [!Ref Environment, production]
Transform:
- AWS::Serverless-2016-10-31
Resources:
# IAM User
AppUser:
Type: AWS::IAM::User
Properties:
UserName: !Ref UserName
PermissionsBoundary: !If
- HasPermissionsBoundary
- !Ref UserPermissionsBoundary
- !Ref AWS::NoValue
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Project
Value: !Ref ProjectName
Outputs:
UserArn:
Description: ARN of the IAM user
Value: !GetAtt AppUser.Arn
Export:
Name: !Sub "${AWS::StackName}-UserArn"
```
## Parameters Best Practices
### AWS-Specific Parameter Types
```yaml
Parameters:
# AWS-specific types for validation
UserArn:
Type: AWS::IAM::User::Arn
Description: IAM user ARN for reference
RoleArn:
Type: AWS::IAM::Role::Arn
Description: IAM role ARN for reference
PolicyArn:
Type: AWS::IAM::Policy::Arn
Description: IAM policy ARN
ManagedPolicyArn:
Type: AWS::IAM::ManagedPolicy::Arn
Description: AWS managed policy ARN
InstanceProfileArn:
Type: AWS::IAM::InstanceProfile::Arn
Description: IAM instance profile ARN
S3BucketPolicy:
Type: AWS::S3::BucketPolicy::Resource
Description: S3 bucket policy reference
```
### Parameter Constraints
```yaml
Parameters:
UserName:
Type: String
Default: app-user
Description: IAM username
MinLength: 1
MaxLength: 64
ConstraintDescription: Must be 1-64 characters
AllowedPattern: "[a-zA-Z0-9+=,.@_-]+"
RoleName:
Type: String
Default: execution-role
Description: IAM role name
MinLength: 1
MaxLength: 64
ConstraintDescription: Must be 1-64 characters
AllowedPattern: "[a-zA-Z0-9+=,.@_-]+"
MaxSessionDuration:
Type: Number
Default: 3600
Description: Maximum session duration in seconds
MinValue: 900
MaxValue: 43200
ConstraintDescription: Must be between 900 and 43200 seconds
AccessKeyRotationFrequency:
Type: Number
Default: 90
Description: Days between access key rotations
MinValue: 1
MaxValue: 365
ConstraintDescription: Must be between 1 and 365 days
```
### SSM Parameter References per Policy ARNs
```yaml
Parameters:
ReadOnlyPolicyArn:
Type: AWS::SSM::Parameter::Value<String>
Default: /iam/policies/read-only-arn
Description: ARN of the read-only policy from SSM
CustomPolicyDocument:
Type: AWS::SSM::Parameter::Value<String>
Default: /iam/policies/custom-policy-json
Description: Policy document from SSM Parameter Store
```
## Outputs and Cross-Stack References
### Export/Import Patterns for IAM
```yaml
# Stack A - IAM Core Stack
AWSTemplateFormatVersion: 2010-09-09
Description: Core IAM infrastructure stack
Resources:
# Execution Role for applications
ApplicationExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-execution-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
MaxSessionDuration: 3600
# Role for Cross-Account Access
CrossAccountReadRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-crossaccount-read"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${TargetAccountId}:root"
Action: sts:AssumeRole
Condition:
StringEquals:
sts:Externalid: !Ref ExternalId
Policies:
- PolicyName: ReadOnlyAccess
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
Resource: !Ref SourceBucketArn
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
Resource: !Ref SourceTableArn
Outputs:
ApplicationExecutionRoleArn:
Description: ARN of the application execution role
Value: !GetAtt ApplicationExecutionRole.Arn
Export:
Name: !Sub "${AWS::StackName}-ExecutionRoleArn"
CrossAccountReadRoleArn:
Description: ARN for cross-account read access
Value: !GetAtt CrossAccountReadRole.Arn
Export:
Name: !Sub "${AWS::StackName}-CrossAccountReadRoleArn"
CrossAccountReadRoleExternalId:
Description: External ID for cross-account role assumption
Value: !Ref ExternalId
Export:
Name: !Sub "${AWS::StackName}-CrossAccountExternalId"
```
```yaml
# Stack B - Application Stack (imports from IAM Stack)
AWSTemplateFormatVersion: 2010-09-09
Description: Application stack importing IAM roles
Parameters:
IAMStackName:
Type: String
Default: iam-core
Description: Name of the IAM stack
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-processor"
Runtime: python3.11
Handler: app.handler
Code:
S3Bucket: !Ref CodeBucket
S3Key: lambda/function.zip
Role: !ImportValue
!Sub "${IAMStackName}-ExecutionRoleArn"
Environment:
Variables:
TARGET_BUCKET: !Ref TargetBucket
```
### Nested Stacks for IAM Modularity
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Main stack with nested IAM stacks
Resources:
# Nested stack for users
IAMUsersStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/bucket/iam-users.yaml
TimeoutInMinutes: 15
Parameters:
Environment: !Ref Environment
UserNames: !Ref UserNames
# Nested stack for roles
IAMRolesStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/bucket/iam-roles.yaml
TimeoutInMinutes: 15
Parameters:
Environment: !Ref Environment
TrustedServices: !Ref TrustedServices
# Nested stack for policies
IAMPoliciesStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/bucket/iam-policies.yaml
TimeoutInMinutes: 15
Parameters:
Environment: !Ref Environment
```
## IAM Users
### User with Access Keys
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: IAM user with programmatic access
Resources:
AppUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub "${AWS::StackName}-app-user"
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Project
Value: !Ref ProjectName
UserAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref AppUser
Status: Active
Serial: 1
UserSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/iam-user-credentials"
Description: IAM user access key credentials
SecretString: !Sub |
{
"username": "${AppUser.UserName}",
"access_key": "${UserAccessKey.Ref}",
"secret_key": "{{resolve:secretsmanager:${UserAccessKey.SecretAccessKey}}}"
}
Outputs:
AccessKeyId:
Description: Access Key ID for the user
Value: !Ref UserAccessKey
Export:
Name: !Sub "${AWS::StackName}-AccessKeyId"
SecretArn:
Description: ARN of the secret containing credentials
Value: !Ref UserSecret
```
### User with Console Password
```yaml
Resources:
ConsoleUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub "${AWS::StackName}-console-user"
UserLoginProfile:
Type: AWS::IAM::UserLoginProfile
Properties:
UserName: !Ref ConsoleUser
Password: !Ref InitialPassword
PasswordResetRequired: true
UserPasswordSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${AWS::StackName}/console-password"
Description: Initial console login password
SecretString: !Ref InitialPassword
```
### User with Permissions Boundary
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: IAM user with permissions boundary
Resources:
# Permissions boundary policy
ReadOnlyBoundaryPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Read-only permissions boundary
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Deny
Action: "*"
Resource: "*"
NotPrincipal:
- !GetAtt ReadOnlyRole.Arn
AppUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub "${AWS::StackName}-restricted-user"
PermissionsBoundary: !Ref ReadOnlyBoundaryPolicy
ManagedPolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
```
## IAM Roles
### Role for Lambda Execution
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda execution role with least privilege
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-lambda-execution"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
Policies:
- PolicyName: !Sub "${AWS::StackName}-dynamodb-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: !GetAtt DataTable.Arn
Condition:
StringEquals:
dynamodb:TableName: !Ref TableName
- Effect: Allow
Action:
- dynamodb:DescribeTable
Resource: "*"
- PolicyName: !Sub "${AWS::StackName}-secrets-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource: !Ref SecretsArn
- PolicyName: !Sub "${AWS::StackName}-kms-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- kms:Decrypt
- kms:DescribeKey
Resource: !Ref KmsKeyArn
DataTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Ref TableName
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
- AttributeName: sk
KeyType: RANGE
```
### Role for ECS Tasks
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: ECS task execution role
Resources:
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-ecs-task-execution"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Policies:
- PolicyName: !Sub "${AWS::StackName}-ecr-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:BatchCheckLayerAvailability
Resource: !Ref EcrRepositoryArn
- Effect: Allow
Action:
- ecr:GetAuthorizationToken
Resource: "*"
- PolicyName: !Sub "${AWS::StackName}-cloudwatch-logs"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
- logs:CreateLogGroup
Resource: !Ref LogGroupArn
ECSTaskRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-ecs-task"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub "${AWS::StackName}-app-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- sqs:ReceiveMessage
- sqs:DeleteMessage
- sqs:GetQueueAttributes
Resource: !GetAtt Queue.Arn
- Effect: Allow
Action:
- sns:Publish
Resource: !Ref TopicArn
```
### Role for Cross-Account Access
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Cross-account access role
Parameters:
SourceAccountId:
Type: String
Description: AWS account ID that can assume this role
ExternalId:
Type: String
Description: External ID for trust relationship
Resources:
CrossAccountReadRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-crossaccount-read"
Description: Role for cross-account read access
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${SourceAccountId}:root"
Action: sts:AssumeRole
Condition:
StringEquals:
sts:Externalid: !Ref ExternalId
IpAddress:
aws:SourceIp:
- 10.0.0.0/8
- 172.16.0.0/12
MaxSessionDuration: 7200
Policies:
- PolicyName: !Sub "${AWS::StackName}-s3-read"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
- s3:ListBucket
Resource:
- !Ref SourceBucketArn
- !Sub "${SourceBucketArn}/*"
- PolicyName: !Sub "${AWS::StackName}-dynamodb-read"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:DescribeTable
Resource:
- !GetAtt SourceTable.Arn
- !Sub "${GetAtt SourceTable.Arn}/index/*"
Outputs:
RoleArn:
Description: ARN of the cross-account role
Value: !GetAtt CrossAccountReadRole.Arn
Export:
Name: !Sub "${AWS::StackName}-CrossAccountRoleArn"
```
### Role for API Gateway with Cognito
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: API Gateway execution role for Cognito authorization
Resources:
ApiGatewayCognitoAuthorizerRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-apigw-cognito-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub "${AWS::StackName}-cognito-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- cognito-idp:DescribeUserPool
- cognito-idp:DescribeUserPoolClient
Resource: !Ref UserPoolArn
ApiGatewayExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-apigw-execution"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
Policies:
- PolicyName: !Sub "${AWS::StackName}-lambda-invoke"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource: !Ref LambdaFunctionArn
```
### Role for EKS Pods
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: IAM role for EKS pod execution
Resources:
EKSPodRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-eks-pod-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: eks.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceArn: !Sub "arn:aws:eks:${AWS::Region}:${AWS::AccountId}:cluster/${EKSClusterName}"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/SecretsManagerReadWrite
Policies:
- PolicyName: !Sub "${AWS::StackName}-s3-read"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
Resource: !Sub "${DataBucketArn}/*"
EKSPodExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-eks-execution"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: eks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
- arn:aws:iam::aws:policy/CloudWatchLogsReadOnly
```
### Role for CodeBuild
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: IAM role for CodeBuild project
Resources:
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-codebuild-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess
Policies:
- PolicyName: !Sub "${AWS::StackName}-source-access"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
Resource:
- !Ref SourceBucketArn
- !Sub "${SourceBucketArn}/*"
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Ref BuildOutputBucketArn
- !Sub "${BuildOutputBucketArn}/*"
- Effect: Allow
Action:
- codecommit:GitPull
Resource: !Ref CodecommitRepositoryArn
- Effect: Allow
Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
- codebuild:BatchPutCodeCoverages
Resource: "*"
```
### Role for Step Functions
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: IAM role for Step Functions state machine
Resources:
StepFunctionsExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-stepfunctions-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: states.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub "${AWS::StackName}-lambda-tasks"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
- lambda:InvokeAsync
Resource:
- !Ref ProcessFunctionArn
- !Ref ValidateFunctionARN
- !Ref NotifyFunctionArn
- PolicyName: !Sub "${AWS::StackName}-dynamodb-tasks"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:Query
- dynamodb:Scan
Resource:
- !GetAtt WorkflowTable.Arn
- !Sub "${GetAtt WorkflowTable.Arn}/*"
- PolicyName: !Sub "${AWS::StackName}-sns-tasks"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- sns:Publish
Resource: !Ref NotificationTopicArn
- PolicyName: !Sub "${AWS::StackName}-events"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- events:PutTargets
- events:PutRule
- events:DescribeRule
Resource: "*"
```
## IAM Policies
### Inline Policy for S3 Access
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Role with S3 access policies
Resources:
S3AccessRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-s3-access"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub "${AWS::StackName}-s3-readonly"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketPolicy
- s3:GetBucketPolicyStatus
Resource:
- !Ref DataBucketArn
- !Sub "${DataBucketArn}/*"
- Effect: Allow
Action:
- s3:ListBucket
Resource: !Ref DataBucketArn
Condition:
StringLike:
s3:prefix:
- ""
- "documents/*"
- "reports/*"
- PolicyName: !Sub "${AWS::StackName}-s3-write"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:PutObjectAcl
- s3:DeleteObject
Resource: !Sub "${DataBucketArn}/processed/*"
```
### Custom Managed Policy
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Custom managed policy for DynamoDB access
Resources:
DynamoDBFullAccessPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Full access to specific DynamoDB tables
ManagedPolicyName: !Sub "${AWS::StackName}-dynamodb-full-access"
Groups:
- !Ref AppUserGroup
Roles:
- !Ref AppExecutionRole
Users:
- !Ref AppUser
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- dynamodb:CreateTable
- dynamodb:UpdateTable
- dynamodb:DeleteTable
- dynamodb:DescribeTable
- dynamodb:DescribeTimeToLive
- dynamodb:ListTagsOfResource
Resource: !GetAtt DataTable.Arn
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:BatchGetItem
- dynamodb:BatchWriteItem
Resource:
- !GetAtt DataTable.Arn
- !Sub "${GetAtt DataTable.Arn}/index/*"
- Effect: Allow
Action:
- dynamodb:DescribeLimits
- dynamodb:ListTables
Resource: "*"
```
### Policy with Conditions
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Policy with IP and time-based conditions
Resources:
RestrictedAccessRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-restricted-access"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub "${AWS::StackName}-conditional-access"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource: !Sub "${DataBucketArn}/*"
Condition:
IpAddress:
aws:SourceIp:
- 10.0.0.0/8
- 192.168.1.0/24
StringEquals:
s3:ExistingObjectTag/classification: internal
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:Query
Resource: !GetAtt DataTable.Arn
Condition:
StringEquals:
dynamodb:Select: SPECIFIC_ATTRIBUTES
dynamodb:Attributes:
- id
- name
- status
```
## Permission Boundaries
### Permission Boundary for Developers
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Permission boundary for developer roles
Resources:
DeveloperBoundaryPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Permission boundary for developers - denies production write access
ManagedPolicyName: !Sub "${AWS::StackName}-developer-boundary"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Deny
Action:
- "*"
Resource: "*"
Condition:
StringEquals:
aws:RequestedRegion:
- us-east-1
- us-west-2
StringLike:
aws:ResourceTag/environment:
- production
- prod
- Effect: Deny
Action:
- iam:CreateUser
- iam:DeleteUser
- iam:PutUserPolicy
- iam:AttachUserPolicy
- iam:DetachUserPolicy
Resource: "*"
- Effect: Deny
Action:
- iam:CreateRole
- iam:DeleteRole
- iam:AttachRolePolicy
- iam:DetachRolePolicy
Resource: "*"
- Effect: Allow
Action:
- s3:Get*
- s3:List*
Resource: "*"
- Effect: Allow
Action:
- dynamodb:Get*
- dynamodb:Query
- dynamodb:Scan
Resource: "*"
- Effect: Allow
Action:
- lambda:InvokeFunction
- lambda:GetFunction
Resource: "*"
DeveloperRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-developer"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Ref DeveloperUserArn
Action: sts:AssumeRole
PermissionsBoundary: !Ref DeveloperBoundaryPolicy
ManagedPolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
```
## IAM Identity Center (SSO)
### SSO Permission Set
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: IAM Identity Center permission set
Resources:
SSOPermissionSet:
Type: AWS::SSO::PermissionSet
Properties:
InstanceArn: !Ref SSOInstanceArn
PermissionSetName: !Sub "${AWS::StackName}-admin-permissions"
Description: Administrator access permission set
SessionDuration: PT8H
RelayStateType: sso.amazonaws.com
ManagedPolicies:
- arn:aws:iam::aws:policy/AdministratorAccess
InlinePolicy: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"aws-portal:*Billing",
"aws-portal:*Usage"
],
"Resource": "*"
}
]
}
SSOAssignment:
Type: AWS::SSO::AccountAssignment
Properties:
InstanceArn: !Ref SSOInstanceArn
PermissionSetArn: !Ref SSOPermissionSet
PrincipalId: !Ref SSOGroupId
PrincipalType: GROUP
TargetId: !Ref TargetAWSAccountId
TargetType: AWS_ACCOUNT
```
### SSO Custom Policy with Conditions
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: SSO permission set with custom policies
Resources:
ReadOnlyPermissionSet:
Type: AWS::SSO::PermissionSet
Properties:
InstanceArn: !Ref SSOInstanceArn
PermissionSetName: !Sub "${AWS::StackName}-read-only"
Description: Read-only access for auditors
SessionDuration: PT4H
ManagedPolicies:
- arn:aws:iam::aws:policy/ReadOnlyAccess
InlinePolicy: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::${DataBucketArn}",
"arn:aws:s3:::${DataBucketArn}/*"
]
},
{
"Effect": "Deny",
"Action": [
"s3:DeleteObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::${DataBucketArn}/*"
]
}
]
}
```
## Service Control Policies (SCP)
### SCP for Organizational Units
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Service control policy for production OU
Resources:
ProductionSCP:
Type: AWS::Organizations::Policy
Properties:
Name: !Sub "${AWS::StackName}-production-restrictions"
Description: SCP to restrict actions in production accounts
Type: SERVICE_CONTROL_POLICY
Content:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDeleteResources",
"Effect": "Deny",
"Action": [
"s3:DeleteBucket",
"dynamodb:DeleteTable",
"rds:DeleteDBInstance",
"lambda:DeleteFunction"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/environment": "production"
}
}
},
{
"Sid": "RequireEncryption",
"Effect": "Deny",
"Action": [
"s3:PutObject",
"dynamodb:PutItem"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
},
{
"Sid": "DenyRootAccess",
"Effect": "Deny",
"Action": [
"iam:CreateAccessKey",
"iam:CreateLoginProfile",
"iam:EnableMFADevice"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/role": "breakglass"
}
}
},
{
"Sid": "AllowKnownServices",
"Effect": "Allow",
"Action": "*",
"Resource": "*",
"NotPrincipal": {
"AWS": [
"arn:aws:iam::aws:policy/AdministratorAccess"
]
}
}
]
}
```
## Conditions and Transform
### Conditions for Environment-Specific Roles
```yaml
AWSTemplateFormatVersion: 2010-09-09
Description: IAM resources with conditional configurations
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
Conditions:
IsProduction: !Equals [!Ref Environment, production]
IsDevelopment: !Equals [!Ref Environment, dev]
CreateAdminRole: !Or [!Equals [!Ref Environment, staging], !Equals [!Ref Environment, production]]
Resources:
# Base role for all environments
BaseExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-base-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
MaxSessionDuration: !FindInMap [EnvironmentConfig, !Ref Environment, MaxSessionDuration]
Policies:
- PolicyName: !Sub "${AWS::StackName}-base-policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
# Admin role only for staging and production
AdminRole:
Type: AWS::IAM::Role
Condition: CreateAdminRole
Properties:
RoleName: !Sub "${AWS::StackName}-admin-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Ref AdminUserArn
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/ReadOnlyAccess
- !If
- IsProduction
- arn:aws:iam::aws:policy/ViewBilling
- !Ref AWS::NoValue
Mappings:
EnvironmentConfig:
dev:
MaxSessionDuration: 3600
staging:
MaxSessionDuration: 7200
production:
MaxSessionDuration: 43200
```
### Transform for Policy Reuse
```yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Using SAM Transform for IAM patterns
Globals:
Function:
Timeout: 30
Runtime: python3.11
Policies:
- S3ReadPolicy:
BucketName: !Ref DataBucket
- DynamoDBCrudPolicy:
TableName: !Ref DataTable
- SecretsManagerReadWrite:
SecretArn: !Ref AppSecret
Resources:
ProcessorFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-processor"
Handler: app.handler
CodeUri: lambda_function/
Environment:
Variables:
LOG_LEVEL: INFO
```
## Best Practices
### Security
- Always apply least privilege: start with minimal permissions and add only what's needed
- Use IAM Access Analyzer to identify excessive permissions
- Implement permission boundaries to limit maximum permissions
- Use condition keys for granular restrictions (aws:SourceIp, aws:ResourceTag, aws:RequestedRegion)
- Enable MFA for all IAM users
- Rotate access keys regularly (max 90 days)
- Use roles instead of long-term credentials where possible
- Implement external ID for cross-account trust
- Use service-linked roles for AWS services
### Template Organization
- Separate IAM into dedicated stacks for clear ownership
- Use nested stacks for modularity
- Export only necessary values for cross-stack references
- Always document intent with Description fields
- Use Parameters for environment-specific configuration
- Implement conditions to manage variants without duplication
- Tag all IAM resources for tracking and cost allocation
### Monitoring
- Enable CloudTrail for audit of all IAM actions
- Configure S3 Object Lock for critical policy files
- Use IAM Access Analyzer for periodic reports
- Implement SCP for organizational guardrails
- Create alarms for unauthorized access
### Compliance
- Implement separation of duties policies
- Use AWS managed policies for standard compliance
- Document all deviations from standards
- Maintain audit trail for at least 90 days
- Apply encryption at rest and in transit
## CloudFormation Stack Management Best Practices
### Stack Policies
Stack Policies prevent unintended updates to critical resources during stack updates.
```yaml
Resources:
# Stack Policy to protect IAM roles from updates
IAMStackPolicy:
Type: AWS::CloudFormation::StackPolicy
Properties:
PolicyDocument:
Statement:
- Effect: Deny
Action: Update:Replace
UpdateReplacePolicy: Retain
Resource: "*"
Condition:
StringEquals:
aws:ResourceTag/Protected: "true"
- Effect: Allow
Action: Update:*
Resource: "*"
```
### Termination Protection
Enable termination protection to prevent accidental stack deletion.
```yaml
Resources:
ProductionStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/bucket/iam-template.yaml
TerminationProtection: true
```
CLI commands for termination protection:
```bash
# Enable termination protection
aws cloudformation update-termination-protection \
--stack-name my-iam-stack \
--enable-termination-protection
# Disable termination protection
aws cloudformation update-termination-protection \
--stack-name my-iam-stack \
--no-enable-termination-protection
```
### Drift Detection
Detect when infrastructure has diverged from the template definition.
```bash
# Detect drift on a stack
aws cloudformation detect-drift \
--stack-name my-iam-stack
# Get drift detection status
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id <detection-id>
# Get drift detection results
aws cloudformation describe-stack-resource-drifts \
--stack-name my-iam-stack
```
### Change Sets
Preview and review changes before executing them.
```bash
# Create a change set
aws cloudformation create-change-set \
--stack-name my-iam-stack \
--change-set-name my-changeset \
--template-body file://template.yaml \
--capabilities CAPABILITY_IAM
# List change sets
aws cloudformation list-change-sets \
--stack-name my-iam-stack
# Describe change set
aws cloudformation describe-change-set \
--stack-name my-iam-stack \
--change-set-name my-changeset
# Execute change set
aws cloudformation execute-change-set \
--stack-name my-iam-stack \
--change-set-name my-changeset
# Delete change set (if not executing)
aws cloudformation delete-change-set \
--stack-name my-iam-stack \
--change-set-name my-changeset
```
## Related Resources
- [IAM Documentation](https://docs.aws.amazon.com/iam/)
- [AWS CloudFormation User Guide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/)
- [IAM Best Practices](https://docs.aws.amazon.com/iam/latest/UserGuide/best-practices.html)
- [IAM Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html)
## Constraints and Warnings
### Resource Limits
- **Roles Limits**: Maximum 1000 IAM roles per AWS account
- **Policies Limits**: Maximum number of managed and inline policies per account
- **Policy Size**: IAM policy documents cannot exceed 2048 characters (inline) or 6144 characters (managed)
- **Users Limits**: Maximum 5000 IAM users per AWS account
### Security Constraints
- **Principal Limits**: IAM trust policies have limits on number of principals
- **Managed Policy Updates**: Changes to managed policies affect all attached entities immediately
- **Access Analyzer**: Access Analyzer requires specific permissions to analyze resources
- **Credential Rotation**: IAM credentials should be rotated regularly; no automatic rotation for access keys
### Operational Constraints
- **Role Deletion**: Roles cannot be deleted while still referenced by resources
- **Policy Detachment**: Detaching policies requires explicit action; deleting attached roles fails
- **MFA Delete**: Disabling MFA requires MFA authentication itself
- **Root Account**: Root account has no restrictions; should never be used for daily operations
### Permissions Constraints
- **Policy Evaluation**: IAM policies are size-limited and complex to write correctly
- **Service Last Access Data**: Service last access data is updated only every 4 hours
- **Simulate Principal Policy**: Simulation can only approximate policy evaluation results
- **Condition Keys**: Not all services support all condition keys in policies
### Cost Considerations
- **Access Analyzer**: Access Analyzer usage generates costs based on number of analyzes
- **Credential Report**: Generating credential reports doesn't directly cost but consumes API calls
- **Custom Managed Policies**: Creating many custom managed policies increases management overhead
- **IAM Access Advisor**: Access advisor provides recommendations but doesn't directly cost
### Best Practice Reminders
- **Least Privilege**: Always grant minimum required permissions
- **Policy Versioning**: Managed policies support versioning for safer updates
- **Resource Tags**: Use resource tags for permission boundaries where possible
- **MFA Enforcement**: Require MFA for privileged operations in production
## Additional Files
For complete details on IAM resources and their properties, see:
- [REFERENCE.md](references/reference.md) - Detailed reference guide for all CloudFormation IAM resources
- [EXAMPLES.md](references/examples.md) - Complete production-ready examples
This skill provides production-ready AWS CloudFormation patterns for creating and managing IAM users, roles, policies, and managed policies. It focuses on implementing least-privilege, permission boundaries, cross-account access, and modular template structure to support secure, repeatable deployments. Use the patterns to standardize IAM infrastructure across environments and teams.
The skill delivers CloudFormation template patterns and examples that define Parameters, Mappings, Conditions, Resources, and Outputs for IAM constructs. It includes ready-made role trust policies, inline and managed policy documents, permission boundary templates, cross-account role patterns using STS, and export/import patterns for cross-stack references. Templates are designed for modularity (nested stacks) and environment-specific configuration via mappings and parameters.
How do I enforce least privilege across multiple stacks?
Export centralized policy and role ARNs from an IAM core stack and import them into application stacks; define narrow resource ARNs and specific actions in policy documents.
When should I use permission boundaries vs. managed policies?
Use permission boundaries to cap the maximum permissions an identity can obtain. Use managed policies to grant specific sets of permissions that can be attached and versioned.
Can I include secrets like access keys in templates?
Avoid embedding secrets in templates. Generate keys in CloudFormation but store credentials in Secrets Manager and reference the secret ARN in outputs if necessary.