or run

tessl search
Log in

aws-cloudformation-iam

tessl install github:giuseppe-trisciuoglio/developer-kit --skill aws-cloudformation-iam

github.com/giuseppe-trisciuoglio/developer-kit

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.

Review Score

83%

Validation Score

10/16

Implementation Score

73%

Activation Score

100%

AWS CloudFormation IAM Security

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)

CloudFormation Template Structure

Standard Format Base Template

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

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

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

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

# 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"
# 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

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.

Resources:
  ProductionStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/bucket/iam-template.yaml
      TerminationProtection: true

CLI commands for termination protection:

# 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.

# 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.

# 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
  • AWS CloudFormation User Guide
  • IAM Best Practices
  • IAM Access Analyzer

Additional Files

For complete details on IAM resources and their properties, see:

  • REFERENCE.md - Detailed reference guide for all CloudFormation IAM resources
  • EXAMPLES.md - Complete production-ready examples