Packer allows us to build images for different platforms, one of them being AWS. AWS images are built within the AWS environment, which means that Packer requires permissions to AWS in order to build an image. If we look at the official documentation it gives us the set of required permissions with "Resource": "*". This applies to all API endpoints they use, including for example DeleteKeyPair, DeregisterImage and TerminateInstances.

I do not really feel comfortable if an external program has permissions to delete any of my production resources. Thus, in this blog post I want to see what I can achieve to further restrict these permissions.

Let’s go through the permissions step by step. The official policy document in the Packer docs defines these required permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:AttachVolume",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:CopyImage",
        "ec2:CreateImage",
        "ec2:CreateKeypair",
        "ec2:CreateSecurityGroup",
        "ec2:CreateSnapshot",
        "ec2:CreateTags",
        "ec2:CreateVolume",
        "ec2:DeleteKeyPair",
        "ec2:DeleteSecurityGroup",
        "ec2:DeleteSnapshot",
        "ec2:DeleteVolume",
        "ec2:DeregisterImage",
        "ec2:DescribeImageAttribute",
        "ec2:DescribeImages",
        "ec2:DescribeInstances",
        "ec2:DescribeInstanceStatus",
        "ec2:DescribeRegions",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSnapshots",
        "ec2:DescribeSubnets",
        "ec2:DescribeTags",
        "ec2:DescribeVolumes",
        "ec2:DetachVolume",
        "ec2:GetPasswordData",
        "ec2:ModifyImageAttribute",
        "ec2:ModifyInstanceAttribute",
        "ec2:ModifySnapshotAttribute",
        "ec2:RegisterImage",
        "ec2:RunInstances",
        "ec2:StopInstances",
        "ec2:TerminateInstances"
      ],
      "Resource": "*"
    }
  ]
}

The least dangerous ones depending on your threat models probably are Describe and Create actions. With Describe actions it might be possible that Packer reads the layout of your infrastructure and accidentally publishes it. With Create actions on the other hand it could happen that Packer creates some resources, does not delete them properly and thus causes additional cost.

The more dangerous ones for production environments are the Modify, Delete, Detach, Deregister, Stop and Terminate ones. These allow Packer to change currently running production infrastructure, or in the worst case even take it down. In my opinion this is something we should not allow and thus we need to modify the set of IAM permissions.

In addition we have a few more actions with other prefixes: AttachVolume, AuthorizeSecurityGroupIngress, CopyImage, GetPasswordData, RegisterImage, and RunInstances.

SSH Key Pair

First of all we can entirely get rid of the CreateKeyPair and DeleteKeyPair permissions by using ssh_keypair_name and ssh_private_key_file in Packer. ssh_keypair_name is the name of a key in your AWS account while ssh_private_key_file must point to the corresponding private key on your local computer.

So, let’s create a new private key pair for packer:

ssh-keygen -f /home/brati/.ssh/id_rsa_packer -N "" -C packer
cat /home/brati/.ssh/id_rsa_packer.pub

Next, import the public key into your EC2 key pair list (do not create a new key pair, as this would generate a different private key).

Add the following two lines to your Packer source block and delete the CreateKeyPair and DeleteKeyPair permissions from your Packer IAM policy.

ssh_keypair_name = "packer"
ssh_private_key_file = "~/.ssh/id_rsa_packer"

Security Group

Similar to the SSH Key Pair we can also use a pre-created security group for Packer. This allows us to get rid of the security group related permissions, namely CreateSecurityGroup, DeleteSecurityGroup, and AuthorizeSecurityGroupIngress. The permission DescribeSecurityGroups is still required, because Packer uses it to verify whether the specified security group actually exists.

So, create a security group named packer with SSH ingress permissions (most probably from anywhere, but can also be a static IP if Packer is running on one of your servers). Take a note of the new security group ID and add a line with the security group ID to your packer config:

security_group_id = "sg-0123456789abcdef0"

Delete Permissions

Next, I want to tackle the delete permissions. I understand that Packer needs to be able to cleanup resources that it creates during creation of the image. I also understand that of course it’s not possible to get rid of the creation of a new instance to create the image, i.e. at least the TerminateInstances call is necessary. However, I feel uneasy when Packer is allowed to delete production resources.

Thus, my idea is to restrict Packer’s delete permissions to resources with a specific tag that must be created by Packer when allocating the resources. That way, Packer will never be able to delete resources which it did not create (unless someone else uses the same tag during creation of resources).

For this we can define the settings run_tags and run_volume_tags in Packer. These options will add tags to the volumes and instances that get created by Packer for the AMI creation process. snapshot_tags and tags will apply tags to the snapshots and image of the created AMI.

This allows us to make the deletion actions DeleteSnapshot, DeleteVolume, DeregisterImage, StopInstances and TerminateInstances a bit safer. Also the modification actions ModifyImageAttribute, ModifyInstanceAttribute, and ModifySnapshotAttribute should profit from this change, but during my tests it seems these were not used by Packer at all.

I would also define AttachVolume and DetachVolume to be a bit unsafe, because Packer could mount and unmount production volumes. Ideally, I would like to restrict Packer to mount volumes with the tag Creator=Packer on instances with the tag Creator=Packer, but I am not sure whether I can define such a two-level constraint with IAM request conditions.

Add the following lines to your Packer source block:

run_tags = {
  Creator = "Packer"
}
run_volume_tags = {
  Creator = "Packer"
}
snapshot_tags = {
  Creator = "Packer"
}
tags = {
  Creator = "Packer"
}

Adjust your IAM permissions and add a new section with more restricted permissions (this already includes the changes we performed in the previous sections):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "RatherSafeActions",
            "Effect": "Allow",
            "Action": [
                "ec2:CopyImage",
                "ec2:CreateImage",
                "ec2:CreateSnapshot",
                "ec2:CreateTags",
                "ec2:CreateVolume",
                "ec2:DescribeImages",
                "ec2:DescribeImageAttribute",
                "ec2:DescribeInstanceStatus",
                "ec2:DescribeInstances",
                "ec2:DescribeRegions",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSnapshots",
                "ec2:DescribeSubnets",
                "ec2:DescribeVolumes",
                "ec2:DescribeTags",
                "ec2:RegisterImage",
                "ec2:RunInstances",
                "ec2:GetPasswordData"
            ],
            "Resource": "*"
        },
        {
            "Sid": "DangerousActions",
            "Effect": "Allow",
            "Action": [
                "ec2:AttachVolume",
                "ec2:DeleteSnapshot",
                "ec2:DeleteVolume",
                "ec2:DeregisterImage",
                "ec2:DetachVolume",
                "ec2:ModifyImageAttribute",
                "ec2:ModifyInstanceAttribute",
                "ec2:ModifySnapshotAttribute",
                "ec2:StopInstances",
                "ec2:TerminateInstances"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "ec2:ResourceTag/Creator": "Packer"
                }
            }
        }
    ]
}

Further Improvements

Depending on your Packer definitions you might be able to further reduce these permissions. As mentioned I think that the Modify actions were not used in my case, so I might be able to delete them and still get AMIs.

Moreover, the permission GetPasswordData seems to be only required on Windows VMs. Thus, if you only want to build Linux images you can delete this permission. If you want to keep it, restricting to instances created by Packer does not seem to be required as to my understanding retrieving the password from a Windows host works by decrypting the password with the SSH key pair. I.e. the Packer SSH key can only decrypt passwords from Windows hosts that were created with the Packer SSH key pair.

I recently heard the idea of tagging resources with a autodelete marker and then automatically deleting them. During research for this article I also saw an AWS blog article mentioning IAM policies that enforce definition of tags. Combining these two we could extend the IAM policy to only allow Packer to create resources with the tag Creator: Packer or maybe even a tag Autodelete: [some date].

Depending on the regions you are using it can also make sense to use resource-based restrictions to restrict all actions to one or more AWS regions.

Packer Definition

I based my tests on the Packer tutorial. The final HCL definition looks like this:

packer {
  required_plugins {
    amazon = {
      version = ">= 0.0.1"
      source  = "github.com/hashicorp/amazon"
    }
  }
}

source "amazon-ebs" "ubuntu" {
  ami_name      = "learn-packer-linux-aws"
  instance_type = "t2.micro"
  region        = "eu-central-1"
  source_ami_filter {
    filters = {
      name                = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*"
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = ["099720109477"]
  }
  ssh_username = "ubuntu"
  ssh_keypair_name = "packer"
  ssh_private_key_file = "~/.ssh/id_rsa_packer"
  security_group_id = "sg-0fe7ca268edf55a34"
  run_tags = {
    Creator = "Packer"
  }
  run_volume_tags = {
    Creator = "Packer"
  }
  snapshot_tags = {
    Creator = "Packer"
  }
  tags = {
    Creator = "Packer"
  }
}

build {
  sources = [
    "source.amazon-ebs.ubuntu"
  ]

  provisioner "shell" {
    environment_vars = [
      "FOO=hello world",
    ]
    inline = [
      "echo Installing Redis",
      "sleep 30",
      "sudo apt-get update",
      "sudo apt-get install -y redis-server",
      "echo \"FOO is $FOO\" > example.txt",
    ]
  }
}
I do not maintain a comments section. If you have any questions or comments regarding my posts, please do not hesitate to send me an e-mail to blog@stefan-koch.name.