{"id":1347,"date":"2020-04-30T18:04:16","date_gmt":"2020-04-30T16:04:16","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=1347"},"modified":"2021-03-24T12:22:02","modified_gmt":"2021-03-24T11:22:02","slug":"aws-cloudformation-aws-iam-how-to-delegate-deployment-safely","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/aws-cloudformation-aws-iam-how-to-delegate-deployment-safely\/","title":{"rendered":"AWS CloudFormation – AWS IAM: how to delegate deployment safely"},"content":{"rendered":"
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\u00a0 of the security team in fact is to understand how to exploit novel cloud technologies, with their unprecedented flexibility, without introducing security vulnerabilities.\u00a0<\/span><\/p>\n 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.<\/span><\/p>\n In this article we will focus on the AWS service called AWS CloudFormation.<\/span><\/p>\n CloudFormation lets you create, update and handle resources in your AWS Cloud Environment through the use of JSON or YAML templates in which you can describe resource by resource your own infrastructure.<\/span><\/p>\n <\/p>\n This leads to a huge set of questions: how can I have control on who deploy what? If I can handle each resource, can I also handle resources permissions? If I can handle permissions, how can I delegate the use of CloudFormation to other parties or team members without the risk of privileges escalation attacks?<\/span><\/p>\n To answer these questions we need to introduce another service: AWS Identity Access Management (AWS IAM).<\/span><\/p>\n AWS Identity and Access Management (IAM) is the AWS service that allows one to handle all permissions inside your AWS Cloud Environment. If you want to execute any action (using the Console, the CLI or the SDK) the permission to do so has to be written inside a policy attached to your \u201cuser\u201d.\u00a0<\/span><\/p>\n Now that we know which are the instruments we can use, let\u2019s describe a very common situation: we created a CloudFormation template that provisions an infrastructure composed by a pre-configured EC2 instance (through Amazon Machine Image) which needs to access an S3 bucket. Being a simple infrastructure, we would like to allow the developers to deploy this template without asking the Security team.<\/span><\/p>\n <\/p>\n 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).<\/span><\/p>\n The template that we will deploy is the following one:<\/span><\/p>\n <\/p>\n Here we can find the information to create:<\/span><\/p>\n We attached to the developer IAM user the following policy so that he can deploy that template:<\/span><\/p>\n <\/p>\n If we connect to the newly created EC2 instance, we can verify that it can download objects from the S3 Bucket: And that it can\u2019t, for example, create other buckets:<\/span><\/p>\n <\/p>\n Now, 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:<\/p>\n 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):<\/span><\/p>\n 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!<\/p>\n Permission boundaries are nothing more than additional IAM policies attached to an IAM entity to limit its permissions. Indeed, the resulting permissions will be the intersection between the ones granted by the IAM policy and the ones allowed by the permission boundary. By themselves, they do not resolve any problem but we can force the developer to attach the permission boundary if he wants to create a role.<\/p>\n The IAM policy part becomes like this:<\/p>\n If during the creation phase of the IAM role through CloudFormation is not present the permission boundary attachment, the creation will fail.<\/span><\/p>\n <\/p>\n To attach the permission boundary to the instance IAM role, the resource will be modified as following:<\/span><\/p>\n <\/p>\n 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.<\/p>\n 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.<\/p>\n In 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.<\/p>\n We have seen how, thanks to a simple modification of the IAM entities configurations (Permission Boundaries), you could enforce the limitation of permissions. This approach usually identifies the developer as a delegated admin. In this way we can increase the independence of developers, reducing the number of daily tasks that the few people with wide permission inside the AWS Cloud Environment have to fulfill without introducing security exploits.<\/p>\nWhat is AWS CloudFormation?<\/span><\/h2>\n
What is AWS IAM?<\/span><\/h2>\n
Use Case<\/span><\/h2>\n
Without Permission Boundaries<\/span><\/h2>\n
---\r\nAWSTemplateFormatVersion: '2010-09-09'\r\nDescription: |\r\n CloudFormation and IAM Permission Boundaries Demo\r\n \r\n \r\n################################################################################\r\n# Metadata #\r\n################################################################################\r\nMetadata:\r\n \r\n AWS::CloudFormation::Interface:\r\n ParameterGroups:\r\n - Label: {default: 'Required parameters'}\r\n Parameters:\r\n - VpcId\r\n - AmiId\r\n - KeyName\r\n - SubnetId\r\n - Label: {default: 'Optional parameters'}\r\n Parameters:\r\n - NameSpace\r\n - ProjectName\r\n - Environment\r\n \r\n \r\n################################################################################\r\n# Parameters #\r\n################################################################################\r\nParameters:\r\n \r\n NameSpace:\r\n Type: String\r\n Default: 'besharp'\r\n \r\n ProjectName:\r\n Type: String\r\n Default: 'permission-boundaries-demo'\r\n \r\n Environment:\r\n Type: String\r\n Default: 'dev'\r\n VpcId:\r\n Type: AWS::EC2::VPC::Id\r\n \r\n SubnetId:\r\n Type: AWS::EC2::Subnet::Id\r\n \r\n AmiId:\r\n Type: AWS::EC2::Image::Id\r\n \r\n KeyName:\r\n Type: AWS::EC2::KeyPair::KeyName\r\n \r\n \r\n################################################################################\r\n# Conditions #\r\n################################################################################\r\nConditions: {}\r\n \r\n \r\n################################################################################\r\n# Mappings #\r\n################################################################################\r\nMappings: {}\r\n \r\n \r\n################################################################################\r\n# Resources #\r\n################################################################################\r\nResources:\r\n \r\n #################################### S3 ####################################\r\n S3Bucket:\r\n Type: AWS::S3::Bucket\r\n Properties:\r\n BucketName: !Sub 'com.${NameSpace}.${ProjectName}'\r\n Tags:\r\n - Key: Name\r\n Value: !Sub '${NameSpace}-${ProjectName}'\r\n - Key: Owner\r\n Value: 'name.surname@besharp.it'\r\n \r\n ################################### EC2 ####################################\r\n EC2Instance:\r\n Type: AWS::EC2::Instance\r\n Properties:\r\n IamInstanceProfile: !Ref IAMInstanceProfile\r\n ImageId: !Ref AmiId\r\n InstanceType: t3a.micro\r\n KeyName: !Ref KeyName\r\n NetworkInterfaces:\r\n - AssociatePublicIpAddress: 'true'\r\n DeviceIndex: '0'\r\n GroupSet:\r\n - !Ref EC2SecurityGroup\r\n SubnetId: !Ref SubnetId\r\n Tags:\r\n - Key: Name\r\n Value: !Sub '${NameSpace}-${ProjectName}'\r\n - Key: Owner\r\n Value: 'name.surname@besharp.it'\r\n \r\n EC2SecurityGroup:\r\n Type: AWS::EC2::SecurityGroup\r\n Properties:\r\n GroupName: !Sub '${NameSpace}-${ProjectName}-ec2'\r\n GroupDescription: !Sub 'Security Group for ${NameSpace}-${ProjectName}-ec2'\r\n VpcId: !Ref VpcId\r\n Tags:\r\n - Key: Name\r\n Value: !Sub '${NameSpace}-${ProjectName}-ec2'\r\n - Key: Owner\r\n Value: 'name.surname@besharp.it'\r\n \r\n ################################### IAM ####################################\r\n IAMInstanceProfile:\r\n Type: AWS::IAM::InstanceProfile\r\n Properties:\r\n Roles:\r\n - !Ref IAMRole\r\n InstanceProfileName: !Sub '${NameSpace}-${ProjectName}'\r\n \r\n IAMRole:\r\n Type: AWS::IAM::Role\r\n Properties:\r\n RoleName: !Sub '${NameSpace}-${ProjectName}'\r\n AssumeRolePolicyDocument:\r\n Version: '2012-10-17'\r\n Statement:\r\n - Effect: Allow\r\n Principal:\r\n Service: ec2.amazonaws.com\r\n Action: sts:AssumeRole\r\n Path: '\/'\r\n Policies:\r\n - PolicyName: 'EC2Access'\r\n PolicyDocument:\r\n Version: '2012-10-17'\r\n Statement:\r\n - Effect: 'Allow'\r\n Action:\r\n - 's3:GetObject'\r\n Resource: !Sub '${S3Bucket.Arn}\/*'\r\n Tags:\r\n - Key: Name\r\n Value: !Sub '${NameSpace}-${ProjectName}'\r\n - Key: Owner\r\n Value: 'name.surname@besharp.it'\r\n \r\n \r\n################################################################################\r\n# Outputs #\r\n################################################################################\r\nOutputs:\r\n \r\n StackName:\r\n Description: 'Stack name.'\r\n Value: !Sub '${AWS::StackName}'\r\n<\/pre>\n
\n
{\r\n \"Version\": \"2012-10-17\",\r\n \"Statement\": [\r\n {\r\n \"Sid\": \"CloudFormationReadAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"cloudformation:DescribeStacks\",\r\n \"cloudformation:ListChangeSets\",\r\n \"cloudformation:ListExports\",\r\n \"cloudformation:ListImports\",\r\n \"cloudformation:ListStacks\"\r\n ],\r\n \"Resource\": \"*\"\r\n },\r\n {\r\n \"Sid\": \"CloudFormationWriteAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"cloudformation:CreateStack\",\r\n \"cloudformation:DeleteStack\",\r\n \"cloudformation:TagResource\",\r\n \"cloudformation:UpdateStack\",\r\n \"cloudformation:ValidateTemplate\"\r\n ],\r\n \"Resource\": \"*\"\r\n },\r\n {\r\n \"Sid\": \"EC2ReadAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"ec2:DescribeImages\",\r\n \"ec2:DescribeInstances\",\r\n \"ec2:DescribeKeyPairs\",\r\n \"ec2:DescribeSecurityGroupReferences\",\r\n \"ec2:DescribeSecurityGroups\",\r\n \"ec2:DescribeSubnets\",\r\n \"ec2:DescribeVpcs\"\r\n ],\r\n \"Resource\": \"*\"\r\n },\r\n {\r\n \"Sid\": \"EC2WriteAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"ec2:AssociateIamInstanceProfile\",\r\n \"ec2:AuthorizeSecurityGroupEgress\",\r\n \"ec2:AuthorizeSecurityGroupIngress\",\r\n \"ec2:CreateSecurityGroup\",\r\n \"ec2:CreateTags\",\r\n \"ec2:DeleteSecurityGroup\",\r\n \"ec2:DeleteTags\",\r\n \"ec2:RevokeSecurityGroupEgress\",\r\n \"ec2:RevokeSecurityGroupIngress\",\r\n \"ec2:RunInstances\",\r\n \"ec2:StartInstances\",\r\n \"ec2:StopInstances\",\r\n \"ec2:TerminateInstances\"\r\n ],\r\n \"Resource\": \"*\"\r\n },\r\n {\r\n \"Sid\": \"IAMReadAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"iam:GetInstanceProfile\",\r\n \"iam:GetRole\",\r\n \"iam:GetRolePolicy\"\r\n ],\r\n \"Resource\": \"*\"\r\n },\r\n {\r\n \"Sid\": \"IAMWriteAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"iam:AddRoleToInstanceProfile\",\r\n \"iam:CreateInstanceProfile\",\r\n \"iam:CreateRole\",\r\n \"iam:DeleteInstanceProfile\",\r\n \"iam:DeleteRole\",\r\n \"iam:DeleteRolePolicy\",\r\n \"iam:PassRole\",\r\n \"iam:PutRolePolicy\",\r\n \"iam:RemoveRoleFromInstanceProfile\",\r\n \"iam:TagRole\",\r\n \"iam:UntagRole\"\r\n ],\r\n \"Resource\": \"*\"\r\n },\r\n {\r\n \"Sid\": \"S3WriteAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"s3:CreateBucket\",\r\n \"s3:DeleteBucket\",\r\n \"s3:PutBucketTagging\"\r\n ],\r\n \"Resource\": \"*\"\r\n }\r\n ]\r\n}\r\n<\/pre>\n
\n<\/span><\/p>\nubuntu@ec2-demo:~$ aws s3 cp s3:\/\/com.besharp.permission-boundaries-demo\/if-you-download-me-you-are-fine .\r\n \r\ndownload: s3:\/\/com.besharp.permission-boundaries-demo\/if-you-download-me-you-are-fine to .\/if-you-download-me-you-are-fine\r\nubuntu@ec2-demo:~$ cat if-you-download-me-you-are-fine\r\ntest-ok\r\n<\/pre>\n
ubuntu@ec2-demo:~$ aws s3api create-bucket --bucket can-i-create-it\r\n \r\nAn error occurred (AccessDenied) when calling the CreateBucket operation: Access Denied\r\n<\/pre>\n
\r\n IAMRole:\r\n Type: AWS::IAM::Role\r\n Properties:\r\n RoleName: !Sub '${NameSpace}-${ProjectName}'\r\n AssumeRolePolicyDocument:\r\n Version: '2012-10-17'\r\n Statement:\r\n - Effect: Allow\r\n Principal:\r\n Service: ec2.amazonaws.com\r\n Action: sts:AssumeRole\r\n Path: '\/'\r\n Policies:\r\n - PolicyName: 'AdministratorAccess'\r\n PolicyDocument:\r\n Version: '2012-10-17'\r\n Statement:\r\n - Effect: 'Allow'\r\n Action:\r\n - '*'\r\n Resource: '*'\r\n Tags:\r\n - Key: Name\r\n Value: !Sub '${NameSpace}-${ProjectName}'\r\n<\/pre>\n
ubuntu@ec2-demo:~$ aws s3 cp s3:\/\/com.besharp.permission-boundaries-demo\/if-you-download-me-you-are-fine .\r\n \r\ndownload: s3:\/\/com.besharp.permission-boundaries-demo\/if-you-download-me-you-are-fine to .\/if-you-download-me-you-are-fine\r\nubuntu@ec2-demo:~$ cat if-you-download-me-you-are-fine\r\ntest-ok\r\nubuntu@ec2-demo:~$ aws s3api create-bucket --bucket can-i-create-it\r\n{\r\n \"Location\": \"\/can-i-create-it\"\r\n}\r\n<\/pre>\n
With Permission Boundaries<\/h2>\n
{\r\n \"Sid\": \"IAMPermissionBoundaryWriteAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"iam:CreateRole\",\r\n \"iam:PutRolePolicy\",\r\n \"iam:UpdateRole\",\r\n \"iam:UpdateRoleDescription\"\r\n ],\r\n \"Resource\": \"arn:aws:iam::111122223333:role\/dev-namespace\/*\",\r\n \"Condition\": {\r\n \"StringEquals\": {\r\n \"iam:PermissionsBoundary\": \"arn:aws:iam::111122223333:policy\/besharp-permission-boundary-demo\"\r\n }\r\n }\r\n },\r\n {\r\n \"Sid\": \"IAMPassRoleAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"iam:PassRole\"\r\n ],\r\n \"Resource\": \"arn:aws:iam::111122223333:role\/dev-namespace\/*\"\r\n },\r\n {\r\n \"Sid\": \"IAMWriteAccess\",\r\n \"Effect\": \"Allow\",\r\n \"Action\": [\r\n \"iam:AddRoleToInstanceProfile\",\r\n \"iam:CreateInstanceProfile\",\r\n \"iam:DeleteInstanceProfile\",\r\n \"iam:DeleteRole\",\r\n \"iam:DeleteRolePolicy\",\r\n \"iam:RemoveRoleFromInstanceProfile\",\r\n \"iam:TagRole\",\r\n \"iam:UntagRole\"\r\n ],\r\n \"Resource\": \"*\"\r\n },\r\n<\/pre>\n
IAMRole:\r\n Type: AWS::IAM::Role\r\n Properties:\r\n RoleName: !Sub '${NameSpace}-${ProjectName}'\r\n PermissionsBoundary: !Ref PermissionBoundaryArn\r\n AssumeRolePolicyDocument:\r\n Version: '2012-10-17'\r\n Statement:\r\n - Effect: Allow\r\n Principal:\r\n Service: ec2.amazonaws.com\r\n Action: sts:AssumeRole\r\n Path: '\/dev-namespace\/'\r\n Policies:\r\n - PolicyName: 'AdministratorAccess'\r\n PolicyDocument:\r\n Version: '2012-10-17'\r\n Statement:\r\n Effect: 'Allow'\r\n Action:\r\n - '*'\r\n Resource: '*'\r\n Tags:\r\n - Key: Name\r\n Value: !Sub '${NameSpace}-${ProjectName}'\r\n - Key: Owner\r\n Value: 'name.surname@besharp.it'\r\n<\/pre>\n
ubuntu@ec2-demo:~$ aws s3 cp s3:\/\/com.besharp.permission-boundaries-demo\/if-you-download-me-you-are-fine .\r\n \r\ndownload: s3:\/\/com.besharp.permission-boundaries-demo\/if-you-download-me-you-are-fine to .\/if-you-download-me-you-are-fine\r\nubuntu@ec2-demo:~$ cat if-you-download-me-you-are-fine\r\ntest-ok\r\nubuntu@ec2-demo:~$ aws s3api create-bucket --bucket can-i-create-it\r\n \r\nAn error occurred (AccessDenied) when calling the CreateBucket operation: Access Denied\r\n<\/pre>\n
Conclusion<\/h2>\n