Updating AWS AMIs with EC2 Systems Manager

December 11, 2016

Amazon Web Services recently launched the EC2 Systems Manager suite, including an Automation service for common maintenance tasks. This blog will show a few worked examples of how to use the service, with a focus on doing automated updates of Amazon Machine Images (AMIs) for Linux-family operating systems.

Getting Started

Arguably the biggest hurdle to getting started is in configuring appropriate permissions via Identity & Access Management (IAM). That topic is covered in the Configure Access to Automation walk-through, although I’ll point out a few of the key points here, as well.

Fortunately, this is all one time setup.

The Automation service works by performing tasks on behalf of a user. So you need to specify an IAM role in your account, under which the service will execute your workflow. This role must have permissions to do all of the tasks you are requesting. For example, if you want to launch an EC2 instance, you’d need to attach a policy with permission for the RunInstances action.

Suffice to say that creating a fine-grained individually-tailored policy would take some time, so a default policy has been provided by AWS, named AmazonSSMAutomationRole.

So, firstly create a role in your account, perhaps named AutomationRole, and attach the AWS-provided AmazonSSMAutomationRole policy to it.

Additionally, attach an inline policy to your AutomationRole, to allow IAM PassRole permissions on *. This allows you to hand the role.

Next, under the role’s “Trust Relationships”, trust the ssm.amazonaws.com service. The Trust relationship document would look like this, with ec2.amazonaws.com still there (we still want to trust EC2.)

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

In the case of the AWS-UpdateLinuxAmi document, it will also launch an instance to use as a workspace while it is applying updates. In order to this, it needs a role to attach to the launched instance. So, create a second role called InstanceRole and give it AmazonEC2RoleforSSM permissions. Ensure that ssm.amazonaws.com is in its Trust Relationships as above.

Exploring the AWS-UpdateLinuxAmi Public Automation Document

Automation allows you to create and execute your own automation documents. But before getting into that, it’s probably a good idea to understand how the public example AWS-UpdateLinuxAmi is put together.

In the AWS Console, go to the EC2 dashboard and look for “Systems Manager Shared Resources” on the left nav bar. Or, just follow this link to the document, directly.

Take a quick look at the document contents and observe the input Parameters section, as well as the mainSteps that make up the automation.

Parameters for AWS-UpdateLinuxAmi document shown in AWS EC2 Console

Document contents of AWS-UpdateLinuxAmi as shown in AWS EC2 Console Documents view

Updating an AMI with the AWS-UpdateLinuxAmi Public Automation Document

Alright, enough of that. Once we have a basic idea of what we’ve signed up for, let’s see it in action. Go to the “Automations” tab, or follow this direct link.

Listing of Automation Executions in the AWS EC2 Console

Under “Document Name”, select “AWS-UpdateLinuxAmi”. Use the default version, version 1. At this point, the UI will expand to drop down input fields for all of the runtime-configurable input parameters.

Run Automation view in AWS EC2 Console

While there are several parameters available in total, only a few are required:

SourceAmiId is the AMI we want to use as a base; the thing we’re going to update. Suppose you want to update the distribution packages on the latest public Ubuntu 16.04 LTS AMI, and perhaps bake your software into the image as well. You could go to the Ubuntu Cloud Images directory and find a suitable base image by filtering on region and OS version. At the time I’m writing this, ami-e6d5d2f1 is the Ubuntu-approved 16.04 LTS image in Amazon’s us-east-1 (Northern Virginia) region. (Keep in mind that AMIs are different per region.)

InstanceIamRole is the role to be attached to the instance. It should have at least permission for AmazonEC2RoleforSSM, attached. If you followed the steps on this page, this value would be InstanceRole (note that this field just a role name, not a role ARN.) If you want a simpler setup, you could potentially just re-use the same AutomationRole, provided that has AmazonEC2RoleforSSM permission.

AutomationAssumeRole – The ARN for the role you created, which you can see on the Roles page. Supposing your AWS account id is 131416101456 (it isn’t), then your Role ARN would be arn:aws:iam::131416101456:role/AutomationRole.

And that’s it! Lots of security, huh? Good to be safe, I guess.

At the bottom of the page, click “Run Automation” and then monitor the progress in the Automations tab.

You can also configure CloudWatch Events to be notified when the task completes, but that’s outside the scope of this blog.

Finding the Updated AMI

When the automation completes successfully, you’ll see a new AMI under the “AMIs” pane. (Find it by clicking on AMIs under the left nav bar, or by following this direct link.)

Unless you changed the default AMI name (as one of the input parameters to the execution) it should be named like UpdateLinuxAmi_from_ami-e6d5d2f1_on_2016-12-07_22.46.21.

List of self-owned AMIs in AWS EC2 Console

Using the CLI

For an automation service, that’s a lot of manual steps. What if you want to do it all in one step? Well, you’ll still have to setup the security, and you could do that with the CLI as well.

But supposing that is already done, you could launch an automation with one command, start-automation-execution:

aws ssm start-automation-execution \
    --document-name "AWS-UpdateLinuxAmi" \
    --parameters \
    "AutomationAssumeRole=arn:aws:iam::131416101456:role/AutomationRole,
     SourceAmiId=ami-e6d5d2f1,
     InstanceIamRole=InstanceRole"

Which will return the ID of the newly started execution:

{
    "AutomationExecutionId": "5d8cd400-f0af-4e3d-8290-9b2115d42458"
}

You can see its execution status via get-automation-execution:

 aws ssm get-automation-execution \
     --automation-execution-id "5d8cd400-f0af-4e3d-8290-9b2115d42458"

Which will show:

{
    "AutomationExecution": {
        "AutomationExecutionStatus": "InProgress",
        "Parameters": {
            "SourceAmiId": [
                "ami-e6d5d2f1"
            ],
            "AutomationAssumeRole": [
                "arn:aws:iam::131416101456:role/AutomationRole"
            ],
            "InstanceIamRole": [
                "InstanceRole"
            ]
        },
        "Outputs": {
            "createImage.ImageId": [
                "No output available yet because the step is not successfully executed"
            ]
        },
        "DocumentName": "AWS-UpdateLinuxAmi",
        "AutomationExecutionId": "5d8cd400-f0af-4e3d-8290-9b2115d42458",
        "DocumentVersion": "1",
        "ExecutionStartTime": 1481497945.1459999,
        "StepExecutions": [
            {
                "Inputs": {
                    "MaxInstanceCount": "1",
                    "UserData": "\"IyEvYmluL2Jhc2gNCg0KZnVuY3Rpb24gZ2V0X2NvbnRlbnRzKCkgew0KICAgIGlmIFsgLXggIiQod2hpY2ggY3VybCkiIF07IHRoZW4NCiAgICAgICAgY3VybCAtcyAtZiAiJDEiDQogICAgZWxpZiBbIC14ICIkKHdoaWNoIHdnZXQpIiBdOyB0aGVuDQogICAgICAgIHdnZXQgIiQxIiAtTyAtDQogICAgZWxzZQ0KICAgICAgICBkaWUgIk5vIGRvd25sb2FkIHV0aWxpdHkgKGN1cmwsIHdnZXQpIg0KICAgIGZpDQp9DQoNCnJlYWRvbmx5IElERU5USVRZX1VSTD0iaHR0cDovLzE2OS4yNTQuMTY5LjI1NC8yMDE2LTA2LTMwL2R5bmFtaWMvaW5zdGFuY2UtaWRlbnRpdHkvZG9jdW1lbnQvIg0KcmVhZG9ubHkgVFJVRV9SRUdJT049JChnZXRfY29udGVudHMgIiRJREVOVElUWV9VUkwiIHwgYXdrIC1GXCIgJy9yZWdpb24vIHsgcHJpbnQgJDQgfScpDQpyZWFkb25seSBERUZBVUxUX1JFR0lPTj0idXMtZWFzdC0xIg0KcmVhZG9ubHkgUkVHSU9OPSIke1RSVUVfUkVHSU9OOi0kREVGQVVMVF9SRUdJT059Ig0KDQpyZWFkb25seSBTQ1JJUFRfTkFNRT0iYXdzLWluc3RhbGwtc3NtLWFnZW50Ig0KcmVhZG9ubHkgU0NSSVBUX1VSTD0iaHR0cHM6Ly9hd3Mtc3NtLWRvd25sb2Fkcy0kUkVHSU9OLnMzLmFtYXpvbmF3cy5jb20vc2NyaXB0cy8kU0NSSVBUX05BTUUiDQoNCmNkIC90bXANCmdldF9jb250ZW50cyAiJFNDUklQVF9VUkwiID4gIiRTQ1JJUFRfTkFNRSINCmNobW9kICt4ICIkU0NSSVBUX05BTUUiDQouLyIkU0NSSVBUX05BTUUiIC0tcmVnaW9uICIkUkVHSU9OIg0K\"",
                    "MinInstanceCount": "1",
                    "ImageId": "\"ami-e6d5d2f1\"",
                    "IamInstanceProfileName": "\"InstanceRole\"",
                    "InstanceType": "\"t2.micro\""
                },
                "Outputs": {
                    "InstanceStates": [
                        "running"
                    ],
                    "InstanceIds": [
                        "i-01ca8c5421617aeff"
                    ]
                },
                "StepName": "launchInstance",
                "ExecutionStartTime": 1481497945.5339999,
                "Action": "aws:runInstances",
                "StepStatus": "InProgress"
            },
            {
                "Action": "aws:runCommand",
                "StepName": "updateOSSoftware",
                "StepStatus": "Pending"
            },
            {
                "Action": "aws:changeInstanceState",
                "StepName": "stopInstance",
                "StepStatus": "Pending"
            },
            {
                "Action": "aws:createImage",
                "StepName": "createImage",
                "StepStatus": "Pending"
            },
            {
                "Action": "aws:changeInstanceState",
                "StepName": "terminateInstance",
                "StepStatus": "Pending"
            }
        ]
    }
}

You can filter out the name of the ID of the new AMI with:

aws ssm get-automation-execution \
    --automation-execution-id "5d8cd400-f0af-4e3d-8290-9b2115d42458" | \
jq '.AutomationExecution.Outputs["createImage.ImageId"][0]'

Under the Hood

You can see in the automation document that the one of the main steps is to download and run the following script on the EC2 instance workspace:

https://aws-ssm-downloads-{{global:REGION}}.s3.amazonaws.com/scripts/aws-update-linux-instance

{{global:REGION}} is an automation global parameter that expands to the region in which you’re running the execution. In my case, us-east-1.

So, it ends up just being a publicly-accessible Shell script that you can checkout.

Leave a Reply