An essential part of using AWS is controlling access to the resources. We've seen with all the previous recipes how often we need to use the AWS Access Keys, and it's surely not a good idea to use a single key for all your activities. Imagine what would happen if a single one of your services was hacked—the intruder would get the main AWS key and would be able to do everything on your behalf.
A good secure setup would be dedicated keys with a dedicated scope of access rights for every person in your team and every service in your infrastructure.
Thankfully, Identity and Access Management (IAM) is there just for that. We'll see how to use it with Terraform.
To step through this recipe, you will need the following:
Let's start with a simple case: two members of a team (Mary and Joe) need to access resources on AWS. They currently all share the same main key, which is a disaster if a leakage happens. So let's ask them what exactly they need to access in the AWS space:
Mary |
S3 in read and write |
Joe |
EC2 in read only |
As expected, neither user really needs full access!
Amazon helps by offering prebuilt security policies for IAM. If those aren't enough, you can tailor the ones you need:
You can find all AWS Managed IAM Policies at https://console.aws.amazon.com/iam/home#policies.
Let's create a first IAM user for Mary in a new iam.tf
file using the aws_iam_user
resource:
resource "aws_iam_user" "mary" { name = "mary" path = "/team/" }
The path
is purely optional and informative, I'm simply suggesting structured paths. So we'll have /apps/
as well later.
We can now create an AWS Access Key for our user Mary, using the aws_iam_access_key
resource with reference to our user:
resource "aws_iam_access_key" "mary" { user = "${aws_iam_user.mary.name}" }
And finally, as we know, we want to attach to this user the AmazonS3FullAccess
managed policy, let's use the dedicated resource:
resource "aws_iam_user_policy_attachment" "mary_s3full" { user = "${aws_iam_user.mary.name}" policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" }
Let's write an output
so we know both parts of the key in outputs.tf
:
output "mary" { value = "ACCESS_KEY: ${aws_iam_access_key.mary.id}, SECRET: ${aws_iam_access_key.mary.secret}" }
Also, terraform apply
this to create the mary
user:
[...] Outputs: mary = ACCESS_KEY: AKIAJPQB7HBK2KLAARRQ, SECRET: wB+Trao2R8qTJ36IEE64GNIGTqeWrpMwid69Etna
Now, terraform apply
this, and confirm using an S3 browser that you can access S3! Here's an example of creating a simple S3 bucket with s3cmd
:
$ s3cmd --access_key=<mary_access_key> --secret_key=<mary_secret_key> mb s3://iacbook-iam-bucket Bucket 's3://iacbook-iam-bucket/' created
Is this account really limited to S3, as it pretends to be? Let's try to list EC2 hosts with Mary's account using the aws
command line (provided you configured the aws
tool accordingly):
$ aws --profile iacbook-mary ec2 describe-hosts An error occurred (UnauthorizedOperation) when calling the DescribeHosts operation: You are not authorized to perform this operation.
So it all looks good and secure! Mary can do her job on S3 safely.
Is there a similar managed policy for Joe, with a read-only scope on EC2? Fortunately, there is! It's creatively named AmazonEC2ReadOnlyAccess
.
Let's create our second user, with this IAM policy in the iam.tf
file:
resource "aws_iam_user" "joe" { name = "joe" path = "/team/" } resource "aws_iam_access_key" "joe" { user = "${aws_iam_user.joe.name}" } resource "aws_iam_user_policy_attachment" "joe_ec2ro" { user = "${aws_iam_user.joe.name}" policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess" }
Don't forget the useful output that comes with it:
output "joe" { value = "ACCESS_KEY: ${aws_iam_access_key.joe.id}, SECRET: ${aws_iam_access_key.joe.secret}" }
Next, terraform apply
this once again, and can the Joe user see what's on S3? No, he can't:
$ s3cmd --access_key=<joe_access_key> --secret_key=<joe_secret_key> ls ERROR: S3 error: 403 (AccessDenied): Access Denied
But can the Joe user simply list the EC2 VMs as he needs to, with the same command that was forbidden to Mary? Yes, he can:
$ aws --profile iacbook-joe ec2 describe-hosts { "Hosts": [] }
We're on track to securely manage our infrastructure access using code!
We've used the CloudWatch Logs service in a previous recipe. If you remember, you had to enter once again your keys in the Docker Engine configuration. If you had 100 servers, your master keys would be on each of them. This is rather unnecessary, if you consider that the scope of this configuration in Docker is just to send logs. Fortunately, there's a managed IAM policy for that named CloudWatchLogsFullAccess
.
So let's create another user, exactly as before for Mary and Joe, except this one will be for our Docker Engines and not for a real user in iam.tf
. I suggest using a different path, just to separate real users and application users. However, that's totally optional and opinionated:
resource "aws_iam_user" "logs" { name = "logs" path = "/apps/" } resource "aws_iam_access_key" "logs" { user = "${aws_iam_user.logs.name}" } resource "aws_iam_user_policy_attachment" "logs_cloudwatch_full" { user = "${aws_iam_user.logs.name}" policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" }
The relevant output
in outputs.tf
is as follows:
output "logs" { value = "ACCESS_KEY: ${aws_iam_access_key.logs.id}, SECRET: ${aws_iam_access_key.logs.secret}" }
Now, terraform apply
this and try the Enabling CloudWatch Logs for Docker with Terraform recipe again with those credentials instead of the master keys: it will still work on the CloudWatch scope, but if something goes wrong, it will never put the rest of your infrastructure in danger. The worst that can happen in this area is the total waste of the logs.
[...] Outputs: joe = ACCESS_KEY: AKIAJQPSXBKSD3DY47BQ, SECRET: VQgtQ7D8I+mxRX28/x5qbFk6cdyxZajhhSsh7Rha logs = ACCESS_KEY: AKIAISIUXTG5RIJZAEYA, SECRET: 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 mary = ACCESS_KEY: AKIAJPQB7HBK2KLAARRQ, SECRET: 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
If you'd prefer to see how this would work using Ansible, it's a bit different. IAM support is not equivalent, as there's no IAM Managed Policies support. However, you can simply create users like this:
--- - name: create mary user iam: iam_type: user name: mary state: present access_key_state: create path: /team/
As there's currently no IAM Managed Policy support, a workaround is to use the JSON from the IAM Policy we want, such as AmazonS3FullAccess
for our user Mary. It's easy to find in the AWS Console in the Policies section (https://console.aws.amazon.com/iam/home#policies). Paste the following JSON content in AmazonS3FullAccess.json
at the root of the Ansible
folder:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:*", "Resource": "*" } ] }
Use this local policy in the iam_policy
module:
- name: Assign a AmazonS3FullAccess policy to mary iam_policy: iam_type: user iam_name: mary policy_name: AmazonS3FullAccess state: present policy_document: AmazonS3FullAccess.json
3.135.183.89