AWS SSM automation to build Golden AMI
I know you are getting the first question in your mind, what do you mean by Golden AMI? A golden AMI is an AMI that contains the latest security patches, software, configuration, and software agents that you need to install for logging, security maintenance, and performance monitoring. We will use this preconfigure Golden AMI to launch an EC2 instance. Below are the steps to do this automation:
- Launch an instance from Source AMI mentioned in SSM Parameter Store
- Executes SSM Run Command that applies the vendor updates to the instance
- Stops the instance
- Creates a new AMI
- Encrypt the AMI
- Tag the AMI
- Update the parameter store using Lambda
- Delete unencrypted AMI
- Terminates the original instance
Pre-Requisites
SSM Parameter Store:
- Parameter Name: /GoldenAMI/Linux/RedHat-7/source (This parameter is used to store plane Source AMIid)
- Parameter Name: /GoldenAMI/Linux/RedHat-7/latest (This parameter is to store latest AMIid after performing SSM automation steps)
RoleName: lambda-ssm-role: This role is required to execute Lambda function
- Permissions: Managed Policies AWSLambdaExecute and AmazonSSMFullAccess
RoleName: ManagedInstanceRole: - An EC2 Role to allow SSM to start instances, create images etc,
- Permissions: Managed Policies AmazonEC2RoleforSSM
RoleName: AutomationServiceRole: - An EC2 Role to Allow SSM to run documents and allow it to assume ManagedInstanceRole we just created.
Note: I have been created a pre-requisite with the Cloudformation template. Here is the attached code.
AWSTemplateFormatVersion: '2010-09-09' Description: AWS CloudFormation template for GoldenAmi Automation with SSM ## This is a Lambda SSM role creation Resources: LambdaSSMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSLambdaExecute - arn:aws:iam::aws:policy/AmazonSSMFullAccess - arn:aws:iam::aws:policy/AmazonS3FullAccess Path: "/" ## An EC2 Role to allow SSM to start instances, create images etc ManagedInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ssm.amazonaws.com - ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess Path: "/" ManagedInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - !Ref ManagedInstanceRole InstanceProfileName: ManagedInstanceProfile #An EC2 Role to Allow SSM to run documents and allow it to assume ManagedInstanceRole we just created AutomationServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ssm.amazonaws.com - ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole - arn:aws:iam::aws:policy/service-role/AWSLambdaRole Path: "/" RoleName: AutomationServiceRole Policies: - PolicyName: passrole PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - iam:PassRole Resource: - !GetAtt ManagedInstanceRole.Arn #Create Parameter for Golden AMI GoldenAmiParameterSource: Type: AWS::SSM::Parameter Properties: Name: /GoldenAMI/Linux/RedHat-7/source Type: String Value: ami-039a49e70ea773ffc Description: SSM Parameter to store AMI value. Tags: Environment: DEV GoldenAmiParameter: Type: AWS::SSM::Parameter Properties: Name: /GoldenAMI/Linux/RedHat-7/latest Type: String Value: to-be-updated Description: SSM Parameter to store AMI value. Tags: Environment: DEV
Create Lambda to update SSM Parameter Store
This function will help us to automatically update the parameter store with the latest AMI when the Automation Document successfully creates a new image.
- Lambda Function Name: Choose it as Automation-UpdateSsmParam. If you change here, update the Automation Document also with the same name. Runtime should be Python 2.7
- Choose the lambda-ssm-role you created earlier.
- The code for lambda function is provided in this file Automation-UpdateSsmParam.py
Note: Here is the attached Lambda python code to update the SSM parameter.
from __future__ import print_function import json import boto3 print('Loading function') #Updates an SSM parameter #Expects parameterName, parameterValue def lambda_handler(event, context): print("Received event: " + json.dumps(event, indent=2)) # get SSM client client = boto3.client('ssm') #confirm parameter exists before updating it response = client.describe_parameters( Filters=[ { 'Key': 'Name', 'Values': [ event['parameterName'] ] }, ] ) if not response['Parameters']: print('No such parameter') return 'SSM parameter not found.' #if parameter has a Descrition field, update it PLUS the Value if 'Description' in response['Parameters'][0]: description = response['Parameters'][0]['Description'] response = client.put_parameter( Name=event['parameterName'], Value=event['parameterValue'], Description=description, Type='String', Overwrite=True ) #otherwise just update Value else: response = client.put_parameter( Name=event['parameterName'], Value=event['parameterValue'], Type='String', Overwrite=True ) reponseString = 'Updated parameter %s with value %s.' % (event['parameterName'], event['parameterValue'])
return reponseString
Create custom Automation Document
- Create Document, Give a Name, like Bake-GoldenAMI-Linux
- Add the contents of the Bake-GoldenAMI-Linux.json file in the field.
- After successful creation of the document, you should be able to view, modify versions,
Note: Here is the attached Bake-GoldenAMI-Linux.json code
description: >- Create a Golden AMI with Linux distribution packages(ClamAV) and Amazon software(SSM & Inspector). For details,see schemaVersion: '0.3' assumeRole: '{{AutomationAssumeRole}}' outputs: - createImage.ImageId parameters: SourceAmiId: type: String description: (Required) The source Amazon Machine Image ID. default: '{{ssm:/GoldenAMI/Linux/RedHat-7/source}}' InstanceIamRole: type: String description: >- (Required) The name of the role that enables Systems Manager (SSM) to manage the instance. default: ManagedInstanceProfile AutomationAssumeRole: type: String description: >- (Required) The ARN of the role that allows Automation to perform the actions on your behalf. default: 'arn:aws:iam::{{global:ACCOUNT_ID}}:role/AutomationServiceRole' SubnetId: type: String description: (Required) The subnet that the created instance will be placed into. default: '' TargetAmiName: type: String description: >- (Optional) The name of the new AMI that will be created. Default is a system-generated string including the source AMI id, and the creation time and date. default: 'GoldenAMI-RH-7_on_{{global:DATE_TIME}}' InstanceType: type: String description: >- (Optional) Type of instance to launch as the workspace host. Instance types vary by region. Default is t2.micro. default: t2.micro PreUpdateScript: type: String description: >- (Optional) URL of a script to run before updates are applied. Default ("none") is to not run a script. default: none PostUpdateScript: type: String description: >- (Optional) URL of a script to run after package updates are applied. Default ("none") is to not run a script. default: '' IncludePackages: type: String description: >- (Optional) Only update these named packages. By default ("all"), all available updates are applied. default: all ExcludePackages: type: String description: >- (Optional) Names of packages to hold back from updates, under all conditions. By default ("none"), no package is excluded. default: none lambdaFunctionName: type: String description: >- (Required) The name of the lambda function. Default ('none') is to not run a script. default: Automation-UpdateSsmParam mainSteps: - name: launchInstance action: 'aws:runInstances' maxAttempts: 3 timeoutSeconds: 1200 onFailure: Abort inputs: ImageId: '{{SourceAmiId}}' InstanceType: '{{InstanceType}}' SubnetId: '{{ SubnetId }}' UserData: >- IyEvYmluL2Jhc2gNCg0KZnVuY3Rpb24gZ2V0X2NvbnRlbnRzKCkgew0KICAgIGlmIFsgLXggIiQod2hpY2ggY3VybCkiIF07IHRoZW4NCiAgICAgICAgY3VybCAtcyAtZiAiJDEiDQogICAgZWxpZiBbIC14ICIkKHdoaWNoIHdnZXQpIiBdOyB0aGVuDQogICAgICAgIHdnZXQgIiQxIiAtTyAtDQogICAgZWxzZQ0KICAgICAgICBkaWUgIk5vIGRvd25sb2FkIHV0aWxpdHkgKGN1cmwsIHdnZXQpIg0KICAgIGZpDQp9DQoNCnJlYWRvbmx5IElERU5USVRZX1VSTD0iaHR0cDovLzE2OS4yNTQuMTY5LjI1NC8yMDE2LTA2LTMwL2R5bmFtaWMvaW5zdGFuY2UtaWRlbnRpdHkvZG9jdW1lbnQvIg0KcmVhZG9ubHkgVFJVRV9SRUdJT049JChnZXRfY29udGVudHMgIiRJREVOVElUWV9VUkwiIHwgYXdrIC1GXCIgJy9yZWdpb24vIHsgcHJpbnQgJDQgfScpDQpyZWFkb25seSBERUZBVUxUX1JFR0lPTj0idXMtZWFzdC0xIg0KcmVhZG9ubHkgUkVHSU9OPSIke1RSVUVfUkVHSU9OOi0kREVGQVVMVF9SRUdJT059Ig0KDQpyZWFkb25seSBTQ1JJUFRfTkFNRT0iYXdzLWluc3RhbGwtc3NtLWFnZW50Ig0KIFNDUklQVF9VUkw9Imh0dHBzOi8vYXdzLXNzbS1kb3dubG9hZHMtJFJFR0lPTi5zMy5hbWF6b25hd3MuY29tL3NjcmlwdHMvJFNDUklQVF9OQU1FIg0KDQppZiBbICIkUkVHSU9OIiA9ICJjbi1ub3J0aC0xIiBdOyB0aGVuDQogIFNDUklQVF9VUkw9Imh0dHBzOi8vYXdzLXNzbS1kb3dubG9hZHMtJFJFR0lPTi5zMy5jbi1ub3J0aC0xLmFtYXpvbmF3cy5jb20uY24vc2NyaXB0cy8kU0NSSVBUX05BTUUiDQpmaQ0KDQppZiBbICIkUkVHSU9OIiA9ICJ1cy1nb3Ytd2VzdC0xIiBdOyB0aGVuDQogIFNDUklQVF9VUkw9Imh0dHBzOi8vYXdzLXNzbS1kb3dubG9hZHMtJFJFR0lPTi5zMy11cy1nb3Ytd2VzdC0xLmFtYXpvbmF3cy5jb20vc2NyaXB0cy8kU0NSSVBUX05BTUUiDQpmaQ0KDQpjZCAvdG1wDQpGSUxFX1NJWkU9MA0KTUFYX1JFVFJZX0NPVU5UPTMNClJFVFJZX0NPVU5UPTANCg0Kd2hpbGUgWyAkUkVUUllfQ09VTlQgLWx0ICRNQVhfUkVUUllfQ09VTlQgXSA7IGRvDQogIGVjaG8gQVdTLVVwZGF0ZUxpbnV4QW1pOiBEb3dubG9hZGluZyBzY3JpcHQgZnJvbSAkU0NSSVBUX1VSTA0KICBnZXRfY29udGVudHMgIiRTQ1JJUFRfVVJMIiA+ICIkU0NSSVBUX05BTUUiDQogIEZJTEVfU0laRT0kKGR1IC1rIC90bXAvJFNDUklQVF9OQU1FIHwgY3V0IC1mMSkNCiAgZWNobyBBV1MtVXBkYXRlTGludXhBbWk6IEZpbmlzaGVkIGRvd25sb2FkaW5nIHNjcmlwdCwgc2l6ZTogJEZJTEVfU0laRQ0KICBpZiBbICRGSUxFX1NJWkUgLWd0IDAgXTsgdGhlbg0KICAgIGJyZWFrDQogIGVsc2UNCiAgICBpZiBbWyAkUkVUUllfQ09VTlQgLWx0IE1BWF9SRVRSWV9DT1VOVCBdXTsgdGhlbg0KICAgICAgUkVUUllfQ09VTlQ9JCgoUkVUUllfQ09VTlQrMSkpOw0KICAgICAgZWNobyBBV1MtVXBkYXRlTGludXhBbWk6IEZpbGVTaXplIGlzIDAsIHJldHJ5Q291bnQ6ICRSRVRSWV9DT1VOVA0KICAgIGZpDQogIGZpIA0KZG9uZQ0KDQppZiBbICRGSUxFX1NJWkUgLWd0IDAgXTsgdGhlbg0KICBjaG1vZCAreCAiJFNDUklQVF9OQU1FIg0KICBlY2hvIEFXUy1VcGRhdGVMaW51eEFtaTogUnVubmluZyBVcGRhdGVTU01BZ2VudCBzY3JpcHQgbm93IC4uLi4NCiAgLi8iJFNDUklQVF9OQU1FIiAtLXJlZ2lvbiAiJFJFR0lPTiINCmVsc2UNCiAgZWNobyBBV1MtVXBkYXRlTGludXhBbWk6IFVuYWJsZSB0byBkb3dubG9hZCBzY3JpcHQsIHF1aXR0aW5nIC4uLi4NCmZp MinInstanceCount: 1 MaxInstanceCount: 1 IamInstanceProfileName: '{{InstanceIamRole}}' - name: updateOSSoftware action: 'aws:runCommand' maxAttempts: 3 timeoutSeconds: 3600 onFailure: Abort inputs: DocumentName: AWS-RunShellScript InstanceIds: - '{{launchInstance.InstanceIds}}' Parameters: commands: - set -e - '[ -x "$(which wget)" ] && get_contents=''wget $1 -O -''' - '[ -x "$(which curl)" ] && get_contents=''curl -s -f $1''' - >- eval $get_contents https://aws-ssm-downloads-{{global:REGION}}.s3.amazonaws.com/scripts/aws-update-linux-instance > /tmp/aws-update-linux-instance - chmod +x /tmp/aws-update-linux-instance - >- /tmp/aws-update-linux-instance --pre-update-script '{{PreUpdateScript}}' --post-update-script '{{PostUpdateScript}}' --include-packages '{{IncludePackages}}' --exclude-packages '{{ExcludePackages}}' 2>&1 | tee /tmp/aws-update-linux-instance.log - name: installCustomizations action: 'aws:runCommand' maxAttempts: 3 timeoutSeconds: 600 onFailure: Abort inputs: DocumentName: AWS-RunShellScript InstanceIds: - '{{launchInstance.InstanceIds}}' Parameters: commands: - >- curl -O http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm - rpm -ivh epel-release-latest-7.noarch.rpm - yum -y install httpd - systemctl enable httpd - systemctl restart httpd - sudo yum --enablerepo=epel install -y clamav - yum-config-manager --disable epel - cat /etc/motd >> /var/www/html/index.html - echo 'Welcome' >> /var/www/html/index.html - cat > /etc/motd <<- EOF - ' __ __ _ _ _ _ ' - ' /\ | \/ (_) /\ | | | | (_) ' - ' / \ | \ / |_ / \ _ _| |_ ___ _ __ ___ __ _| |_ _ ___ _ __ ' - ' / /\ \ | |\/| | | / /\ \| | | | __/ _ \| ''_ ` _ \ / _` | __| |/ _ \| ''_ \ ' - ' / ____ \| | | | | / ____ \ |_| | || (_) | | | | | | (_| | |_| | (_) | | | |' - ' /_/ \_\_| |_|_| /_/ \_\__,_|\__\___/|_| |_| |_|\__,_|\__|_|\___/|_| |_|' - ' ' - ' ' - EOF - name: installInspectorAgent action: 'aws:runCommand' maxAttempts: 3 timeoutSeconds: 600 onFailure: Abort inputs: DocumentName: AmazonInspector-ManageAWSAgent InstanceIds: - '{{launchInstance.InstanceIds}}' Parameters: Operation: Install - name: installUnifiedCloudWatchAgent action: 'aws:runCommand' maxAttempts: 3 timeoutSeconds: 1200 onFailure: Abort inputs: DocumentName: AWS-ConfigureAWSPackage InstanceIds: - '{{launchInstance.InstanceIds}}' Parameters: name: AmazonCloudWatchAgent action: Install - name: stopInstance action: 'aws:changeInstanceState' maxAttempts: 3 timeoutSeconds: 1200 onFailure: Abort inputs: InstanceIds: - '{{launchInstance.InstanceIds}}' DesiredState: stopped - name: createImage action: 'aws:createImage' maxAttempts: 3 onFailure: Abort inputs: InstanceId: '{{launchInstance.InstanceIds}}' ImageName: '{{TargetAmiName}}' NoReboot: true ImageDescription: >- AMI Generated by EC2 Automation on {{global:DATE_TIME}} from {{SourceAmiId}} - name: createEncryptedCopy action: 'aws:copyImage' maxAttempts: 3 onFailure: Abort inputs: SourceImageId: '{{createImage.ImageId}}' SourceRegion: '{{global:REGION}}' ImageName: 'Encrypted-{{TargetAmiName}}' ImageDescription: >- Encrypted GoldenAMI by SSM Automation on {{global:DATE_TIME}} from source AMI {{createImage.ImageId}} Encrypted: true - name: createTagsForEncryptedImage action: 'aws:createTags' maxAttempts: 1 onFailure: Continue inputs: ResourceType: EC2 ResourceIds: - '{{createEncryptedCopy.ImageId}}' Tags: - Key: Automation-Id Value: '{{automation:EXECUTION_ID}}' - Key: Owner Value: Mystique - Key: SourceAMI Value: '{{SourceAmiId}}' - Key: Amazon-Inspector Value: 'true' - Key: Amazon-SSM Value: 'true' - Key: Encrypted Value: 'true' - name: updateSsmParam action: 'aws:invokeLambdaFunction' timeoutSeconds: 1200 maxAttempts: 1 onFailure: Abort inputs: FunctionName: Automation-UpdateSsmParam Payload: >- {"parameterName":"/GoldenAMI/Linux/RedHat-7/latest", "parameterValue":"{{createEncryptedCopy.ImageId}}"} - name: terminateInstance action: 'aws:changeInstanceState' maxAttempts: 3 onFailure: Continue inputs: InstanceIds: - '{{launchInstance.InstanceIds}}' DesiredState: terminated - name: deleteUnEcryptedImage action: 'aws:deleteImage' maxAttempts: 3 timeoutSeconds: 180 onFailure: Abort inputs: ImageId: '{{createImage.ImageId}}'
After completing above setup follow the below steps to test that automation successfully working or not.
- Go to AWS services and choose Systems Manager Services, Automations.
- Choose Execution automation and pick the one we just created. The easiest way is to search by "Owned by Me".
- Then finally click on the execute automation button and check the output.
Executive Assistant |Co-ordinator|MIS|Salesforce | Sales Operation|AR-Credit Control
5yVery nice