Terraform and IAM


Introduction

In my previous post I talked about deploying a Jekyll blog on AWS and I mentioned the possibility to manage the IAM configuration (which I then explained how it can be done via the AWS console) with Terraform. So, let’s have a look.

There is a solid reason why people use the infrastructure-as-code approach. Not only can we keep everything documented and version-controlled, but we can also do all of our management from the comfort of the CLI.

Of course, there are other infrastructure-as-code implementations, such as Puppet, but I personally use Terraform, mainly because of it’s simplicity and capabilities.

If you are new to Terraform I advise you the check it’s documentation and play around with it. It’s definitely worth it.

Note: In this post I will not be talking about setting up and configuring Terraform.

Making the IAM policy

If you want to easily generate an IAM policy you can use the Policy Generator, located in the “Create Policy” section of your IAM dashboard.

We want our deployer to have the following permissions:

  1. Create and manage CloudFront distributions
  2. Create and manage S3 buckets

These options are quite permissive, but if we want to limit them we have to do some parts of the deployment process manually, like:

  • Creating an S3 bucket
  • Creating a CloudFront distribution

I’m fine with the deployer having the permissions to create CloudFront distributions and S3 buckets, so for now I’ll allow it to have all permissions for S3 and CloudFront.

You can play around with the policy generator and when you’re ready review your policy. Mine looks like this:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "<GENERATED_SID>",
            "Effect": "Allow",
            "Action": [
                "cloudfront:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "<GENERATED_SID>",
            "Effect": "Allow",
            "Action": [
                "s3:CreateBucket"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Refining the policy for Terraform usage

Terraform will take care for generating IDs, etc. so we can remove the Sid fields from our JSON.

You’ll end up having something like this:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudfront:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:CreateBucket"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

The Terraform part of everything

Now we can make our .tf files, which will define what Terraform should do.

What we’re going to need is:

  1. An account
  2. A user policy
  3. An IAM access key

User

That’s pretty simple, I have the following configuration:

resource "aws_iam_user" "blog_deployer" {
  name = "blog_deployer"
}

IAM Access Key

This would look something like this:

resource "aws_iam_access_key" "blog_deployer_access_key" {
  user = "${aws_iam_user.blog_deployer.name}"
  pgp_key = <base-64 encoded PGP public key>
}

By adding your PGP public key you can then get the IAM user’s secret key from Terraform.

IAM User Policy

This is what your user policy should look like in Terraform:

resource "aws_iam_user_policy" "blog_deployer_user_policy" {
  name = "blog_deployer_user_policy"
  user = "${aws_iam_user.blog_deployer.name}"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Action": [
              "cloudfront:*"
          ],
          "Resource": [
              "*"
          ]
      },
      {
          "Effect": "Allow",
          "Action": [
              "s3:CreateBucket"
          ],
          "Resource": [
              "*"
          ]
      }
  ]
}
EOF
}

Output

There are a couple of ways to get the IAM user’s access ID and secret key, the easiest of all being to just output it to the console, which I would not recommend since it’s totally not secure.

You can output the user’s ID by including this in your .tf file:

output "id" {
  value = "${aws_iam_access_key.blog_deployer_access_key.id}"
}

For outputting your secret key add the following:

output "secret" {
  value = "${aws_iam_access_key.blog_deployer_access_key.encrypted_secret}"
}

To get the secret key in a secure way run terraform output secret and then you can decrypt it.

Applying the Terraform configuration

If you’re working in a new directory remember to run terraform init to download the Terraform AWS plugin.

When you’re ready you can run terraform plan and you should see something like this:

Refreshing Terraform state in-memory prior to plan...

Plan: 3 to add, 0 to change, 0 to destroy.

Check to make sure that we’re deploying the correct configuration and then run terraform apply.

You’ll see the following when Terraform has applied everything:

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

access key = <ACCESS_KEY>

Conclusion

That’s how you can deploy an IAM user with an attached user policy with Terraform. You can use the provisioned user with s3_website to deploy to S3 and CloudFront.