Reading AWS’ security good practices, you’ll come across a pattern where your organisation’s AWS assets would be partitioned across a number of subaccounts - for example - one subaccount for every environment such as development, testing, production, etc. This is a good idea as is keeps different resources as separate as they can get (logically) - you simply get another account, and then only the billing function is kept shared as you’d want to deal with one invoice rather than 10.
Roles and role-based access control
When I first started out with AWS, I’d create a new user every time I wanted to protect access to a different subset of my resources. Very quickly, I’d be drowning in access keys, and trying to remember which accounts are still used, and what they are used for.
How would we untangle that mess? We ought to first identify what needs to be accessed, to do what, and then by whom. For example, if we’re dealing with someone who manages our web presence, we might only give them a minimal set of permissions to access a particular S3 bucket, and nothing else. Similarly, if we have another function which requires access to logs, we can create a separate role for that and assign relevant policies. This fits in nicely with the principle of least privilege and separation of responsibilities.
The correct way of modelling this is to assign one AWS user account per person. A person can then use supplied credentials to issue calls through APIs – this is how we get from a person to a user. This, coupled with what is described below allows us to reduce credential sharing. With the added power of MFA, we are reducing the potential of unauthorised access - a real, breathing person must reach for their OTP token, or other additional authentication factor to execute an operation.
A role’s trust relationship defines who can assume the role. So this is where you’d define a set of users who can assume a particular role. This can refer to both user accounts within our own AWS account, a sub-organisation, or another organisation for example one which provides a service to our organisation and operates on resources in our AWS account.
Assuming roles - putting on one of many hats, for a few hours
Once trust relationships are in place, and we’ve defined who can take one a role, there is a mechanism which allows a particular user context to temporarily assume a role.
This is accomplished by the sts:AssumeRole
API call which will generate a set of temporary security credentials (id/secret) and a security token valid for a short time (by default 1 hour).
One can assume roles quite easily by using the profiles capability of the AWS SDK, and some additional tools like aws-vault
.
Profiles
Within your .aws/config
file, you can define profiles, which are pieces of configuration to be used together. For example
[profile jan-work]
region = eu-west-1
When using the profile, either using --profile
command line options, or by setting the AWS_PROFILE
environment variable,
the context in which operations will be executed, will be that profile, so the SDK will set the region to Ireland.
Assuming roles automatically
Let use define an additional profile.
[profile security-auditor]
source_profile = jan-work
role_arn = arn:aws:iam::1234567890:role/security-auditor
Provided that there’s a trust relationship established between jan-work
and security-auditor
, we can assume that role and get read
only access to all AWS resources, for auditing purposes. As long as the trust relationship exists, the role can even live in a different
account. This allows us to have many sub-organisations and to assume roles within those organisations.
I’ve used aws-vault
to automate this process. The first step is to import the your credentials for the account into aws-vault
- this boils down to executing the following
and entering your AWS credentials.
$ aws-vault add jan-work
Enter Access Key ID: AKIA1E3GDYEOGFJPIT7
Enter Secret Access Key: 6b1479c92c82ff1047b9995194255942c0e84c4585e35708e67e9aaf
Added credentials to profile "jan-work" in vault
The credentials are now securely stored within your operating system’s secure credential storage mechanism.
You can then use aws-vault
in conjunction with tools which use the AWS SDK, and pass temporary credentials to them using environment variables.
For example:
$ aws-vault exec security-auditor -- aws s3 ls
2020-02-19 18:19:25 work-terraform-state
2020-02-20 16:48:15 work-logs
2019-01-11 10:53:37 work-archive
Slapping MFA on top
Suppose that you’d want some sort of assurance, that the person assuming this role is actually the person who is the user. You can utilise
multi factor authentication to accomplish that. In short - every time the role is to be assumed, the user will be prompted to enter a
single use token, which has a lifetime of 1 minute. This video will walk you through how to
set up a virtual token provider in the form of a mobile application such as Authy
or Google Authenticator
.
Adding MFA to an existing role is a two-step process. First, add the virtual MFA device generated above to the profile which contains
the role to be assumed. Edit the profile by adding a mfa_serial
configuration directive to the config.
[profile security-auditor]
source_profile = jan-work
role_arn = arn:aws:iam::1234567890:role/security-auditor
mfa_serial = arn:aws:iam::1234567890:mfa/jan-work
The next step involves modifying the trust relationship on the role, to only permit users which have successfully authenticated themselves
using both AWS credentials and an MFA token. Go to the role you wish to assume in your AWS console, click on Trust relationships | Edit trust relationship
,
and modify it so that it has an additional condition - aws:MultiFactorAuthPresent
. See below for an example:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::1234567890:user/jan-work"
},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
Next time you try to run aws-vault
with that profile you should get the following:
$ aws-vault exec security-auditor -- aws s3 ls
Enter token for arn:aws:iam::1234567890:mfa/jan-work: 162287
2020-02-19 18:19:25 work-terraform-state
2020-02-20 16:48:15 work-logs
2019-01-11 10:53:37 work-archive
This works with everything!
… well, mostly. It will work with any executable which uses AWS environment variables for their configuration, so any tool based on the official AWS SDK, of which there are many.
Terraform is one such tool and it boils down to prepending a call to aws-vault
like this:
$ aws-vault exec dev-infra-admin -- terraform apply
This will prompt you for an MFA token, if you’ve configured that and will run terraform plan
just as you did before.