Nightmare infrastructure – episode 4. Just when you thought it couldn’t get any worse...
 29 October 2025 - 1 min. read 
  
  Damiano Giorgi 
  DevOps Engineer 
   Day by day the number of companies that look with interest at the cloud computing world increases and many of them wonder how the cloud can help their business and which security flaws it can introduce. One of the most complicated tasks  of the security team in fact is to understand how to exploit novel cloud technologies, with their unprecedented flexibility, without introducing security vulnerabilities. One of the most useful tools to enforce compliance with security best practices is certainly Infrastructure as Code (IaC), which allows us to handle our resources inside the cloud environment through templating systems. This makes it possible to deploy the infrastructure in a repeatable and reproducible way.
Day by day the number of companies that look with interest at the cloud computing world increases and many of them wonder how the cloud can help their business and which security flaws it can introduce. One of the most complicated tasks  of the security team in fact is to understand how to exploit novel cloud technologies, with their unprecedented flexibility, without introducing security vulnerabilities. One of the most useful tools to enforce compliance with security best practices is certainly Infrastructure as Code (IaC), which allows us to handle our resources inside the cloud environment through templating systems. This makes it possible to deploy the infrastructure in a repeatable and reproducible way. More farsighted people know that to deploy this infrastructure the CloudFormation template needs to create an IAM Role for the EC2. Thus we need to make sure that the developer who will run the template will not be able to modify the policies of this role in order to give the EC2 (and thus himself) wider permission (e.g. admin access).
More farsighted people know that to deploy this infrastructure the CloudFormation template needs to create an IAM Role for the EC2. Thus we need to make sure that the developer who will run the template will not be able to modify the policies of this role in order to give the EC2 (and thus himself) wider permission (e.g. admin access).---
AWSTemplateFormatVersion: '2010-09-09'
Description: |
 CloudFormation and IAM Permission Boundaries Demo
 
 
################################################################################
#                                  Metadata                                    #
################################################################################
Metadata:
 
 AWS::CloudFormation::Interface:
   ParameterGroups:
     - Label: {default: 'Required parameters'}
       Parameters:
         - VpcId
         - AmiId
         - KeyName
         - SubnetId
     - Label: {default: 'Optional parameters'}
       Parameters:
         - NameSpace
         - ProjectName
         - Environment
 
 
################################################################################
#                                Parameters                                    #
################################################################################
Parameters:
 
 NameSpace:
   Type: String
   Default: 'besharp'
 
 ProjectName:
   Type: String
   Default: 'permission-boundaries-demo'
 
 Environment:
   Type: String
   Default: 'dev'
  VpcId:
   Type: AWS::EC2::VPC::Id
 
 SubnetId:
   Type: AWS::EC2::Subnet::Id
 
 AmiId:
   Type: AWS::EC2::Image::Id
 
 KeyName:
   Type: AWS::EC2::KeyPair::KeyName
 
 
################################################################################
#                                 Conditions                                   #
################################################################################
Conditions: {}
 
 
################################################################################
#                                  Mappings                                    #
################################################################################
Mappings: {}
 
 
################################################################################
#                                  Resources                                   #
################################################################################
Resources:
 
 #################################### S3 ####################################
 S3Bucket:
   Type: AWS::S3::Bucket
   Properties:
     BucketName: !Sub 'com.${NameSpace}.${ProjectName}'
     Tags:
       - Key: Name
         Value: !Sub '${NameSpace}-${ProjectName}'
       - Key: Owner
         Value: 'name.surname@besharp.it'
 
 ################################### EC2 ####################################
 EC2Instance:
   Type: AWS::EC2::Instance
   Properties:
     IamInstanceProfile: !Ref IAMInstanceProfile
     ImageId: !Ref AmiId
     InstanceType: t3a.micro
     KeyName: !Ref KeyName
     NetworkInterfaces:
       - AssociatePublicIpAddress: 'true'
         DeviceIndex: '0'
         GroupSet:
           - !Ref EC2SecurityGroup
         SubnetId: !Ref SubnetId
     Tags:
       - Key: Name
         Value: !Sub '${NameSpace}-${ProjectName}'
       - Key: Owner
         Value: 'name.surname@besharp.it'
 
 EC2SecurityGroup:
   Type: AWS::EC2::SecurityGroup
   Properties:
     GroupName: !Sub '${NameSpace}-${ProjectName}-ec2'
     GroupDescription: !Sub 'Security Group for ${NameSpace}-${ProjectName}-ec2'
     VpcId: !Ref VpcId
     Tags:
       - Key: Name
         Value: !Sub '${NameSpace}-${ProjectName}-ec2'
       - Key: Owner
         Value: 'name.surname@besharp.it'
 
 ################################### IAM ####################################
 IAMInstanceProfile:
   Type: AWS::IAM::InstanceProfile
   Properties:
     Roles:
       - !Ref IAMRole
     InstanceProfileName: !Sub '${NameSpace}-${ProjectName}'
 
 IAMRole:
   Type: AWS::IAM::Role
   Properties:
     RoleName: !Sub '${NameSpace}-${ProjectName}'
     AssumeRolePolicyDocument:
       Version: '2012-10-17'
       Statement:
         - Effect: Allow
           Principal:
             Service: ec2.amazonaws.com
           Action: sts:AssumeRole
     Path: '/'
     Policies:
       - PolicyName: 'EC2Access'
         PolicyDocument:
           Version: '2012-10-17'
           Statement:
             - Effect: 'Allow'
               Action:
                 - 's3:GetObject'
               Resource: !Sub '${S3Bucket.Arn}/*'
     Tags:
       - Key: Name
         Value: !Sub '${NameSpace}-${ProjectName}'
       - Key: Owner
         Value: 'name.surname@besharp.it'
 
 
################################################################################
#                                   Outputs                                    #
################################################################################
Outputs:
 
 StackName:
   Description: 'Stack name.'
   Value: !Sub '${AWS::StackName}'
Here we can find the information to create:{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Sid": "CloudFormationReadAccess",
           "Effect": "Allow",
           "Action": [
               "cloudformation:DescribeStacks",
               "cloudformation:ListChangeSets",
               "cloudformation:ListExports",
               "cloudformation:ListImports",
               "cloudformation:ListStacks"
           ],
           "Resource": "*"
       },
       {
           "Sid": "CloudFormationWriteAccess",
           "Effect": "Allow",
           "Action": [
               "cloudformation:CreateStack",
               "cloudformation:DeleteStack",
               "cloudformation:TagResource",
               "cloudformation:UpdateStack",
               "cloudformation:ValidateTemplate"
           ],
           "Resource": "*"
       },
       {
           "Sid": "EC2ReadAccess",
           "Effect": "Allow",
           "Action": [
               "ec2:DescribeImages",
               "ec2:DescribeInstances",
               "ec2:DescribeKeyPairs",
               "ec2:DescribeSecurityGroupReferences",
               "ec2:DescribeSecurityGroups",
               "ec2:DescribeSubnets",
               "ec2:DescribeVpcs"
           ],
           "Resource": "*"
       },
       {
           "Sid": "EC2WriteAccess",
           "Effect": "Allow",
           "Action": [
               "ec2:AssociateIamInstanceProfile",
               "ec2:AuthorizeSecurityGroupEgress",
               "ec2:AuthorizeSecurityGroupIngress",
               "ec2:CreateSecurityGroup",
               "ec2:CreateTags",
               "ec2:DeleteSecurityGroup",
               "ec2:DeleteTags",
               "ec2:RevokeSecurityGroupEgress",
               "ec2:RevokeSecurityGroupIngress",
               "ec2:RunInstances",
               "ec2:StartInstances",
               "ec2:StopInstances",
               "ec2:TerminateInstances"
           ],
           "Resource": "*"
       },
       {
           "Sid": "IAMReadAccess",
           "Effect": "Allow",
           "Action": [
               "iam:GetInstanceProfile",
               "iam:GetRole",
               "iam:GetRolePolicy"
           ],
           "Resource": "*"
       },
       {
           "Sid": "IAMWriteAccess",
           "Effect": "Allow",
           "Action": [
               "iam:AddRoleToInstanceProfile",
               "iam:CreateInstanceProfile",
               "iam:CreateRole",
               "iam:DeleteInstanceProfile",
               "iam:DeleteRole",
               "iam:DeleteRolePolicy",
               "iam:PassRole",
               "iam:PutRolePolicy",
               "iam:RemoveRoleFromInstanceProfile",
               "iam:TagRole",
               "iam:UntagRole"
           ],
           "Resource": "*"
       },
       {
           "Sid": "S3WriteAccess",
           "Effect": "Allow",
           "Action": [
               "s3:CreateBucket",
               "s3:DeleteBucket",
               "s3:PutBucketTagging"
           ],
           "Resource": "*"
       }
   ]
}
If we connect to the newly created EC2 instance, we can verify that it can download objects from the S3 Bucket:ubuntu@ec2-demo:~$ aws s3 cp s3://com.besharp.permission-boundaries-demo/if-you-download-me-you-are-fine . download: s3://com.besharp.permission-boundaries-demo/if-you-download-me-you-are-fine to ./if-you-download-me-you-are-fine ubuntu@ec2-demo:~$ cat if-you-download-me-you-are-fine test-okAnd that it can’t, for example, create other buckets:
ubuntu@ec2-demo:~$ aws s3api create-bucket --bucket can-i-create-it An error occurred (AccessDenied) when calling the CreateBucket operation: Access DeniedNow, the developer (regardless of the good or bad intention) might want to modify the set of permissions attached to the role to execute actions that normally he is not authorized to do. The IAM Role resource can be modified like this:
 
 IAMRole:
   Type: AWS::IAM::Role
   Properties:
     RoleName: !Sub '${NameSpace}-${ProjectName}'
     AssumeRolePolicyDocument:
       Version: '2012-10-17'
       Statement:
         - Effect: Allow
           Principal:
             Service: ec2.amazonaws.com
           Action: sts:AssumeRole
     Path: '/'
     Policies:
       - PolicyName: 'AdministratorAccess'
         PolicyDocument:
           Version: '2012-10-17'
           Statement:
             - Effect: 'Allow'
               Action:
                 - '*'
               Resource: '*'
     Tags:
       - Key: Name
         Value: !Sub '${NameSpace}-${ProjectName}'
In this way, for example, the instance can download objects from the bucket, but also create new buckets (not to mention that, modifying the policy roughly like that, in fact it has administrator permissions inside the account):ubuntu@ec2-demo:~$ aws s3 cp s3://com.besharp.permission-boundaries-demo/if-you-download-me-you-are-fine .
 
download: s3://com.besharp.permission-boundaries-demo/if-you-download-me-you-are-fine to ./if-you-download-me-you-are-fine
ubuntu@ec2-demo:~$ cat if-you-download-me-you-are-fine
test-ok
ubuntu@ec2-demo:~$ aws s3api create-bucket --bucket can-i-create-it
{
   "Location": "/can-i-create-it"
}
Clearly the principal concern is not only the fact that a developer can gain unintended permissions, but also the increased attack surface exploitable from malicious entities: the old style linux privileges escalation ported to the cloud!{
           "Sid": "IAMPermissionBoundaryWriteAccess",
           "Effect": "Allow",
           "Action": [
               "iam:CreateRole",
               "iam:PutRolePolicy",
               "iam:UpdateRole",
               "iam:UpdateRoleDescription"
           ],
           "Resource": "arn:aws:iam::111122223333:role/dev-namespace/*",
           "Condition": {
               "StringEquals": {
                   "iam:PermissionsBoundary": "arn:aws:iam::111122223333:policy/besharp-permission-boundary-demo"
               }
           }
       },
       {
           "Sid": "IAMPassRoleAccess",
           "Effect": "Allow",
           "Action": [
               "iam:PassRole"
           ],
           "Resource": "arn:aws:iam::111122223333:role/dev-namespace/*"
       },
       {
           "Sid": "IAMWriteAccess",
           "Effect": "Allow",
           "Action": [
               "iam:AddRoleToInstanceProfile",
               "iam:CreateInstanceProfile",
               "iam:DeleteInstanceProfile",
               "iam:DeleteRole",
               "iam:DeleteRolePolicy",
               "iam:RemoveRoleFromInstanceProfile",
               "iam:TagRole",
               "iam:UntagRole"
           ],
           "Resource": "*"
       },
If during the creation phase of the IAM role through CloudFormation is not present the permission boundary attachment, the creation will fail. To attach the permission boundary to the instance IAM role, the resource will be modified as following:
To attach the permission boundary to the instance IAM role, the resource will be modified as following: IAMRole:
   Type: AWS::IAM::Role
   Properties:
     RoleName: !Sub '${NameSpace}-${ProjectName}'
     PermissionsBoundary: !Ref PermissionBoundaryArn
     AssumeRolePolicyDocument:
       Version: '2012-10-17'
       Statement:
         - Effect: Allow
           Principal:
             Service: ec2.amazonaws.com
           Action: sts:AssumeRole
     Path: '/dev-namespace/'
     Policies:
       - PolicyName: 'AdministratorAccess'
         PolicyDocument:
           Version: '2012-10-17'
           Statement:
             Effect: 'Allow'
             Action:
                - '*'
             Resource: '*'
     Tags:
       - Key: Name
         Value: !Sub '${NameSpace}-${ProjectName}'
       - Key: Owner
         Value: 'name.surname@besharp.it'
Note: to make the solution safer and to limit the dangerous action iam:PassRole, we added the Path attribute, which in fact creates a namespace for developers inside IAM.Attaching the permission boundary, the creation phase will succeed and the instance permissions (and the ones of any IAM role that the developer wants to create) will be limited.ubuntu@ec2-demo:~$ aws s3 cp s3://com.besharp.permission-boundaries-demo/if-you-download-me-you-are-fine . download: s3://com.besharp.permission-boundaries-demo/if-you-download-me-you-are-fine to ./if-you-download-me-you-are-fine ubuntu@ec2-demo:~$ cat if-you-download-me-you-are-fine test-ok ubuntu@ec2-demo:~$ aws s3api create-bucket --bucket can-i-create-it An error occurred (AccessDenied) when calling the CreateBucket operation: Access DeniedIn this configuration permission boundaries are the IAM policies under Security team control. The team can agree on which is the maximum set of permissions that a resource can have and AWS IAM will assure that all resources with the restricted IAM role associated with them will not be able to perform unintended actions or to create another resource which can do so.