From c7356af64e0ccf799b731a06a0ab1488175f1ad5 Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Thu, 7 Mar 2019 00:10:18 -0500 Subject: [PATCH 01/10] initial version of EncryptRootVolume Document --- .../EncryptRootVolume/Design/Design.md | 28 +++ .../EncryptRootVolume/Design/schema.json | 28 +++ .../Documents/npark-encryptrootvolume.json | 198 ++++++++++++++++++ .../Automation/EncryptRootVolume/Makefile | 34 +++ .../CloudFormationTemplates/TestTemplate.yml | 128 +++++++++++ .../EncryptRootVolume/Tests/test_document.py | 170 +++++++++++++++ 6 files changed, 586 insertions(+) create mode 100644 Documents/Automation/EncryptRootVolume/Design/Design.md create mode 100644 Documents/Automation/EncryptRootVolume/Design/schema.json create mode 100644 Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json create mode 100644 Documents/Automation/EncryptRootVolume/Makefile create mode 100644 Documents/Automation/EncryptRootVolume/Tests/CloudFormationTemplates/TestTemplate.yml create mode 100644 Documents/Automation/EncryptRootVolume/Tests/test_document.py diff --git a/Documents/Automation/EncryptRootVolume/Design/Design.md b/Documents/Automation/EncryptRootVolume/Design/Design.md new file mode 100644 index 0000000..e4baec4 --- /dev/null +++ b/Documents/Automation/EncryptRootVolume/Design/Design.md @@ -0,0 +1,28 @@ +# Encrypt EBS root volume + +## Notes + +Encrypts the root volume of an EC2 instance. This will be a replace operation and not an in-line encryption operation. + +## Document Design + +Refer to schema.json + +Document Steps: +1. aws:npark-encryptrootvolume - Execute CloudFormation Template to attach the volume. + * Parameters: + * instanceId: (Required) Instance ID of the ec2 instance whose root volume needs to be encrypted + * region: (Required) Region in which the ec2 instance belong + * KmsKeyId: (Required) Customer KMS key to use during the encryption + * devicename: (Optional) Device name of the root volume. Defaults to /dev/sda1 + * AutomationAssumeRole: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf + +## Test script + +Python script will: +# 1. Create a test stack with an instance, a volume and a KMS Key (Customer managed) +# 2. Execute automation document to replace the root volume with the encrypted one (after a copy operation of the root volume snapshot) +# 3. Ensure the automation has executed successfully +# 4. Detach the old volume +# 5. Attach the new (and encrypted) volume +# 5. Clean up test stack diff --git a/Documents/Automation/EncryptRootVolume/Design/schema.json b/Documents/Automation/EncryptRootVolume/Design/schema.json new file mode 100644 index 0000000..daaf06e --- /dev/null +++ b/Documents/Automation/EncryptRootVolume/Design/schema.json @@ -0,0 +1,28 @@ +{ + "schemaVersion": "0.3", + "description": "Encrypt Root Volume", + "assumeRole": "{{ AutomationAssumeRole }}", + "parameters": { + "instanceId": { + "description": "Instance ID of the ec2 instance whose root volume needs to be encrypted", + "type": "String" + }, + "region": { + "description": "Region in which the ec2 instance belong", + "type": "String" + }, + "KmsKeyId": { + "description": "Customer KMS key to use during the encryption", + "type": "String" + }, + "devicename": { + "description": "Device name of the root volume. Defaults to /dev/sda1", + "type": "String" + }, + "AutomationAssumeRole": { + "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf", + "type": "String" + } + }, + "mainSteps": [] +} diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json new file mode 100644 index 0000000..4d202ae --- /dev/null +++ b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json @@ -0,0 +1,198 @@ +{ + "schemaVersion": "0.3", + "description": "Encrypt Root Volume Automation Document", + "assumeRole": "{{ AutomationAssumeRole }}", + "parameters": { + "instanceId": { + "description": "(Required) Instance ID of the ec2 instance whose root volume needs to be encrypted", + "type": "String" + }, + "region": { + "description": "(Required) Region in which the ec2 instance belong", + "type": "String" + }, + "KmsKeyId": { + "description": "(Required) Customer KMS key to use during the encryption", + "type": "String" + }, + "devicename": { + "description": "(Optional) Device name of the root volume. Defaults to /dev/sda1", + "type": "String" + }, + "AutomationAssumeRole": { + "type": "String", + "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", + "default": "" + } + }, + "mainSteps": [ + { + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "DescribeInstances", + "InstanceIds": [ + "{{instanceId}}" + ] + }, + "outputs": [ + { + "Name": "EBSVolumeID", + "Selector": "$.Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId", + "Type": "String" + } + ], + "name": "extractEBSvolumeID", + "action": "aws:executeAwsApi", + "timeoutSeconds": 30, + "onFailure": "Abort" + }, + { + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "DescribeInstances", + "InstanceIds": [ + "{{instanceId}}" + ] + }, + "outputs": [ + { + "Name": "PlacementAvailabilityZone", + "Selector": "$.Reservations[0].Instances[0].Placement.AvailabilityZone", + "Type": "String" + } + ], + "name": "extractAvailabilityZone", + "action": "aws:executeAwsApi", + "timeoutSeconds": 30, + "onFailure": "Abort" + }, + { + "maxAttempts": 3, + "inputs": { + "DocumentName": "AWS-CreateSnapshot", + "RuntimeParameters": { + "VolumeId": "{{extractEBSvolumeID.EBSVolumeID}}" + } + }, + "name": "CreateSnapshot", + "action": "aws:executeAutomation", + "timeoutSeconds": 3600, + "onFailure": "Abort" + }, + { + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "DescribeSnapshots", + "SnapshotIds": "{{CreateSnapshot.Output}}" + }, + "outputs": [ + { + "Name": "SNAPSHOTID", + "Selector": "$.Snapshots[0].SnapshotId", + "Type": "String" + } + ], + "name": "extractSnapshotID", + "action": "aws:executeAwsApi", + "timeoutSeconds": 30, + "onFailure": "Abort" + }, + { + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "CopySnapshot", + "SourceSnapshotId": "{{extractSnapshotID.SNAPSHOTID}}", + "SourceRegion": "{{region}}", + "Encrypted": true, + "KmsKeyId": "{{KmsKeyId}}", + "DestinationRegion": "{{region}}" + }, + "outputs": [ + { + "Name": "EncryptedSnapshotID", + "Selector": "$.SnapshotId", + "Type": "String" + } + ], + "name": "CopySnapshot", + "action": "aws:executeAwsApi", + "timeoutSeconds": 3600, + "onFailure": "Abort" + }, + { + "inputs": { + "Duration": "PT2M" + }, + "name": "sleep1", + "action": "aws:sleep" + }, + { + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "CreateVolume", + "AvailabilityZone": "{{extractAvailabilityZone.PlacementAvailabilityZone}}", + "Encrypted": true, + "KmsKeyId": "{{KmsKeyId}}", + "SnapshotId": "{{CopySnapshot.EncryptedSnapshotID}}", + "VolumeType": "gp2" + }, + "outputs": [ + { + "Name": "NewRootVolumeID", + "Selector": "$.VolumeId", + "Type": "String" + } + ], + "name": "CreateVolume", + "action": "aws:executeAwsApi", + "timeoutSeconds": 30, + "onFailure": "Abort" + }, + { + "maxAttempts": 1, + "inputs": { + "DocumentName": "AWS-StopEC2Instance", + "RuntimeParameters": { + "InstanceId": "{{instanceId}}" + } + }, + "name": "StopInstance", + "action": "aws:executeAutomation", + "timeoutSeconds": 300, + "onFailure": "Abort" + }, + { + "maxAttempts": 1, + "inputs": { + "DocumentName": "AWS-DetachEBSVolume", + "RuntimeParameters": { + "VolumeId": "{{extractEBSvolumeID.EBSVolumeID}}" + } + }, + "name": "DetachEBSVolume", + "action": "aws:executeAutomation", + "timeoutSeconds": 300, + "onFailure": "Abort" + }, + { + "maxAttempts": 1, + "inputs": { + "DocumentName": "AWS-AttachEBSVolume", + "RuntimeParameters": { + "Device": "{{devicename}}", + "InstanceId": "{{instanceId}}", + "VolumeId": "{{CreateVolume.NewRootVolumeID}}" + } + }, + "name": "AttachNewEBSVolume", + "action": "aws:executeAutomation", + "timeoutSeconds": 180, + "onFailure": "Abort" + } + ] +} diff --git a/Documents/Automation/EncryptRootVolume/Makefile b/Documents/Automation/EncryptRootVolume/Makefile new file mode 100644 index 0000000..7de9b11 --- /dev/null +++ b/Documents/Automation/EncryptRootVolume/Makefile @@ -0,0 +1,34 @@ +# +# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the "Software"), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +TARGET_DIR = "./Output" + +documents: targetdir createdocuments + @echo "Done making documents" + +targetdir: + @echo "Making $(TARGET_DIR)" + mkdir -p ./Output + +createdocuments: + python ./Setup/create_document.py > ./Output/npark-EncryptRootVolume.json + +test: documents + python -m unittest discover Tests + +clean: + @echo "Removing $(TARGET_DIR)" + @rm -rf ./Output diff --git a/Documents/Automation/EncryptRootVolume/Tests/CloudFormationTemplates/TestTemplate.yml b/Documents/Automation/EncryptRootVolume/Tests/CloudFormationTemplates/TestTemplate.yml new file mode 100644 index 0000000..1bb4385 --- /dev/null +++ b/Documents/Automation/EncryptRootVolume/Tests/CloudFormationTemplates/TestTemplate.yml @@ -0,0 +1,128 @@ +# +# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the "Software"), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: Test stack for encrypting root volume of an EC2 Instance +Outputs: + InstanceId: + Description: Instance Id + Value: + Ref: Instance0 + AutomationAssumeRoleName: + Description: Automation Assume Role Name + Value: !Ref AutomationAssumeRole + AutomationAssumeRoleARN: + Description: Automation Assume Role ARN + Value: !GetAtt AutomationAssumeRole.Arn + KmsKeyId: + Description: Export of KMS Key + Export: + Name: DiskEncryptKMSKeyArn + Value: + !Ref DiskEncryptKey + +Parameters: + AMI: + Description: AMI ID for instances. + Type: String + INSTANCETYPE: + Description: AMI Instance Type (t2.micro, m1.large, etc.) + Type: String + UserARN: + Description: user ARN + Type: String +Resources: + AutomationAssumeRole: + Type: "AWS::IAM::Role" + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Principal: + Service: + - "lambda.amazonaws.com" + - "ssm.amazonaws.com" + Action: + - "sts:AssumeRole" + - Effect: "Allow" + Principal: + AWS: !Ref UserARN + Action: + - "sts:AssumeRole" + ManagedPolicyArns: + - "arn:aws:iam::aws:policy/AdministratorAccess" + Instance0: + Type: AWS::EC2::Instance + Properties: + ImageId: !Ref AMI + InstanceType: !Ref INSTANCETYPE + Tags: + - Key: Name + Value: Test root volume encryption of an EC2 Instance + + DiskEncryptKey: + Type: AWS::KMS::Key + Properties: + KeyPolicy: + Version: 2012-10-17 + Id: key-default-1 + Statement: + - Sid: Enable IAM User Permissions + Effect: Allow + Principal: + AWS: !Join + - '' + - - 'arn:aws:iam::' + - !Ref 'AWS::AccountId' + - ':root' + Action: 'kms:*' + Resource: "*" + - Sid: Allow access for Key Administrators + Effect: Allow + Principal: + AWS: !Ref UserARN + Action: + - "kms:Create*" + - "kms:Describe*" + - "kms:Enable*" + - "kms:List*" + - "kms:Put*" + - "kms:Update*" + - "kms:Revoke*" + - "kms:Disable*" + - "kms:Get*" + - "kms:Delete*" + - "kms:TagResource" + - "kms:UntagResource" + - "kms:ScheduleKeyDeletion" + - "kms:CancelKeyDeletion" + Resource: "*" + - Sid: Allow use of the key + Effect: Allow + Principal: + AWS: !Ref UserARN + Action: + - "kms:Encrypt" + - "kms:Decrypt" + - "kms:ReEncrypt*" + - "kms:GenerateDataKey*" + - "kms:DescribeKey" + Resource: "*" + Tags: + - Key: Name + Value: DiskEncryptKey diff --git a/Documents/Automation/EncryptRootVolume/Tests/test_document.py b/Documents/Automation/EncryptRootVolume/Tests/test_document.py new file mode 100644 index 0000000..f72bac5 --- /dev/null +++ b/Documents/Automation/EncryptRootVolume/Tests/test_document.py @@ -0,0 +1,170 @@ +# +# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the "Software"), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +#!/usr/bin/env python + +import os +import sys +import logging +import unittest + +import ConfigParser +import boto3 +import json + +import time + +DOC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +REPOROOT = os.path.dirname(DOC_DIR) + +# Import shared testing code +sys.path.append( + os.path.join( + REPOROOT, + 'Testing' + ) +) +sys.path.append(os.path.join( + DOC_DIR, "Documents/Lambdas" +)) +sys.path.append( + os.path.abspath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "lib/" + )) +) +import ssm_testing # noqa pylint: disable=import-error,wrong-import-position + +CONFIG = ConfigParser.ConfigParser() +CONFIG.readfp(open(os.path.join(REPOROOT, 'Testing', 'defaults.cfg'))) +CONFIG.read([os.path.join(REPOROOT, 'Testing', 'local.cfg')]) + +REGION = CONFIG.get('general', 'region') +PREFIX = CONFIG.get('general', 'resource_prefix') + +WINDOWS_AMI_ID = CONFIG.get('windows', 'windows2016.{}'.format(REGION)) +INSTANCE_TYPE = CONFIG.get('windows', 'instance_type') +LINUX_AMI_ID = CONFIG.get('linux', 'ami') +LINUX_INSTANCE_TYPE = CONFIG.get('linux', 'instance_type') + +SSM_DOC_NAME = PREFIX + 'encryptrootvolume' +CFN_STACK_NAME = PREFIX + 'encryptrootvolume' +TEST_CFN_STACK_NAME = PREFIX + 'encryptrootvolume' + +logging.basicConfig(level=CONFIG.get('general', 'log_level').upper()) +LOGGER = logging.getLogger(__name__) +logging.getLogger('botocore').setLevel(level=logging.WARNING) + +boto3.setup_default_session(region_name=REGION) + +iam_client = boto3.client('iam') +s3_client = boto3.client('s3') +sns_client = boto3.client('sns') +sts_client = boto3.client('sts') + + +def verify_role_created(role_arn): + LOGGER.info("Verifying that role exists: " + role_arn) + # For whatever reason assuming a role that got created too fast fails, so we just wait until we can. + retry_count = 12 + while True: + try: + sts_client.assume_role(RoleArn=role_arn, RoleSessionName="checking_assume") + break + except Exception as e: + retry_count -= 1 + if retry_count == 0: + raise e + + LOGGER.info("Unable to assume role... trying again in 5 sec") + time.sleep(5) + + +class DocumentTest(unittest.TestCase): + def test_update_document(self): + cfn_client = boto3.client('cloudformation', region_name=REGION) + ssm_client = boto3.client('ssm', region_name=REGION) + + ssm_doc = ssm_testing.SSMTester( + ssm_client=ssm_client, + doc_filename=os.path.join(DOC_DIR, + 'Documents/npark-encryptrootvolume.json'), + doc_name=SSM_DOC_NAME, + doc_type='Automation' + ) + + test_cf_stack = ssm_testing.CFNTester( + cfn_client=cfn_client, + template_filename=os.path.abspath(os.path.join( + DOC_DIR, + "Tests/CloudFormationTemplates/TestTemplate.yml")), + stack_name=TEST_CFN_STACK_NAME + ) + + LOGGER.info('Creating Test Stack') + LOGGER.info('AMI:' + LINUX_AMI_ID) + LOGGER.info('Instance Type:' + LINUX_INSTANCE_TYPE) + test_cf_stack.create_stack([ + { + 'ParameterKey': 'AMI', + 'ParameterValue': LINUX_AMI_ID + }, + { + 'ParameterKey': 'INSTANCETYPE', + 'ParameterValue': LINUX_INSTANCE_TYPE + }, + { + 'ParameterKey': 'UserARN', + 'ParameterValue': sts_client.get_caller_identity().get('Arn') + } + ]) + LOGGER.info('Test Stack has been created') + + # Verify role exists + role_arn = test_cf_stack.stack_outputs['AutomationAssumeRoleARN'] + verify_role_created(role_arn) + try: + LOGGER.info("Creating automation document") + self.assertEqual(ssm_doc.create_document(), 'Active') + + instance_id = test_cf_stack.stack_outputs['InstanceId'] + kms_key_id = test_cf_stack.stack_outputs['KmsKeyId'] + + execution = ssm_doc.execute_automation( + params={'instanceId': [instance_id], + 'region': [REGION], + 'KmsKeyId': [kms_key_id], + 'devicename': ['/dev/xvda'], + 'AutomationAssumeRole': [role_arn]}) + self.assertEqual(ssm_doc.automation_execution_status(ssm_client, execution, False), 'Success') + + LOGGER.info('Encrypting root volume process has been initiated') + + response=ssm_doc.automation_execution_status(ssm_client, execution) + if response == 'Success': + LOGGER.info("All Tests Successful, will clean up now") + + finally: + try: + ssm_doc.destroy() + except Exception: + pass + + test_cf_stack.delete_stack() + + +if __name__ == '__main__': + unittest.main() From 9899500061fa60edf5a1c2f87b37e91f1674e25a Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Sun, 17 Mar 2019 01:22:36 -0400 Subject: [PATCH 02/10] expanded DescribeInstance step's Output; Changed the way I determine the Root Volume of an ec2 instance; Preserving DeleteOnTerminate setting --- .../Documents/npark-encryptrootvolume.json | 130 ++++++++++++------ 1 file changed, 90 insertions(+), 40 deletions(-) diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json index 4d202ae..02d5134 100644 --- a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json +++ b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json @@ -3,22 +3,14 @@ "description": "Encrypt Root Volume Automation Document", "assumeRole": "{{ AutomationAssumeRole }}", "parameters": { - "instanceId": { + "InstanceId": { "description": "(Required) Instance ID of the ec2 instance whose root volume needs to be encrypted", "type": "String" }, - "region": { - "description": "(Required) Region in which the ec2 instance belong", - "type": "String" - }, "KmsKeyId": { "description": "(Required) Customer KMS key to use during the encryption", "type": "String" }, - "devicename": { - "description": "(Optional) Device name of the root volume. Defaults to /dev/sda1", - "type": "String" - }, "AutomationAssumeRole": { "type": "String", "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", @@ -32,48 +24,72 @@ "Service": "ec2", "Api": "DescribeInstances", "InstanceIds": [ - "{{instanceId}}" + "{{InstanceId}}" ] }, "outputs": [ { - "Name": "EBSVolumeID", - "Selector": "$.Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId", + "Name": "AvailabilityZone", + "Selector": "$.Reservations[0].Instances[0].Placement.AvailabilityZone", "Type": "String" - } - ], - "name": "extractEBSvolumeID", - "action": "aws:executeAwsApi", - "timeoutSeconds": 30, - "onFailure": "Abort" - }, - { - "maxAttempts": 1, - "inputs": { - "Service": "ec2", - "Api": "DescribeInstances", - "InstanceIds": [ - "{{instanceId}}" - ] - }, - "outputs": [ + }, { - "Name": "PlacementAvailabilityZone", - "Selector": "$.Reservations[0].Instances[0].Placement.AvailabilityZone", + "Name": "RootDeviceName", + "Selector": "$.Reservations[0].Instances[0].RootDeviceName", "Type": "String" } ], - "name": "extractAvailabilityZone", + "name": "describeInstance", "action": "aws:executeAwsApi", "timeoutSeconds": 30, "onFailure": "Abort" }, + + { + "name": "describeInstanceRootVolume", + "action": "aws:executeAwsApi", + "onFailure": "Abort", + "timeoutSeconds": 60, + "maxAttempts": 10, + "inputs": { + "Service": "ec2", + "Api": "DescribeVolumes", + "Filters": [ + { + "Name": "attachment.instance-id", + "Values": [ + "{{ InstanceId }}" + ] + }, + { + "Name": "attachment.device", + "Values": [ + "{{ describeInstance.RootDeviceName }}" + ] + } + ] + }, + "outputs": [ + { + "Name": "RootDeviceVolumeId", + "Selector": "$.Volumes[0].Attachments[0].VolumeId", + "Type": "String" + }, + { + "Name": "RootDeviceDeleteOnTermination", + "Selector": "$.Volumes[0].Attachments[0].DeleteOnTermination", + "Type": "Boolean" + } + ], + "isCritical": true + }, + { "maxAttempts": 3, "inputs": { "DocumentName": "AWS-CreateSnapshot", "RuntimeParameters": { - "VolumeId": "{{extractEBSvolumeID.EBSVolumeID}}" + "VolumeId": "{{describeInstanceRootVolume.RootDeviceVolumeId}}" } }, "name": "CreateSnapshot", @@ -106,10 +122,10 @@ "Service": "ec2", "Api": "CopySnapshot", "SourceSnapshotId": "{{extractSnapshotID.SNAPSHOTID}}", - "SourceRegion": "{{region}}", + "SourceRegion": "{{global:REGION}}", "Encrypted": true, "KmsKeyId": "{{KmsKeyId}}", - "DestinationRegion": "{{region}}" + "DestinationRegion": "{{global:REGION}}" }, "outputs": [ { @@ -135,7 +151,7 @@ "inputs": { "Service": "ec2", "Api": "CreateVolume", - "AvailabilityZone": "{{extractAvailabilityZone.PlacementAvailabilityZone}}", + "AvailabilityZone": "{{describeInstance.AvailabilityZone}}", "Encrypted": true, "KmsKeyId": "{{KmsKeyId}}", "SnapshotId": "{{CopySnapshot.EncryptedSnapshotID}}", @@ -158,7 +174,7 @@ "inputs": { "DocumentName": "AWS-StopEC2Instance", "RuntimeParameters": { - "InstanceId": "{{instanceId}}" + "InstanceId": "{{InstanceId}}" } }, "name": "StopInstance", @@ -171,7 +187,7 @@ "inputs": { "DocumentName": "AWS-DetachEBSVolume", "RuntimeParameters": { - "VolumeId": "{{extractEBSvolumeID.EBSVolumeID}}" + "VolumeId": "{{describeInstanceRootVolume.RootDeviceVolumeId}}" } }, "name": "DetachEBSVolume", @@ -184,8 +200,8 @@ "inputs": { "DocumentName": "AWS-AttachEBSVolume", "RuntimeParameters": { - "Device": "{{devicename}}", - "InstanceId": "{{instanceId}}", + "Device": "{{describeInstance.RootDeviceName}}", + "InstanceId": "{{InstanceId}}", "VolumeId": "{{CreateVolume.NewRootVolumeID}}" } }, @@ -193,6 +209,40 @@ "action": "aws:executeAutomation", "timeoutSeconds": 180, "onFailure": "Abort" + }, + { + "name": "ApplyDeleteOnTerminationValue", + "action": "aws:executeAwsApi", + "onFailure": "Abort", + "timeoutSeconds": 60, + "maxAttempts": 10, + "inputs": { + "Service": "ec2", + "Api": "ModifyInstanceAttribute", + "InstanceId": "{{InstanceId}}", + "BlockDeviceMappings": [ + { + "DeviceName": "{{describeInstance.RootDeviceName}}", + "Ebs": { + "DeleteOnTermination": "{{describeInstanceRootVolume.RootDeviceDeleteOnTermination}}" + } + } + ] + }, + "isCritical": true + }, + { + "maxAttempts": 1, + "inputs": { + "DocumentName": "AWS-StartEC2Instance", + "RuntimeParameters": { + "InstanceId": "{{InstanceId}}" + } + }, + "name": "StartInstance", + "action": "aws:executeAutomation", + "timeoutSeconds": 300, + "onFailure": "Abort" } ] } From 944cb5009cf01f386ba142282c00e7d913f05edb Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Sun, 17 Mar 2019 01:25:10 -0400 Subject: [PATCH 03/10] asserting that root volume encryption has been completed --- Documents/Automation/EncryptRootVolume/Tests/test_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documents/Automation/EncryptRootVolume/Tests/test_document.py b/Documents/Automation/EncryptRootVolume/Tests/test_document.py index f72bac5..20c14bc 100644 --- a/Documents/Automation/EncryptRootVolume/Tests/test_document.py +++ b/Documents/Automation/EncryptRootVolume/Tests/test_document.py @@ -151,7 +151,7 @@ def test_update_document(self): 'AutomationAssumeRole': [role_arn]}) self.assertEqual(ssm_doc.automation_execution_status(ssm_client, execution, False), 'Success') - LOGGER.info('Encrypting root volume process has been initiated') + LOGGER.info('Encryption of root volume has been completed') response=ssm_doc.automation_execution_status(ssm_client, execution) if response == 'Success': From 50cc4701474cc2ef2b783aa78d6ec9d130de3ee0 Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Wed, 20 Mar 2019 03:38:07 -0400 Subject: [PATCH 04/10] Changed sleep to wait. Restoring original EC2 state rather than arbitrarily starting the instance. Adding support for preserving DeleteOnTerminate. Cosmetic changes for camelCase names and variable access. --- .../EncryptRootVolume/Design/Design.md | 6 +- .../Documents/npark-encryptrootvolume.json | 144 ++++++++++++------ .../EncryptRootVolume/Tests/test_document.py | 39 ++++- 3 files changed, 131 insertions(+), 58 deletions(-) diff --git a/Documents/Automation/EncryptRootVolume/Design/Design.md b/Documents/Automation/EncryptRootVolume/Design/Design.md index e4baec4..4ac9ccf 100644 --- a/Documents/Automation/EncryptRootVolume/Design/Design.md +++ b/Documents/Automation/EncryptRootVolume/Design/Design.md @@ -22,7 +22,5 @@ Document Steps: Python script will: # 1. Create a test stack with an instance, a volume and a KMS Key (Customer managed) # 2. Execute automation document to replace the root volume with the encrypted one (after a copy operation of the root volume snapshot) -# 3. Ensure the automation has executed successfully -# 4. Detach the old volume -# 5. Attach the new (and encrypted) volume -# 5. Clean up test stack +# 3. Ensure the Automation has executed successfull +# 4. Clean up test stack diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json index 02d5134..ffceae9 100644 --- a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json +++ b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json @@ -1,17 +1,17 @@ { "schemaVersion": "0.3", "description": "Encrypt Root Volume Automation Document", - "assumeRole": "{{ AutomationAssumeRole }}", + "assumeRole": "{{automationAssumeRole}}", "parameters": { - "InstanceId": { + "instanceId": { "description": "(Required) Instance ID of the ec2 instance whose root volume needs to be encrypted", "type": "String" }, - "KmsKeyId": { + "kmsKeyId": { "description": "(Required) Customer KMS key to use during the encryption", "type": "String" }, - "AutomationAssumeRole": { + "automationAssumeRole": { "type": "String", "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", "default": "" @@ -24,19 +24,24 @@ "Service": "ec2", "Api": "DescribeInstances", "InstanceIds": [ - "{{InstanceId}}" + "{{instanceId}}" ] }, "outputs": [ { - "Name": "AvailabilityZone", + "Name": "availabilityZone", "Selector": "$.Reservations[0].Instances[0].Placement.AvailabilityZone", "Type": "String" }, { - "Name": "RootDeviceName", + "Name": "rootDeviceName", "Selector": "$.Reservations[0].Instances[0].RootDeviceName", "Type": "String" + }, + { + "Name": "instanceState", + "Selector": "$.Reservations[0].Instances[0].State.Name", + "Type": "String" } ], "name": "describeInstance", @@ -58,23 +63,28 @@ { "Name": "attachment.instance-id", "Values": [ - "{{ InstanceId }}" + "{{instanceId}}" ] }, { "Name": "attachment.device", "Values": [ - "{{ describeInstance.RootDeviceName }}" + "{{describeInstance.rootDeviceName}}" ] } ] }, "outputs": [ { - "Name": "RootDeviceVolumeId", + "Name": "rootDeviceVolumeId", "Selector": "$.Volumes[0].Attachments[0].VolumeId", "Type": "String" }, + { + "Name": "rootDeviceVolumeType", + "Selector": "$.Volumes[0].VolumeType", + "Type": "String" + }, { "Name": "RootDeviceDeleteOnTermination", "Selector": "$.Volumes[0].Attachments[0].DeleteOnTermination", @@ -89,10 +99,10 @@ "inputs": { "DocumentName": "AWS-CreateSnapshot", "RuntimeParameters": { - "VolumeId": "{{describeInstanceRootVolume.RootDeviceVolumeId}}" + "VolumeId": "{{describeInstanceRootVolume.rootDeviceVolumeId}}" } }, - "name": "CreateSnapshot", + "name": "createSnapshot", "action": "aws:executeAutomation", "timeoutSeconds": 3600, "onFailure": "Abort" @@ -102,16 +112,16 @@ "inputs": { "Service": "ec2", "Api": "DescribeSnapshots", - "SnapshotIds": "{{CreateSnapshot.Output}}" + "SnapshotIds": "{{createSnapshot.Output}}" }, "outputs": [ { - "Name": "SNAPSHOTID", + "Name": "SnapshotId", "Selector": "$.Snapshots[0].SnapshotId", "Type": "String" } ], - "name": "extractSnapshotID", + "name": "extractSnapshotId", "action": "aws:executeAwsApi", "timeoutSeconds": 30, "onFailure": "Abort" @@ -121,41 +131,50 @@ "inputs": { "Service": "ec2", "Api": "CopySnapshot", - "SourceSnapshotId": "{{extractSnapshotID.SNAPSHOTID}}", + "SourceSnapshotId": "{{extractSnapshotId.SnapshotId}}", "SourceRegion": "{{global:REGION}}", "Encrypted": true, - "KmsKeyId": "{{KmsKeyId}}", + "KmsKeyId": "{{kmsKeyId}}", "DestinationRegion": "{{global:REGION}}" }, "outputs": [ { - "Name": "EncryptedSnapshotID", + "Name": "encryptedSnapshotId", "Selector": "$.SnapshotId", "Type": "String" } ], - "name": "CopySnapshot", + "name": "copyAndEncryptSnapshot", "action": "aws:executeAwsApi", "timeoutSeconds": 3600, "onFailure": "Abort" }, { + "name": "waitForSnapshot", + "action": "aws:waitForAwsResourceProperty", + "timeoutSeconds": "180", "inputs": { - "Duration": "PT2M" - }, - "name": "sleep1", - "action": "aws:sleep" + "Service":"ec2", + "Api":"DescribeSnapshots", + "SnapshotIds": [ + "{{copyAndEncryptSnapshot.encryptedSnapshotId}}" + ], + "PropertySelector": "$.Snapshots[0].State", + "DesiredValues": [ + "completed" + ] + } }, { "maxAttempts": 1, "inputs": { "Service": "ec2", "Api": "CreateVolume", - "AvailabilityZone": "{{describeInstance.AvailabilityZone}}", + "AvailabilityZone": "{{describeInstance.availabilityZone}}", "Encrypted": true, - "KmsKeyId": "{{KmsKeyId}}", - "SnapshotId": "{{CopySnapshot.EncryptedSnapshotID}}", - "VolumeType": "gp2" + "KmsKeyId": "{{kmsKeyId}}", + "SnapshotId": "{{copyAndEncryptSnapshot.encryptedSnapshotId}}", + "VolumeType": "{{describeInstanceRootVolume.rootDeviceVolumeType}}" }, "outputs": [ { @@ -164,20 +183,20 @@ "Type": "String" } ], - "name": "CreateVolume", + "name": "createVolume", "action": "aws:executeAwsApi", "timeoutSeconds": 30, - "onFailure": "Abort" + "onFailure": "step:handleFailureAndQuit" }, { "maxAttempts": 1, "inputs": { "DocumentName": "AWS-StopEC2Instance", "RuntimeParameters": { - "InstanceId": "{{InstanceId}}" + "InstanceId": "{{instanceId}}" } }, - "name": "StopInstance", + "name": "stopInstance", "action": "aws:executeAutomation", "timeoutSeconds": 300, "onFailure": "Abort" @@ -187,10 +206,10 @@ "inputs": { "DocumentName": "AWS-DetachEBSVolume", "RuntimeParameters": { - "VolumeId": "{{describeInstanceRootVolume.RootDeviceVolumeId}}" + "VolumeId": "{{describeInstanceRootVolume.rootDeviceVolumeId}}" } }, - "name": "DetachEBSVolume", + "name": "detachEBSVolume", "action": "aws:executeAutomation", "timeoutSeconds": 300, "onFailure": "Abort" @@ -200,18 +219,18 @@ "inputs": { "DocumentName": "AWS-AttachEBSVolume", "RuntimeParameters": { - "Device": "{{describeInstance.RootDeviceName}}", - "InstanceId": "{{InstanceId}}", - "VolumeId": "{{CreateVolume.NewRootVolumeID}}" + "Device": "{{describeInstance.rootDeviceName}}", + "InstanceId": "{{instanceId}}", + "VolumeId": "{{createVolume.NewRootVolumeID}}" } }, - "name": "AttachNewEBSVolume", + "name": "attachNewEBSVolume", "action": "aws:executeAutomation", "timeoutSeconds": 180, "onFailure": "Abort" }, { - "name": "ApplyDeleteOnTerminationValue", + "name": "applyDeleteOnTerminationValue", "action": "aws:executeAwsApi", "onFailure": "Abort", "timeoutSeconds": 60, @@ -219,10 +238,10 @@ "inputs": { "Service": "ec2", "Api": "ModifyInstanceAttribute", - "InstanceId": "{{InstanceId}}", + "InstanceId": "{{instanceId}}", "BlockDeviceMappings": [ { - "DeviceName": "{{describeInstance.RootDeviceName}}", + "DeviceName": "{{describeInstance.rootDeviceName}}", "Ebs": { "DeleteOnTermination": "{{describeInstanceRootVolume.RootDeviceDeleteOnTermination}}" } @@ -231,18 +250,45 @@ }, "isCritical": true }, + { - "maxAttempts": 1, + "name": "restoreInstanceInitialState", + "action": "aws:changeInstanceState", + "onFailure": "Abort", "inputs": { - "DocumentName": "AWS-StartEC2Instance", - "RuntimeParameters": { - "InstanceId": "{{InstanceId}}" - } + "InstanceIds": [ + "{{instanceId}}" + ], + "DesiredState": "{{describeInstance.instanceState}}" }, - "name": "StartInstance", - "action": "aws:executeAutomation", - "timeoutSeconds": 300, - "onFailure": "Abort" + "isCritical": true, + "isEnd": true + }, + + { + "name": "handleFailureAndQuit", + "action": "aws:executeAwsApi", + "onFailure": "Abort", + "timeoutSeconds": 300, + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "DeleteSnapshot", + "SnapshotId": "{{copyAndEncryptSnapshot.encryptedSnapshotId}}" + } + }, + { + "name": "handleFailureAndQuit2", + "action": "aws:executeAwsApi", + "onFailure": "Abort", + "timeoutSeconds": 300, + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "DeleteSnapshot", + "SnapshotId": "{{extractSnapshotId.SnapshotId}}" + }, + "isEnd":true } ] } diff --git a/Documents/Automation/EncryptRootVolume/Tests/test_document.py b/Documents/Automation/EncryptRootVolume/Tests/test_document.py index 20c14bc..3b333fa 100644 --- a/Documents/Automation/EncryptRootVolume/Tests/test_document.py +++ b/Documents/Automation/EncryptRootVolume/Tests/test_document.py @@ -74,6 +74,7 @@ s3_client = boto3.client('s3') sns_client = boto3.client('sns') sts_client = boto3.client('sts') +ec2_client = boto3.client('ec2') def verify_role_created(role_arn): @@ -145,17 +146,45 @@ def test_update_document(self): execution = ssm_doc.execute_automation( params={'instanceId': [instance_id], - 'region': [REGION], - 'KmsKeyId': [kms_key_id], - 'devicename': ['/dev/xvda'], - 'AutomationAssumeRole': [role_arn]}) + 'kmsKeyId': [kms_key_id], + 'automationAssumeRole': [role_arn]}) self.assertEqual(ssm_doc.automation_execution_status(ssm_client, execution, False), 'Success') LOGGER.info('Encryption of root volume has been completed') response=ssm_doc.automation_execution_status(ssm_client, execution) if response == 'Success': - LOGGER.info("All Tests Successful, will clean up now") + response = ec2_client.describe_instances( + InstanceIds=[ + instance_id + ], + DryRun=False + ) + rootdevicename = response['Reservations'][0]['Instances'][0]['RootDeviceName'] + + response = ec2_client.describe_volumes( + Filters=[ + { + 'Name': 'attachment.instance-id', + 'Values': [ + instance_id + ], + }, + { + 'Name': 'attachment.device', + 'Values': [ + rootdevicename + ], + }, + ], + DryRun=False + ) + is_encrypted=response['Volumes'][0]['Encrypted'] + self.assertEqual(is_encrypted, True) + if is_encrypted: + LOGGER.info("All Tests Successful, will clean up now") + else: + LOGGER.info("FAIL: root volume is NOT encrypted, will clean up now") finally: try: From c62c5c4aacc33705e3918314cd7bf42776c1ca3f Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Thu, 21 Mar 2019 14:05:42 -0400 Subject: [PATCH 05/10] Created additional parameter options for snapshot waits; shuffled and massaged each steps so that the 'name' attribute is the first element of the step; added clean up logic to pretty much all steps --- .../Documents/npark-encryptrootvolume.json | 319 +++++++++++------- 1 file changed, 192 insertions(+), 127 deletions(-) diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json index ffceae9..d4d735c 100644 --- a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json +++ b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json @@ -15,10 +15,24 @@ "type": "String", "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", "default": "" + }, + "maxWaitForCreateSnapshot": { + "type": "Integer", + "description": "(Optional) maximum wait time for creating snapshot of the root volume (prior to encryption). Default maximum wait time is 30 minutes (1800 seconds)", + "default": 1800 + }, + "maxWaitForSnapshotWithEncryption": { + "type": "Integer", + "description": "(Optional) maximum wait time for copying snapshot (with encryption) of the root volume. Default maximum wait time is 30 minutes (1800 seconds)", + "default": 1800 } }, "mainSteps": [ { + "name": "describeInstance", + "action": "aws:executeAwsApi", + "timeoutSeconds": 30, + "onFailure": "Abort", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -43,71 +57,72 @@ "Selector": "$.Reservations[0].Instances[0].State.Name", "Type": "String" } - ], - "name": "describeInstance", - "action": "aws:executeAwsApi", - "timeoutSeconds": 30, - "onFailure": "Abort" + ] }, { - "name": "describeInstanceRootVolume", - "action": "aws:executeAwsApi", - "onFailure": "Abort", - "timeoutSeconds": 60, - "maxAttempts": 10, - "inputs": { - "Service": "ec2", - "Api": "DescribeVolumes", - "Filters": [ - { - "Name": "attachment.instance-id", - "Values": [ - "{{instanceId}}" - ] - }, - { - "Name": "attachment.device", - "Values": [ - "{{describeInstance.rootDeviceName}}" - ] - } - ] - }, - "outputs": [ - { - "Name": "rootDeviceVolumeId", - "Selector": "$.Volumes[0].Attachments[0].VolumeId", - "Type": "String" - }, - { - "Name": "rootDeviceVolumeType", - "Selector": "$.Volumes[0].VolumeType", - "Type": "String" - }, - { - "Name": "RootDeviceDeleteOnTermination", - "Selector": "$.Volumes[0].Attachments[0].DeleteOnTermination", - "Type": "Boolean" - } - ], - "isCritical": true + "name": "describeInstanceRootVolume", + "action": "aws:executeAwsApi", + "onFailure": "Abort", + "timeoutSeconds": 60, + "maxAttempts": 10, + "isCritical": true, + "inputs": { + "Service": "ec2", + "Api": "DescribeVolumes", + "Filters": [ + { + "Name": "attachment.instance-id", + "Values": [ + "{{instanceId}}" + ] + }, + { + "Name": "attachment.device", + "Values": [ + "{{describeInstance.rootDeviceName}}" + ] + } + ] + }, + "outputs": [ + { + "Name": "rootDeviceVolumeId", + "Selector": "$.Volumes[0].Attachments[0].VolumeId", + "Type": "String" + }, + { + "Name": "rootDeviceVolumeType", + "Selector": "$.Volumes[0].VolumeType", + "Type": "String" + }, + { + "Name": "RootDeviceDeleteOnTermination", + "Selector": "$.Volumes[0].Attachments[0].DeleteOnTermination", + "Type": "Boolean" + } + ] }, { + "name": "createSnapshot", + "action": "aws:executeAutomation", + "timeoutSeconds": 1800, + "onFailure": "Abort", "maxAttempts": 3, "inputs": { "DocumentName": "AWS-CreateSnapshot", "RuntimeParameters": { "VolumeId": "{{describeInstanceRootVolume.rootDeviceVolumeId}}" } - }, - "name": "createSnapshot", - "action": "aws:executeAutomation", - "timeoutSeconds": 3600, - "onFailure": "Abort" + } }, + { + "name": "extractSnapshotId", + "action": "aws:executeAwsApi", + "timeoutSeconds": 30, + "onFailure": "Abort", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -120,13 +135,14 @@ "Selector": "$.Snapshots[0].SnapshotId", "Type": "String" } - ], - "name": "extractSnapshotId", - "action": "aws:executeAwsApi", - "timeoutSeconds": 30, - "onFailure": "Abort" + ] }, + { + "name": "copyAndEncryptSnapshot", + "action": "aws:executeAwsApi", + "timeoutSeconds": 3600, + "onFailure": "step:deleteRootVolumeSnapshot", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -143,19 +159,17 @@ "Selector": "$.SnapshotId", "Type": "String" } - ], - "name": "copyAndEncryptSnapshot", - "action": "aws:executeAwsApi", - "timeoutSeconds": 3600, - "onFailure": "Abort" + ] }, + { "name": "waitForSnapshot", "action": "aws:waitForAwsResourceProperty", - "timeoutSeconds": "180", + "timeoutSeconds": 1800, + "onFailure": "step:deleteRootVolumeSnapshot", "inputs": { - "Service":"ec2", - "Api":"DescribeSnapshots", + "Service": "ec2", + "Api": "DescribeSnapshots", "SnapshotIds": [ "{{copyAndEncryptSnapshot.encryptedSnapshotId}}" ], @@ -165,7 +179,12 @@ ] } }, + { + "name": "createVolume", + "action": "aws:executeAwsApi", + "timeoutSeconds": 30, + "onFailure": "step:deleteEncryptedRootVolumeSnapshot", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -182,39 +201,42 @@ "Selector": "$.VolumeId", "Type": "String" } - ], - "name": "createVolume", - "action": "aws:executeAwsApi", - "timeoutSeconds": 30, - "onFailure": "step:handleFailureAndQuit" + ] }, + { + "name": "stopInstance", + "action": "aws:executeAutomation", + "timeoutSeconds": 300, + "onFailure": "step:deleteEncryptedRootVolumeSnapshot", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-StopEC2Instance", "RuntimeParameters": { "InstanceId": "{{instanceId}}" } - }, - "name": "stopInstance", - "action": "aws:executeAutomation", - "timeoutSeconds": 300, - "onFailure": "Abort" + } }, + { + "name": "detachEBSVolume", + "action": "aws:executeAutomation", + "timeoutSeconds": 300, + "onFailure": "step:deleteEncryptedRootVolumeSnapshot", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-DetachEBSVolume", "RuntimeParameters": { "VolumeId": "{{describeInstanceRootVolume.rootDeviceVolumeId}}" } - }, - "name": "detachEBSVolume", - "action": "aws:executeAutomation", - "timeoutSeconds": 300, - "onFailure": "Abort" + } }, + { + "name": "attachNewEBSVolume", + "action": "aws:executeAutomation", + "timeoutSeconds": 180, + "onFailure": "step:attachOriginalVolume", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-AttachEBSVolume", @@ -223,72 +245,115 @@ "InstanceId": "{{instanceId}}", "VolumeId": "{{createVolume.NewRootVolumeID}}" } - }, - "name": "attachNewEBSVolume", - "action": "aws:executeAutomation", - "timeoutSeconds": 180, - "onFailure": "Abort" + } }, + { - "name": "applyDeleteOnTerminationValue", - "action": "aws:executeAwsApi", - "onFailure": "Abort", - "timeoutSeconds": 60, - "maxAttempts": 10, - "inputs": { - "Service": "ec2", - "Api": "ModifyInstanceAttribute", - "InstanceId": "{{instanceId}}", - "BlockDeviceMappings": [ - { - "DeviceName": "{{describeInstance.rootDeviceName}}", - "Ebs": { - "DeleteOnTermination": "{{describeInstanceRootVolume.RootDeviceDeleteOnTermination}}" - } - } - ] - }, - "isCritical": true + "name": "applyDeleteOnTerminationValue", + "action": "aws:executeAwsApi", + "onFailure": "step:detachNewVolume", + "timeoutSeconds": 60, + "maxAttempts": 10, + "isCritical": true, + "nextStep": "deleteRootVolumeSnapshot", + "inputs": { + "Service": "ec2", + "Api": "ModifyInstanceAttribute", + "InstanceId": "{{instanceId}}", + "BlockDeviceMappings": [ + { + "DeviceName": "{{describeInstance.rootDeviceName}}", + "Ebs": { + "DeleteOnTermination": "{{describeInstanceRootVolume.RootDeviceDeleteOnTermination}}" + } + } + ] + } }, { "name": "restoreInstanceInitialState", "action": "aws:changeInstanceState", "onFailure": "Abort", + "isCritical": true, + "isEnd": true, "inputs": { "InstanceIds": [ "{{instanceId}}" ], "DesiredState": "{{describeInstance.instanceState}}" - }, - "isCritical": true, - "isEnd": true + } + }, + + { + "name": "detachNewVolume", + "action": "aws:executeAutomation", + "timeoutSeconds": 300, + "onFailure": "Abort", + "maxAttempts": 1, + "inputs": { + "DocumentName": "AWS-DetachEBSVolume", + "RuntimeParameters": { + "VolumeId": "{{createVolume.NewRootVolumeID}}" + } + } + }, + + { + "name": "attachOriginalVolume", + "action": "aws:executeAutomation", + "timeoutSeconds": 180, + "onFailure": "Abort", + "maxAttempts": 1, + "inputs": { + "DocumentName": "AWS-AttachEBSVolume", + "RuntimeParameters": { + "Device": "{{describeInstance.rootDeviceName}}", + "InstanceId": "{{instanceId}}", + "VolumeId": "{{describeInstanceRootVolume.rootDeviceVolumeId}}" + } + } + }, + + { + "name": "deleteNewEncryptedVolume", + "action": "aws:executeAwsApi", + "timeoutSeconds": 300, + "onFailure": "Abort", + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "DeleteVolume", + "VolumeId": "{{createVolume.NewRootVolumeID}}" + } }, { - "name": "handleFailureAndQuit", - "action": "aws:executeAwsApi", - "onFailure": "Abort", - "timeoutSeconds": 300, - "maxAttempts": 1, - "inputs": { - "Service": "ec2", - "Api": "DeleteSnapshot", - "SnapshotId": "{{copyAndEncryptSnapshot.encryptedSnapshotId}}" - } + "name": "deleteEncryptedRootVolumeSnapshot", + "action": "aws:executeAwsApi", + "onFailure": "Abort", + "timeoutSeconds": 300, + "maxAttempts": 1, + "inputs": { + "Service": "ec2", + "Api": "DeleteSnapshot", + "SnapshotId": "{{copyAndEncryptSnapshot.encryptedSnapshotId}}" + } }, + { - "name": "handleFailureAndQuit2", - "action": "aws:executeAwsApi", - "onFailure": "Abort", - "timeoutSeconds": 300, - "maxAttempts": 1, - "inputs": { - "Service": "ec2", - "Api": "DeleteSnapshot", - "SnapshotId": "{{extractSnapshotId.SnapshotId}}" - }, - "isEnd":true + "name": "deleteRootVolumeSnapshot", + "action": "aws:executeAwsApi", + "onFailure": "Abort", + "timeoutSeconds": 300, + "maxAttempts": 1, + "nextStep": "restoreInstanceInitialState", + "inputs": { + "Service": "ec2", + "Api": "DeleteSnapshot", + "SnapshotId": "{{extractSnapshotId.SnapshotId}}" + } } + ] } From 4b5a558c98453ff28b159073d142ba8939616b12 Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Thu, 21 Mar 2019 17:06:46 -0400 Subject: [PATCH 06/10] changed to more descriptive parameter name; added outputs section to display old and the new root volume IDs --- .../Documents/npark-encryptrootvolume.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json index d4d735c..2c41c70 100644 --- a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json +++ b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json @@ -16,14 +16,14 @@ "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", "default": "" }, - "maxWaitForCreateSnapshot": { + "maxWaitInSecondsForCreateSnapshot": { "type": "Integer", - "description": "(Optional) maximum wait time for creating snapshot of the root volume (prior to encryption). Default maximum wait time is 30 minutes (1800 seconds)", + "description": "(Optional) Maximum wait time for creating snapshot of the root volume (prior to encryption). Default maximum wait time is 30 minutes (1800 seconds)", "default": 1800 }, - "maxWaitForSnapshotWithEncryption": { + "maxWaitInSecondsForSnapshotWithEncryption": { "type": "Integer", - "description": "(Optional) maximum wait time for copying snapshot (with encryption) of the root volume. Default maximum wait time is 30 minutes (1800 seconds)", + "description": "(Optional) Maximum wait time for copying snapshot (with encryption) of the root volume. Default maximum wait time is 30 minutes (1800 seconds)", "default": 1800 } }, @@ -355,5 +355,9 @@ } } + ], + "outputs":[ + "describeInstanceRootVolume.rootDeviceVolumeId", + "createVolume.NewRootVolumeID" ] } From 6e321d203187c905f836d2de2410061d16af6b62 Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Thu, 21 Mar 2019 17:10:08 -0400 Subject: [PATCH 07/10] removed max wait for now --- .../Documents/npark-encryptrootvolume.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json index 2c41c70..96e7a0c 100644 --- a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json +++ b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json @@ -15,16 +15,6 @@ "type": "String", "description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf.", "default": "" - }, - "maxWaitInSecondsForCreateSnapshot": { - "type": "Integer", - "description": "(Optional) Maximum wait time for creating snapshot of the root volume (prior to encryption). Default maximum wait time is 30 minutes (1800 seconds)", - "default": 1800 - }, - "maxWaitInSecondsForSnapshotWithEncryption": { - "type": "Integer", - "description": "(Optional) Maximum wait time for copying snapshot (with encryption) of the root volume. Default maximum wait time is 30 minutes (1800 seconds)", - "default": 1800 } }, "mainSteps": [ From 7029545a074a2aefd8adb284760611254c92f3e4 Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Tue, 26 Mar 2019 03:01:20 -0400 Subject: [PATCH 08/10] onFailure changed to 'Continue' in the clean up stages; being more aggressive on clean up efforts; --- .../Documents/npark-encryptrootvolume.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json index 96e7a0c..ca7d6ec 100644 --- a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json +++ b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json @@ -112,7 +112,7 @@ "name": "extractSnapshotId", "action": "aws:executeAwsApi", "timeoutSeconds": 30, - "onFailure": "Abort", + "onFailure": "step:deleteRootVolumeSnapshot", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -156,7 +156,7 @@ "name": "waitForSnapshot", "action": "aws:waitForAwsResourceProperty", "timeoutSeconds": 1800, - "onFailure": "step:deleteRootVolumeSnapshot", + "onFailure": "step:deleteEncryptedRootVolumeSnapshot", "inputs": { "Service": "ec2", "Api": "DescribeSnapshots", @@ -264,7 +264,7 @@ { "name": "restoreInstanceInitialState", "action": "aws:changeInstanceState", - "onFailure": "Abort", + "onFailure": "Continue", "isCritical": true, "isEnd": true, "inputs": { @@ -279,7 +279,7 @@ "name": "detachNewVolume", "action": "aws:executeAutomation", "timeoutSeconds": 300, - "onFailure": "Abort", + "onFailure": "Continue", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-DetachEBSVolume", @@ -293,7 +293,7 @@ "name": "attachOriginalVolume", "action": "aws:executeAutomation", "timeoutSeconds": 180, - "onFailure": "Abort", + "onFailure": "Continue", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-AttachEBSVolume", @@ -309,7 +309,7 @@ "name": "deleteNewEncryptedVolume", "action": "aws:executeAwsApi", "timeoutSeconds": 300, - "onFailure": "Abort", + "onFailure": "Continue", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -321,7 +321,7 @@ { "name": "deleteEncryptedRootVolumeSnapshot", "action": "aws:executeAwsApi", - "onFailure": "Abort", + "onFailure": "Continue", "timeoutSeconds": 300, "maxAttempts": 1, "inputs": { @@ -334,7 +334,7 @@ { "name": "deleteRootVolumeSnapshot", "action": "aws:executeAwsApi", - "onFailure": "Abort", + "onFailure": "Continue", "timeoutSeconds": 300, "maxAttempts": 1, "nextStep": "restoreInstanceInitialState", From 50032bf5962b3094d24b1075b3054bcee3e0808f Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Fri, 5 Apr 2019 14:38:36 -0400 Subject: [PATCH 09/10] clean up process adjustment --- .../Documents/npark-encryptrootvolume.json | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json index ca7d6ec..7db21d8 100644 --- a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json +++ b/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json @@ -23,6 +23,7 @@ "action": "aws:executeAwsApi", "timeoutSeconds": 30, "onFailure": "Abort", + "nextStep": "describeInstanceRootVolume", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -54,6 +55,7 @@ "name": "describeInstanceRootVolume", "action": "aws:executeAwsApi", "onFailure": "Abort", + "nextStep": "createSnapshot", "timeoutSeconds": 60, "maxAttempts": 10, "isCritical": true, @@ -99,6 +101,7 @@ "action": "aws:executeAutomation", "timeoutSeconds": 1800, "onFailure": "Abort", + "nextStep": "extractSnapshotId", "maxAttempts": 3, "inputs": { "DocumentName": "AWS-CreateSnapshot", @@ -113,6 +116,7 @@ "action": "aws:executeAwsApi", "timeoutSeconds": 30, "onFailure": "step:deleteRootVolumeSnapshot", + "nextStep": "copyAndEncryptSnapshot", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -132,7 +136,8 @@ "name": "copyAndEncryptSnapshot", "action": "aws:executeAwsApi", "timeoutSeconds": 3600, - "onFailure": "step:deleteRootVolumeSnapshot", + "onFailure": "step:deleteEncryptedRootVolumeSnapshot", + "nextStep": "waitForSnapshot", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -157,6 +162,7 @@ "action": "aws:waitForAwsResourceProperty", "timeoutSeconds": 1800, "onFailure": "step:deleteEncryptedRootVolumeSnapshot", + "nextStep": "createVolume", "inputs": { "Service": "ec2", "Api": "DescribeSnapshots", @@ -174,7 +180,8 @@ "name": "createVolume", "action": "aws:executeAwsApi", "timeoutSeconds": 30, - "onFailure": "step:deleteEncryptedRootVolumeSnapshot", + "onFailure": "step:deleteNewEncryptedVolume", + "nextStep": "stopInstance", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -198,7 +205,8 @@ "name": "stopInstance", "action": "aws:executeAutomation", "timeoutSeconds": 300, - "onFailure": "step:deleteEncryptedRootVolumeSnapshot", + "onFailure": "step:deleteNewEncryptedVolume", + "nextStep": "detachEBSVolume", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-StopEC2Instance", @@ -212,7 +220,8 @@ "name": "detachEBSVolume", "action": "aws:executeAutomation", "timeoutSeconds": 300, - "onFailure": "step:deleteEncryptedRootVolumeSnapshot", + "onFailure": "step:attachOriginalVolume", + "nextStep": "attachNewEBSVolume", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-DetachEBSVolume", @@ -226,7 +235,8 @@ "name": "attachNewEBSVolume", "action": "aws:executeAutomation", "timeoutSeconds": 180, - "onFailure": "step:attachOriginalVolume", + "onFailure": "step:detachNewVolume", + "nextStep": "applyDeleteOnTerminationValue", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-AttachEBSVolume", @@ -242,10 +252,10 @@ "name": "applyDeleteOnTerminationValue", "action": "aws:executeAwsApi", "onFailure": "step:detachNewVolume", + "nextStep": "restoreInstanceInitialState", "timeoutSeconds": 60, "maxAttempts": 10, "isCritical": true, - "nextStep": "deleteRootVolumeSnapshot", "inputs": { "Service": "ec2", "Api": "ModifyInstanceAttribute", @@ -264,7 +274,7 @@ { "name": "restoreInstanceInitialState", "action": "aws:changeInstanceState", - "onFailure": "Continue", + "onFailure": "Abort", "isCritical": true, "isEnd": true, "inputs": { @@ -280,6 +290,7 @@ "action": "aws:executeAutomation", "timeoutSeconds": 300, "onFailure": "Continue", + "nextStep": "attachOriginalVolume", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-DetachEBSVolume", @@ -294,6 +305,7 @@ "action": "aws:executeAutomation", "timeoutSeconds": 180, "onFailure": "Continue", + "nextStep": "deleteNewEncryptedVolume", "maxAttempts": 1, "inputs": { "DocumentName": "AWS-AttachEBSVolume", @@ -304,12 +316,13 @@ } } }, - + { "name": "deleteNewEncryptedVolume", "action": "aws:executeAwsApi", "timeoutSeconds": 300, "onFailure": "Continue", + "nextStep": "deleteEncryptedRootVolumeSnapshot", "maxAttempts": 1, "inputs": { "Service": "ec2", @@ -322,6 +335,7 @@ "name": "deleteEncryptedRootVolumeSnapshot", "action": "aws:executeAwsApi", "onFailure": "Continue", + "nextStep": "deleteRootVolumeSnapshot", "timeoutSeconds": 300, "maxAttempts": 1, "inputs": { @@ -335,9 +349,9 @@ "name": "deleteRootVolumeSnapshot", "action": "aws:executeAwsApi", "onFailure": "Continue", + "nextStep": "restoreInstanceInitialState", "timeoutSeconds": 300, "maxAttempts": 1, - "nextStep": "restoreInstanceInitialState", "inputs": { "Service": "ec2", "Api": "DeleteSnapshot", From 0bc9f65653a4c7a0b8c94c9c321397ce82f84d01 Mon Sep 17 00:00:00 2001 From: Andrew Park Date: Mon, 8 Apr 2019 20:41:31 -0400 Subject: [PATCH 10/10] renamed npark-encryptrootvolume.json to aws-encryptrootvolume.json --- .../{npark-encryptrootvolume.json => aws-encryptrootvolume.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documents/Automation/EncryptRootVolume/Documents/{npark-encryptrootvolume.json => aws-encryptrootvolume.json} (100%) diff --git a/Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json b/Documents/Automation/EncryptRootVolume/Documents/aws-encryptrootvolume.json similarity index 100% rename from Documents/Automation/EncryptRootVolume/Documents/npark-encryptrootvolume.json rename to Documents/Automation/EncryptRootVolume/Documents/aws-encryptrootvolume.json