Introduction
This is the second in a series of posts looking at some of the core services, building blocks and approaches that will help you build out a multi-account best practice landing zone:
- Part 1: Initial setup up of a multi-account AWS environment
- Part 2: Adding AWS SSO and controlling permissions
- Part 3: Centralising audit, compliance and incident detection
Setting up AWS Single Sign-On (SSO)
AWS SSO is the service to centrally manage access across your AWS organization. Your identity source in AWS SSO defines where your users and groups are managed. Although most enterprises will choose to manage users through Active Directory or an external identity provider, AWS SSO comes with an inbuilt identity source that allows you to create your users and groups directly in SSO, which is what we will use here.
AWS SSO prevents the need to store credentials on disk. Adding and removing users from a group dynamically changes their permissions, and you can customise the maximum session durations. There are some shortcomings with the service, and if you want to get more involved, it's worth checking out aws-sso-util by Ben Kehoe.
Setting up AWS SSO is something that must be done in the console in the management account, clicking on the 'Enable AWS SSO' button.
Once enabled, you will see a dashboard where you can customise the User Portal URL to something more memorable.
Click on Settings and make a note of the AWS SSO ARN as well as the User Portal URL. We then manually create the four groups we are going to use, which are "Developer", "Admin", "SecurityAdmin" and "IncidentResponse". For each one, make a note of the Group ID.
Next we want to create a user. Set the username you want, fill in the name and unique email address, and finally select the groups you want to add the user too. For simplicity, I add the user to all groups for testing purposes.
Now this has been set up, we are in a position to start exploring how to make our environments secure and put in place more fine grained permissions.
Permission Sets and Assignments
Having defined four groups, we have to apply policies to give them permission to access AWS resources. All users within a group will inherit these permissions. We do this by creating a permission set, which is a template you create and maintain that defines a collection of one or more IAM policies. For this blog, we will just focus on setting up the Developer permission set.
DevPermissionSet:
Type: AWS::SSO::PermissionSet
Properties:
Name: 'Developer'
Description: 'Developer access to AWS organization'
InstanceArn: {AWS SSO instance ARN}
ManagedPolicies:
- arn:aws:iam::aws:policy/AdministratorAccess
SessionDuration: 'PT1H'
This creates a permission set with admin access that we can assign to an AWS account using an Assignment as shown below:
DevAssignmentSandbox:
Type: AWS::SSO::Assignment
Properties:
InstanceArn: {AWS SSO instance ARN}
PermissionSetArn: !GetAtt DevPermissionSet.PermissionSetArn
PrincipalId: {GroupID for Developer Group in SSO}
PrincipalType: GROUP
TargetType: AWS_ACCOUNT
TargetId: {Account ID for target account}
We create an Assignment for each account in the Sandbox, Dev and Prod OUs. Unfortunately there is no current way in CloudFormation to natively reference OUs as targets. There is a custom resource provider in org-formation you can use to iterate over OUs, but for this post we use CloudFormation to make it clear how it works under the covers.
After running the pipeline, we can log in as our user via the User Portal URL, and are given an option to access the Sandbox, Dev and Prod accounts as a developer. The admin access may be acceptable for the sandbox account, but is overly permissive for other environments. Our next goal is to look at a way of retaining a single developer permission set but putting more control on the permissions.
Fine tune developer permission set
Luckily Ben Kehoe has written a great article on how to 'Use aws:PrincipalAccount to fine-tune your AWS SSO permission sets', and this is the approach we will use.
We extend the permission set to grant read only and support access, and then add an inline policy with admin access, but allow this only where the account is one of those in the Sandbox OU.
DevPermissionSet:
Type: AWS::SSO::PermissionSet
Properties:
Name: !Ref permissionSetName
Description: !Sub '${permissionSetName} access to AWS organization'
InstanceArn: !Ref instanceArn
ManagedPolicies:
- arn:aws:iam::aws:policy/ReadOnlyAccess
- arn:aws:iam::aws:policy/AWSSupportAccess
SessionDuration: !Ref sessionDuration
InlinePolicy:
Version: 2012-10-17
Statement:
- Sid: 'AdminAccessToResources'
Effect: Allow
Action:
- "*"
Resource:
- "*"
Condition:
StringEquals:
aws:PrincipalAccount: Fn::EnumTargetAccounts SandboxBinding ${account}
This now allows users in the developer group to inherit admin permissions in a Sandbox account. We go on to add a new statement in the developer policy for accounts in the Dev OU as shown below. Note that this a very cut down example of what it may look like for your organisation.
- Sid: DeveloperPermissionsPolicy
Effect: Allow
Action:
- account:ListRegions
- cloudformation:*
- cloudwatch:*
- dynamodb:*
- iam:Get*
- iam:List*
- lambda:*
- logs:*
- organizations:DescribeOrganization
- s3:*
- sts:*
- secretsmanager:*
Resource: "*"
Condition:
StringEquals:
aws:PrincipalAccount: Fn::EnumTargetAccounts DevBinding ${account}
- Sid: DeleteDetachTagRole
Effect: Allow
Action:
- iam:DeleteRole
- iam:DeleteRolePolicy
- iam:DetachRolePolicy
- iam:TagRole
- iam:UntagRole
Resource:
- arn:aws:iam::*:role/dev-*
Condition:
StringEquals:
aws:PrincipalAccount: Fn::EnumTargetAccounts DevBinding ${account}
- Sid: CreateRole
Effect: Allow
Action:
- iam:AttachRolePolicy
- iam:CreateRole
- iam:PutRolePolicy
- iam:PutRolePermissionsBoundary
Resource:
- arn:aws:iam::*:role/dev-*
Condition:
StringEquals:
aws:PrincipalAccount: Fn::EnumTargetAccounts DevBinding ${account}
We can now log into a Development account and carry out actions such as creating an S3 bucket. We can try and launch an EC2 instance, and this will fail as we are not authorised to do so. However, there is a big security flaw in the policy above. In the Development accounts, we want to give developers the capability to create roles to support the services they are building. The policy above allows the developer to create a new IAM role (prefixed with 'dev-') with admin access, assume the new role, and then carry out actions with these privileges. We can test this by assuming a new role with admin access, and successfully launching a new EC2 instance.
Our next goal is to look at constraining the permissions that can be granted when creating these roles using a permission boundary.
Constrain permissions using a Permission Boundary
A permissions boundary is an advanced feature for using a managed policy to set the maximum permissions that an identity-based policy can grant to an IAM entity. An entity's permissions boundary allows it to perform only the actions that are allowed by both its identity-based policies and its permissions boundaries.
We start by deploying an example managed policy to all Development accounts
ExamplePermissionBoundaryPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: dev-permission-boundary-policy
Description: Permission Boundary example
Path: /dev/
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: ...
Next we update the CreateRole statement to enforce that a role can only be created if it includes the managed policy above as a permission boundary. This will now limit the permissions that can be assigned, as the allowable permissions for that role are those allowed by both the identity policy attached to the role, and those allowed in the permission boundary
- Sid: CreateRoleWithPermissionBoundary
Effect: Allow
Action:
- iam:AttachRolePolicy
- iam:CreateRole
- iam:PutRolePolicy
- iam:PutRolePermissionsBoundary
Resource:
- arn:aws:iam::*:role/dev-*
Condition:
StringLike:
iam:PermissionsBoundary: !Sub arn:aws:iam::*:policy/dev/dev-permission-boundary-policy
StringEquals:
aws:PrincipalAccount: Fn::EnumTargetAccounts DevBinding ${account}
This covers off the Sandbox and Dev OUs, but what about Prod? Currently developers have read only access to Production, so what happens if there is an issue or incident that needs a higher level of permission to investigate. This is where we look at cross account roles.
Elevating privileges with Cross Account Role
In support of security incidents and exceptional cases, you should have a process by which authorised administrators can temporarily gain access to your accounts. We will support this break glass process using cross-account roles. In this scenario, we want our users in the IncidentResponse group to be able to assume an Administrator role in a Production account.
The template below could be run as and when required. The AssumeRoleBinding
targets accounts in the Security OU and the RoleBinding
targets accounts in the Prod OU.
The template creates an IAM role in Production accounts with admin access that can be assumed by accounts in the Security OU.
OrganizationBindings:
AssumeRoleBinding:
OrganizationalUnit: !Ref SecurityOU
Region: 'eu-west-2'
RoleBinding:
OrganizationalUnit: !Ref ProdOU
Region: 'eu-west-2'
Resources:
Role:
Type: AWS::IAM::Role
OrganizationBinding: !Ref RoleBinding
Properties:
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AdministratorAccess
RoleName: "elevated-security-role"
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
AWS: Fn::EnumTargetAccounts AssumeRoleBinding '${account}' # role can only be assumed from SecurityOU
In the accounts in the Security OU, we only want users in the IncidentResponse group to be able to assume the role. We can do that using an inline policy on the permission set.
IncidentResponsePermissionSet:
Type: AWS::SSO::PermissionSet
Properties:
Name: !Ref permissionSetName
Description: !Sub '${permissionSetName} access to Production accounts'
InstanceArn: !Ref instanceArn
SessionDuration: !Ref sessionDuration
InlinePolicy:
Version: 2012-10-17
Statement:
- Sid: 'AccessToElevatedRole'
Effect: Allow
Action: sts:AssumeRole
Resource: Fn::EnumTargetAccounts RoleBinding 'arn:aws:iam::${account}:role/elevated-security-role'
We prevent all Security Admins from being able to assume the role by placing an explicit deny on their permission set
SecurityPermissionSet:
Type: AWS::SSO::PermissionSet
Properties:
Name: !Ref permissionSetName
Description: !Sub '${permissionSetName} access to AWS organization'
InstanceArn: !Ref instanceArn
ManagedPolicies:
- arn:aws:iam::aws:policy/AdministratorAccess
SessionDuration: !Ref sessionDuration
InlinePolicy:
Version: 2012-10-17
Statement:
- Sid: 'DenyAccessToElevatedRole'
Effect: Deny
Action: sts:AssumeRole
Resource: Fn::EnumTargetAccounts RoleBinding 'arn:aws:iam::${account}:role/elevated-security-role'
This now means that users in the IncidentResponse group can access their Security account, and from their assume the elevated role in a Production account. This could be further improved by raising an alert whenever this role is assumed. This can be achieved by ensuring that CloudTrail logs to a CloudWatch Log Group, and then setting up a Metric Filter when as assumeRole
event takes place for the elevated role. This can then trigger a CloudWatch Alarm and appropriate action take place. We will look more at CloudTrail in our next blog post in this series.
Finally, adopting a multi-account setup using AWS Organizations allows us to take advantage of Service Control Policies, which we will look at now.
Applying Service Control Policies to your Organization
A Service Control Policy (SCP) is a special type of organization policy used to manage permissions in an AWS organization. An SCP alone is not sufficient to grant permission. Instead, it defines a guardrail on the actions that can be delegated to IAM user and roles in an account. The administrator must still attach identity-based or resource-based policies to actually grant permission.
An SCP restricts permissions for IAM users and roles in member accounts, including the member account's root user. They have no effect on users or roles in the management account. SCPs do not affect any service-linked role. Service-linked roles enable other AWS services to integrate with AWS Organizations and can't be restricted by SCPs.
Root is the parent OU for all accounts and other OU's in your organization. When you apply a policy to the root, it applies to every OU and account in the organization. An OU can contain other OU's up to five levels deep. You can apply an SCP at root, to an OU or to a specific account within an OU.
So let's take a look at some typical SCPs that we can apply.
Restrict use of Root User
The root user is the owner of the AWS account. It is not possible to use an IAM policy to explicitly deny the root user access to resource. The root user also has permissions to carry out other destructive activities such as closing an account. AWS strongly recommend not to use root, and so we can apply an SCP to block access for the root user.
RestrictAccessForRootSCP:
Type: OC::ORG::ServiceControlPolicy
Properties:
PolicyName: RestrictAccessForRoot
Description: Deny root user from accessing any resources
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: RestrictAccessForRoot
Effect: Deny
Action:
- '*'
Resource:
- '*'
Condition:
StringLike:
'aws:PrincipalArn':
- 'arn:aws:iam::*:root'
Enforce use of tags
Another best practice within AWS is to create an effective tagging strategy for your resources. This can be used to allocate costs, for operational support, or even to enforce access control in combination with tag-based conditions.
An effective resource tagging strategy will combine both Tag Policies and SCPs. Tag Policies only enforce the accepted value of a tag, and not its presence. You can then use an SCP to ensure that a resource cannot be created with the relevant tags present. The example below is for EC2 instances, but a similar concept applies across other types of resources.
- Sid: DenyNonTaggedResourceCreation
Effect: Deny
Action: 'ec2:RunInstances'
Resource:
- 'arn:aws:ec2:*:*:instance/*'
- 'arn:aws:ec2:*:*:volume/*'
Condition:
'Null':
'aws:RequestTag/ServiceName': 'true'
Prevent member accounts from leaving the organization
Now we have set up our organization with some of the guardrails in place, we want to prevent a member account from leaving. This can be carried out with the following:
## Prevent leaving organization
- Sid: PreventLeavingOrganization
Effect: Deny
Action:
- 'organizations:LeaveOrganization'
Resource: '*'
Deny access to AWS based on the requested AWS Region
Many organisations have requirements around data sovereignty and impose restrictions on the regions in which services can be deployed. This policy uses the Deny effect to deny access to all requests for operations that don't target the London region (eu-west-2). The NotAction
element enables you to list services whose operations are exempted from this restriction, typically as global services have endpoints physically hosted in the us-east-1 region, so they must be exempted in this way.
- Sid: RestrictAccessOnlyToLondonRegion
Effect: Deny
NotAction:
- 'a4b:*'
- 'acm:*'
- 'access-analyzer:*'
- 'aws-marketplace-management:*'
- 'aws-marketplace:*'
- 'aws-portal:*'
- 'budgets:*'
- 'ce:*'
- 'chime:*'
- 'cloudfront:*'
- 'config:*'
- 'cur:*'
- 'directconnect:*'
- 'ec2:DescribeRegions'
- 'ec2:DescribeTransitGateways'
- 'ec2:DescribeVpnGateways'
- 'fms:*'
- 'globalaccelerator:*'
- 'health:*'
- 'iam:*'
- 'importexport:*'
- 'kms:*'
- 'mobileanalytics:*'
- 'networkmanager:*'
- 'organizations:*'
- 'pricing:*'
- 'route53:*'
- 'route53domains:*'
- 's3:GetAccountPublic*'
- 's3:ListAllMyBuckets'
- 's3:PutAccountPublic*'
- 'shield:*'
- 'sts:*'
- 'support:*'
- 'trustedadvisor:*'
- 'waf-regional:*'
- 'waf:*'
- 'wafv2:*'
- 'wellarchitected:*'
Resource: '*'
Condition:
StringNotEquals:
'aws:RequestedRegion':
- eu-west-2
ArnNotLike:
'aws:PrincipalARN':
- 'arn:aws:iam::*:role/ByPassThisSCPRole'
Prevent internet access
There are many new approaches around networking setup within your AWS environment, with this recent blog post talking about deployment models for AWS Network Firewall.
Some companies may want to ensure all egress is routed via AWS Network Firewall in combination with AWS Transit Gateway creating the notion of an Inspection VPC, with this controlling an allow list for outbound traffic. In this case, we can enforce that accounts are not able to create their own Internet Gateway using the following SCP:
- Sid: DenyCreateInternetGateway
Effect: Deny
Action:
- 'ec2:AttachInternetGateway'
- 'ec2:CreateInternetGateway'
Resource: '*'
- Sid: DenyCreateEgressOnlyInternetGateway
Effect: Deny
Action:
- 'ec2:AttachEgressOnlyInternetGateway'
Resource: '*'
Prevent large instance types
In our setup we provide a Sandbox account to allow developers to innovate. We can allow them to spin up EC2 instances, but can prevent any large instances being launched:
- Sid: DenyLargerThan2XLarge
Effect: Deny
Action:
- "ec2:RunInstances"
Resource: "arn:aws:ec2:*:*:instance/*"
Condition:
ForAnyValue:StringNotLike:
"ec2:InstanceType":
- "*.nano"
- "*.small"
- "*.micro"
- "*.medium"
- "*.large"
- "*.xlarge"
- "*.2xlarge"
There are a maximum of 5 SCPs that can be attached to either the root, an OU or an account. This means we will create an SCP combining a number of statements.
One great aspect about managing the AWS environment through configuration is that the organization.yml
file clearly shows what SCPs are applied, which can help when tracking down issues. An example is shown below:
OrganizationRoot:
Type: OC::ORG::OrganizationRoot
Properties:
DefaultOrganizationAccessRoleName: OrganizationAccountAccessRole
DefaultBuildAccessRoleName: OrganizationFormationBuildAccessRole
ServiceControlPolicies:
- !Ref OrgWideManagementControlsSCP
- !Ref OrgWideServiceControlsSCP
<<: !Include ./250-scps/deny-root-access.yml
<<: !Include ./250-scps/deny-large-ec2.yml
<<: !Include ./250-scps/org-wide-management-controls.yml
<<: !Include ./250-scps/org-wide-service-controls.yml
If you log into the Management Account, access AWS Organizations, select the Root account and select Policies, you will see these new SCPs applied:
This post has touched on a number of the different policy types that exist, and how powerful the use of conditions and permission boundaries can be. In the next post, we will start to look at compliance and incident detection with services such as AWS Config, AWS CloudTrail and Amazon GuardDuty.
Top comments (3)
Hi,
Under the fine tune developer permission sets section, you have this:
Condition:
StringEquals:
aws:PrincipalAccount: Fn::EnumTargetAccounts SandboxBinding ${account}
What is the binding exactly? Is it just the name of the OU with Binding after it or have you created a binding somewhere that I have missed?
Ah I see - sorry should have read a bit further back.
How easy is it to incorporate into an existing org and control tower environment? Where starting from scratch is not possible.
Where can I find this code?