Part 8 - A note on S3 access.

By Ben Outram / 2018-06-15

Private S3 access

Earlier in part five we walked through a script written for our EC2 web server instances to fetch a build of the Spring Boot S3 Example project from Amazon S3 storage.

We used a build file that has been made publicly accessible in S3. What happens if you want to copy a file that is not public?

Continuing with the code already built by this lab, let's keep the services configured in part five working by assuming that this is still a Spring Boot application with the same filename and that your simply going to take a copy of the springboot-s3-example.jar file from the public S3 bucket and copy it to a private S3 bucket that you own.

Change the value of the s3_bucket_name variable in terrafrom.tfvars to match the name of your private S3 bucket. For example, if your bucket is s3://my-private-s3-example this would be:

s3_bucket_name = "my-private-s3-example"

We will need to pass an IAM role to the instances when they start. This will be used to pass credentials allowing access to the S3 bucket. The AWS CLI will get temporary security credentials from the instance metadata.

Create a new file iam.tf which we will add all of our role configuration to.

Create the S3 access role with an inline policy allowing the AWS CLI to assume roles:

resource "aws_iam_role" "s3_access_role" {
  name = "s3_access_role"
  path = "/"

  assume_role_policy = << EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
               "Service": "ec2.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
EOF
}

Attach a policy to the S3 access role with permissions to list and retrieve objects in the code bucket. This uses a wildcard to allow access to all files in the bucket but it can easily be made more specific.

resource "aws_iam_role_policy" "s3_code_bucket_access_policy" {
  name = "s3_code_bucket_access_policy"
  role = "${aws_iam_role.s3_access_role.id}"

  policy = << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::${var.s3_bucket_name}"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": ["arn:aws:s3:::${var.s3_bucket_name}/*"]
    }
  ]
}
EOF
}

Create an instance profile for the S3 access role that will passed to the EC2 instances when they start:

resource "aws_iam_instance_profile" "example_profile" {
  name = "terraform_instance_profile"
  role = "${aws_iam_role.s3_access_role.name}"
}

Find the launch configuration that's used with the EC2 auto scaling group. This was created in part six in file web.tf.

Add the following property to the configuration block to specify the instance profile:

iam_instance_profile = "${aws_iam_instance_profile.example_profile.id}"

The launch configuration in web.tf should now look like this:

resource "aws_launch_configuration" "launch_config" {
  name_prefix                 = "terraform-example-web-instance"
  image_id                    = "${lookup(var.amis, var.region)}"
  instance_type               = "${var.instance_type}"
  key_name                    = "${aws_key_pair.deployer.id}"
  security_groups             = ["${aws_security_group.default.id}"]
  associate_public_ip_address = true
  user_data                   = "${data.template_file.provision.rendered}"
  iam_instance_profile        = "${aws_iam_instance_profile.example_profile.id}"

  lifecycle {
    create_before_destroy = true
  }
}

In the provision.sh shell script find the aws s3 copy command. Remove the option to not sign requests otherwise credentials will not be loaded.

Replace:

aws s3 cp s3://${s3_bucket_name}/ /opt/springboot-s3-example/ --region=${region} --no-sign-request --recursive --exclude "*" --include "springboot-s3-example*.jar"

with:

aws s3 cp s3://${s3_bucket_name}/ /opt/springboot-s3-example/ --region=${region} --recursive --exclude "*" --include "springboot-s3-example*.jar"

Finally, we need to give our Terraform IAM user the ability to apply and destroy the role and instance profile.

  1. Navigate to the IAM console at https://console.aws.amazon.com/iam/.
  2. Navigate to 'Policies' and then click the 'Create policy' button.
  3. Choose the 'JSON editor' tab and replace the default empty policy with the policy document below.
  4. Click the 'Review policy' button.
  5. Enter a policy name, e.g. TerraformRolePolicy and then click the 'Create policy' button.
  6. Now we are going to apply the policy to the Terraform users group that you should have created during part one. Navigate to 'Groups' and select the Terraform users group.
  7. Choose the 'Permissions' tab and then click the 'Attach Policy' button.
  8. Make it easier to find your policy by changing the filter to show 'Customer Managed' policies. Select the policy that you just created and then click 'Attach Policy'.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:CreateInstanceProfile",
        "iam:GetInstanceProfile",
        "iam:DeleteInstanceProfile",
        "iam:AddRoleToInstanceProfile",
        "iam:RemoveRoleFromInstanceProfile"
      ],
      "Resource": ["arn:aws:iam::*:instance-profile/terraform_instance_profile"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:PassRole",
        "iam:CreateRole",
        "iam:GetRole",
        "iam:DeleteRole",
        "iam:GetRolePolicy",
        "iam:PutRolePolicy",
        "iam:DeleteRolePolicy",
        "iam:ListInstanceProfilesForRole"
      ],
      "Resource": ["arn:aws:iam::*:role/s3_access_role"]
    }
  ]
}

This completes the changes to configuration that are required for our EC2 instances to copy a file from a private bucket.

You can find all the source code for this part of the lab including the role policy JSON document here in GitHub.

Manually removing the Instance Profile

You should never need to interfere with resources that are managed by Terraform using the AWS web console or command line interface (CLI). However, this note is here to help if you are in the unfortunate state of manually needing to unpick your Terraform stack. For example, you may have accidentally lost the Terraform state.

Removing the EC2 instances and the IAM S3 Access Role will not remove the instance profile that is associated with it. The next Terraform apply will fail due to the instance profile existing.

Instance profiles are not visible in the AWS web console. To remove it you will need to use the AWS CLI.

Listing the instance profiles requires an additional AWS permission which you will need to apply to the Terraform users group via a policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["iam:ListInstanceProfiles"],
      "Resource": ["*"]
    }
  ]
}

Providing that you have set up the CLI with your access credentials, list all of the instance profiles using:

$ aws iam list-instance-profiles

This should reveal a profile named terraform_instance_profile. Delete it:

$ aws iam delete-instance-profile --instance-profile-name terraform_instance_profile

More posts in this series.