IAM Users and Groups in CloudFormation and Terraform

As possibly the last AWS using sysadmin in London who’s not invested in Terraform I’ve decided it’s time to take my quarterly look at the tool. This time around I decided to start with a basic IAM admin user and group. For my stripped down example I’m going to create a user and group, add the user to a group and set an explicit IAM group policy.

As a novice terraform user I find the code easy to read, the online documentation was short but helpful and the getting started guide did indeed guide my starting. My only initial dislike is the required, in many cases, duplicated “name” argument. I don’t yet understand the difference between it and the resource name in the common case and it feels like something that could be implicit in many cases. I assume there’s a page that’ll prove me completely wrong about 10 minutes after publishing this post.

resource "aws_iam_user" "deanwilson-admin" {
    name = "deanwilson-admin"
    path = "/"
}

resource "aws_iam_group" "admin-users" {
    name = "admin-users"
    path = "/"
}

resource "aws_iam_group_membership" "admin-user-membership" {
    name = "admin-user-membership"
    users = [
        "${aws_iam_user.deanwilson-admin.name}",
    ]
    group = "${aws_iam_group.admin-users.name}"
}

resource "aws_iam_group_policy" "explicit-admin" {
    name = "explicit-admin"
    group = "${aws_iam_group.admin-users.id}"
    policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "*",
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
EOF
}

Now I have my basic use case running under terraform I thought it was worth writing the same resources in CloudFormation. The closest like for like match I could quickly put together looks like this:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Add an admin group and users.",

  "Resources": {

    "DeanwilsonAdmin": {
      "Type": "AWS::IAM::User",
      "Properties": {
        "Path": "/"
      }
    },

    "AdminUsers": {
      "Type": "AWS::IAM::Group",
      "Properties": {
        "Path": "/"
      }
    },

    "AdminUserMembership": {
      "Type": "AWS::IAM::UserToGroupAddition",
      "Properties": {
        "GroupName": { "Ref": "AdminUsers" },
        "Users": [ { "Ref": "DeanwilsonAdmin" } ]
      }
    },

    "ExplicitAdmin": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "Groups": [ { "Ref": "AdminUsers" } ],
        "PolicyName": "admin-policy",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [ {
            "Action": "*",
            "Effect": "Allow",
            "Resource": "*"
          } ]
        }
      }
    }

  }
}

With a little rework the CloudFormation can be stripped down to the following two, quite deeply nested, resources.

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Add an admin group and users.",

  "Resources": {

    "DeanwilsonAdmin": {
      "Type": "AWS::IAM::User",
      "Properties": {
        "Path": "/",
        "Groups": [ { "Ref": "AdminUsers" } ]
      }
    },

    "AdminUsers": {
      "Type": "AWS::IAM::Group",
      "Properties": {
        "Path": "/",
        "Policies" : [ {
          "PolicyName": "admin-policy",
          "PolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [ {
              "Action": "*",
              "Effect": "Allow",
              "Resource": "*"
            } ]
          }
        } ]
      }
    }

  }
}

As a heavy CloudFormation user I don’t (yet) find the terraform syntax to be a massive win over CFN, although I suspect the lack of a nested “Properties” section will be the first thing to win me over. The terraform created resources having normal names, not the semi-random CloudFormation ones, has both its up and down sides and the granularity of ‘terraform plan’ output is a very nice feature. As a heavy user of a single cloud provider I don’t think I’d move to terraform yet, the conversion would take quite a while, but I do think I’ll continue to try it out for some of my smaller, personal projects to build up a library of functionality to make any future porting easier.

As a closing note for this kind of access control you should use the AWS provided managed policies where possible. Unfortunately most of the tooling current has one issue or another with it so for these examples I wrote the policy out in long hand.