tessl install github:giuseppe-trisciuoglio/developer-kit --skill aws-cloudformation-iamgithub.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%
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.
Use this skill when:
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:
# 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 referenceParameters:
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 daysParameters:
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# 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 TargetBucketAWSTemplateFormatVersion: 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 EnvironmentAWSTemplateFormatVersion: 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 UserSecretResources:
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 InitialPasswordAWSTemplateFormatVersion: 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/ReadOnlyAccessAWSTemplateFormatVersion: 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: RANGEAWSTemplateFormatVersion: 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 TopicArnAWSTemplateFormatVersion: 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"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 LambdaFunctionArnAWSTemplateFormatVersion: 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/CloudWatchLogsReadOnlyAWSTemplateFormatVersion: 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: "*"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: "*"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/*"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: "*"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
- statusAWSTemplateFormatVersion: 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/ReadOnlyAccessAWSTemplateFormatVersion: 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_ACCOUNTAWSTemplateFormatVersion: 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}/*"
]
}
]
}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"
]
}
}
]
}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: 43200AWSTemplateFormatVersion: 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: INFOStack 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: "*"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: trueCLI 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-protectionDetect 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-stackPreview 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-changesetFor complete details on IAM resources and their properties, see: