Remote Development on AWS: from Cloud9 to VS Code
20 November 2024 - 2 min. read
Alessio Gandini
Cloud-native Development Line Manager
--- 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:
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.