Create SSM Automation runbook to share AMI with another account using CloudFormation
One of the AWS SOA topics is related to deployment, provisioning, and automation.
The created AMI can be shared with another account.
Two methods are introduced on the official AWS page.
- AWS Management Console
- AWS CLI
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sharingamis-explicit.html
This time, we will create a custom SSM Automation runbook to achieve the same action as above.
Environment
Create an EC2 instance in a private subnet.
The instance will be the latest Amazon Linux 2.
Create an SSM Automation runbook.
The contents of this runbook are as follows
- Create an AMI for the instance.
- Share the created AMI with another account.
It also creates an association between this runbook and the instance.
CloudFormation template files
The above configuration is built with CloudFormation.
The CloudFormation templates are placed at the following URL
https://github.com/awstut-an-r/awstut-soa/tree/main/03/005
Explanation of key points of template files
Automation Runbook
Resources:
CreateAndShareAmiRunbook:
Type: AWS::SSM::Document
Properties:
Content:
assumeRole: "{{AutomationAssumeRole}}"
description: Create and Share AMI with another account.
schemaVersion: "0.3"
parameters:
AccountId:
type: String
description: (Required) The Account ID you want to share AMI.
default: ""
AutomationAssumeRole:
type: String
description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
default: ""
InstanceId:
type: String
description: (Required) The instance ID you want to run commands on.
default: ""
mainSteps:
- name: createImage
action: aws:executeAutomation
maxAttempts: 1
timeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
onFailure: Abort
inputs:
DocumentName: AWS-CreateImage
RuntimeParameters:
AutomationAssumeRole:
- "{{AutomationAssumeRole}}"
InstanceId:
- "{{InstanceId}}"
outputs:
- Name: AmiStringList
Selector: "$"
Type: String
- name: convertStringListToString
action: "aws:executeScript"
inputs:
Runtime: python3.8
Handler: handler
InputPayload:
StringList:
- "{{createImage.Output}}"
Script: |-
def handler(events, context):
list = events['StringList']
return {'First': list[0]}
outputs:
- Name: Ami
Selector: $.Payload.First
Type: String
- name: shareAmiWithAnotherAccount
action: aws:executeAwsApi
maxAttempts: 1
onFailure: Abort
inputs:
Service: ec2
Api: modify-image-attribute
ImageId: "{{convertStringListToString.Ami}}"
LaunchPermission:
Add:
- UserId: "{{AccountId}}"
DocumentFormat: YAML
DocumentType: Automation
Name: !Sub "${Prefix}-CreateAndShareAmiRunbook"
TargetType: /AWS::EC2::Instance
Code language: YAML (yaml)
Create a custom runbook.
For more information on creating runbooks, please see the following pages.
The Content property defines the contents of the runbook.
In the parameters data element, specify the parameters for executing this runbook.
In this case, we receive the following three parameters
- AccountId: ID of the account that shares the AMI
- AutomationAssumeRole: ARN of the service role that Autometion will use to run this runbook
- InstanceId: ID of the instance that creates the AMI
The mainSteps data element describes the process to be performed in the runbook.
The book consists of three steps.
Step 1
The first step is to create an AMI from the instance.
AMI creation is done using the SSM Automation runbook AWS-CreateImage.
You will be executing another Automation runbook within Automation, which can be accomplished by executing the aws:executeAutomation action.
Specify the parameters required to run AWS-CreateImage on inputs.
In this case, specify the service role for SSM and the ID of the instance to create the AMI.
Specify the data returned after running AWS-CreateImage with outputs.
After this runbook is executed, the ID of the created AMI will be returned, so we will take this as is.
Step 2
The second step converts the type of data received in the first step.
The type of data returned after executing the aws:executeAutomation action is a StringList.
Output
The output generated by the secondary automation. You can reference the output by using the following format: Secondary_Automation_Step_Name.Output
Type: StringList
aws:executeAutomation – Run another automation
In other words, the data returned after executing AWS-CreateImage will be in the form “[‘ami-abcdefg’]”, meaning that the AMI ID cannot be received as a string.
In the three steps described below, this AMI ID is used for processing, and the data type sought is a String.
Therefore, cast the StringList type to a String type by referring to the following page.
https://qiita.com/r18j21/items/6aae052e67455cce2e4e
The way to cast it is to execute a Python script with an aws:executeScript action.
The content of this script is to refer to the data output in the first step and return the first data.
This will convert the data that was of type StringList to a String.
Step 3
The third step is to share the AMI with another account.
The AMI is shared by executing an AWS API in the form of an aws:executeAwsApi action.
https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeAwsApi.html
AMI sharing can be performed with the modify-image-attribute method of the EC2 service.
To execute this API, two parameters are set.
The first is the ImageId. This specifies the AMI ID converted to a String type in two steps.
The second is LaunchPermission, which specifies the account with which the AMI will be shared.
Service Roles for Automation
Resources:
CreateAndShareAmiRunbookRole:
Type: AWS::IAM::Role
DeletionPolicy: Delete
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- ssm.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
Policies:
- PolicyName: CopyAndShareAmiWithAnotherAccountPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ec2:CreateImage
- ec2:DescribeInstances
- ec2:ModifyImageAttribute
- iam:PassRole
Resource:
- "*"
Code language: YAML (yaml)
Normally, if no service role is specified, the default service link role (SLR), AWSServiceRoleforAmazonSSM, is used.
In this case, create the service role according to the following official AWS page.
https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-setup-iam.html
In addition, grant the necessary permissions for AMI creation and sharing as an inline policy.
SSM Association
Resources:
CreateAndShareAmiRunbookAssociation:
Type: AWS::SSM::Association
Properties:
AssociationName: !Sub "${Prefix}-CreateAndShareAmiRunbookAssociation"
AutomationTargetParameterName: InstanceId
Name: !Ref CreateAndShareAmiRunbook
Parameters:
AccountId:
- !Ref AccountId
AutomationAssumeRole:
- !GetAtt CreateAndShareAmiRunbookRole.Arn
InstanceId:
- "{{RESOURCE_ID}}"
Targets:
- Key: !Sub "tag:${TagKey}"
Values:
- !Ref TagValue
WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)
Create an association between the runbook and the instance described above.
For information on how to create SSM associations and run SSM Automation runbook on EC2 instances, please see the following page.
In this case, instances with the following tags are targeted for association.
- Tag Key: MyDocument
- Tag Value: Group1
Tag instances with the above conditions to make them eligible for association.
Architecting
Use CloudFormation to build this environment and check its actual behavior.
Create CloudFormation stacks and check the resources in the stacks
Create CloudFormation stacks.
For information on how to create stacks and check each stack, please refer to the following pages.
After reviewing the resources in each stack, information on the main resources created in this case is as follows
- EC2 instance: i-0f5ba431de00d32a6
- Custom SSM Automation runbook: soa-03-005-CreateAndShareAmiRunbook
- SSM association: da0a89d3-bb77-480b-afda-c15d5f8fffec
Action Check
Now that you are ready, check each resource from the AWS Management Console.
Custom SSM Automation Runbook
Check the runbook you have created.
You can see that the runbook has been successfully created.
There are three parameters in this runbook, the contents of which are to be shared with another account after the AMI of the EC2 instance is created.
SSM Association
関連付けを確認します。
You can see that an association has been created for the aforementioned runbook.
Parameters for running the runbook.
The account specified in AccountId and the AMI to be created in the runbook will be shared.
Looking at the target, we can see that the target is the instance whose tag name MyDocument has the value “Group1”.
Check the run history of the runbook.
Execution has been completed successfully.
Check the detailed log of each step performed.
Check the first step.
The AWS-CreateImage runbook was executed with the service role for SSM and instance ID as parameters.
The Outputs shows that an AMI was created.
The ID of the AMI created this time is “ami-06b79a64a1a2b1447”.
Check the second step.
The Python script is now executed.
Looking at Input parameters, the AMI ID mentioned above is stored as a StringList.
The Outputs section outputs this as a String type.
Check the third step.
The modify-image-attribute method has been executed and the AMI has been shared to another account.
Finally, check the details of the AMI you have created.
If you look at the Shared accounts section, you will indeed see the ID of another account.
This means that by creating a custom SSM Automation runbook and associating it with an EC2 instance, an AMI for this instance was created and then the AMI was shared with another account.
Summary
We have identified a way to share an AMI with another account using a custom SSM Automation runbook.