In this post, I will guide you on how to add an extra layer of security to Terraform when you’re using it on hobby projects with AWS. This is particularly helpful when your AWS API keys are stored in an unencrypted form on your local machine.
My suggested strategy involves creating a specific user with limited access. This user’s only permission will be to assume a role that has more privileges. We will then add a policy to this privileged role, allowing only those users who have successfully authenticated via Multi-Factor Authentication (MFA) to assume it. This approach might sound straightforward, but it’s effective. Here’s how to do it:
Create a user
allow access to aws console
Edit the newly created user and add a virtual MFA device:
Click the security credentials tab
Click Assign MFA device
, name the device, and choose Virtual (this is important), use your phone to set it up. Remember the ARN, it will look like arn:aws:iam::123:mfa/USER
Click the Access Keys
tab, add an API key for Command Line Interface
and save it in a secure location.
Custom Trust Policy
for the trusted entity type, and choose the json editor{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeAdminRolePolicy",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123:user/USER"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"sts:ExternalId": "a-random-string"},
"Bool": { "aws:multifactorAuthPresent": true}
}
}
]
}
Note: Remember the role ARN, it will look something like arn:aws:iam::123:role/ROLE
~/.aws
directory if it doesn’t exist:mkdir ~/.aws
touch ~/.aws/config
touch ~/.aws/credentials
[default]
region = us-east-1
output = json
[default]
aws_access_key_id = <YOUR_KEY_ID>
aws_secret_access_key = <YOUR_ACCESS_KEY>
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::123:role/ROLE"
}
]
}
duration
seconds, the serial-number
and token-code
parameters are important!aws --profile default sts assume-role \
--role-arn arn:aws:iam::<account id>:role/terraform-admin \
--role-session-name TFsession \
--output text \
--query "Credentials.[AccessKeyId,SecretAccessKey]" \
--external-id a-random-string \
--duration 900 \
--serial-number arn:aws:iam::<account id>:mfa/terraform-mfa \
--token-code 12345
This command will output the temporary AccessKeyId, SecretAccessKey, and SessionToken, you can now place these into the shell environment – and run terraform:
export AWS_ACCESS_KEY_ID="new_access_key_id"
export AWS_SECRET_ACCESS_KEY="new_secret_access_key"
export AWS_SESSION_TOKEN="new_session_token"
Make sure that the terraform provider has minimal configuration, you’ll especially need to make sure that it doesn’t specify a profile, otherwise it will interfere with how the credentials are read. This is mine:
provider "aws" {
region = "us-east-1"
}
I have created a simple script that makes it easier to obtain a session when you want to run terraform:
Important: Please ensure to ‘source’ the script instead of executing it in a regular manner. ‘Sourcing’ the script ensures that the environment variables, which are set by the script, persist even after the script finishes its execution. This is crucial because the Terraform client relies on these environment variables. If you don’t ‘source’ the script, these variables will not be available to the Terraform client once the script execution is completed.
source get_tf_session.sh --aid 123 --eid a-random-string --role terraform-admin --mfa terraform-mfa --code 12345
...
...
terraform plan
Thats it! You should now be able to run terraform commands with the correct permissions from the privileged role, but only if you provide the correct MFA code.