######################################################################################### # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # # SPDX-License-Identifier: MIT-0 # # # # 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: AWS CloudFormation template Cloud9 setup to help with MGN pre-reqs setup Parameters: Cloud9InstanceType: Description: Example Cloud9 instance type Type: String Default: t3.small AllowedValues: - t2.micro - t3.micro - t3.small - t3.medium ConstraintDescription: Must be a valid Cloud9 instance type Cloud9InstanceVolumeSize: Type: Number Description: The Size in GB of the Cloud9 Instance Volume. Default: 15 S3BucketName: Type: String Description: The S3 bucket where packaged artifacts should be uploaded MGNCodeZipUrl: Type: String Description: URL for MGN Code base zip Resources: ################## PERMISSIONS AND ROLES ################# MGNC9Role: Type: AWS::IAM::Role Properties: Tags: - Key: Environment Value: AWS Example AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com - ssm.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AdministratorAccess Path: "/" MGNC9LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: Fn::Join: - '' - - C9LambdaPolicy- - Ref: AWS::Region PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:DescribeStackEvents - cloudformation:DescribeStackResource - cloudformation:DescribeStackResources - ec2:DescribeInstances - ec2:AssociateIamInstanceProfile - ec2:ModifyInstanceAttribute - ec2:ReplaceIamInstanceProfileAssociation - iam:ListInstanceProfiles - ssm:ListCommandInvocations - s3:List* Resource: "*" - Effect: Allow Action: s3:GetObject Resource: !Sub arn:aws:s3:::${S3BucketName}/central_account_monitoring_resources_packaged.yaml - Effect: Allow Action: iam:PassRole Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/${AWS::StackName}-MGNC9Role-* ################## LAMBDA BOOTSTRAP FUNCTION ################ MGNC9BootstrapCustomResource: Type: Custom::MGNC9BootstrapCustomResource Properties: Tags: - Key: SSMBootstrap Value: Active ServiceToken: Fn::GetAtt: - MGNC9BootstrapInstanceLambdaFunction - Arn REGION: Ref: AWS::Region StackName: Ref: AWS::StackName EnvironmentId: Ref: MGNC9Instance LabIdeInstanceProfileName: Ref: MGNC9InstanceProfile LabIdeInstanceProfileArn: Fn::GetAtt: - MGNC9InstanceProfile - Arn MGNC9BootstrapInstanceLambdaFunction: Type: AWS::Lambda::Function Properties: FunctionName: MGNC9BootstrapInstanceLambdaFunction Tags: - Key: Environment Value: AWS Example Handler: index.lambda_handler Role: Fn::GetAtt: - MGNC9LambdaExecutionRole - Arn Runtime: python3.7 MemorySize: 256 Timeout: '600' Code: ZipFile: | from __future__ import print_function import boto3 import json import os import time import traceback import cfnresponse def lambda_handler(event, context): print('Event Info') print(json.dumps(event)) responseData = {"status": 'Success'} # cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') # logger.info('context: {}'.format(context)) responseData = {} ec2 = boto3.client('ec2') if event['RequestType'] == 'Create': try: # Get the InstanceId of the Cloud9 IDE instance_details = ec2.describe_instances(Filters=[{'Name': 'tag:SSMBootstrap', 'Values': ["Active"]}, {'Name': 'instance-state-name', 'Values': ['running']}]) if instance_details: instance = instance_details['Reservations'][0]['Instances'][0] # Create the IamInstanceProfile request object iam_instance_profile = { 'Arn': event['ResourceProperties']['LabIdeInstanceProfileArn'], 'Name': event['ResourceProperties']['LabIdeInstanceProfileName'] } instance_state = instance['State']['Name'] print("instance state: " + instance_state) while instance_state != 'running': time.sleep(5) instance_state = ec2.describe_instances(InstanceIds=[instance['InstanceId']]) # attach instance profile response = ec2.associate_iam_instance_profile(IamInstanceProfile=iam_instance_profile, InstanceId=instance['InstanceId']) responseData = {'Success': 'Attached instance profile to instance: '+instance['InstanceId']} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') else: print("No Cloud9 instances can be found") except Exception as e: print("Error associating instance profile to Cloud9 instance") print(traceback.format_exc()) responseData = {'Error': str(e)} cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID') else: responseData = {'Success': 'Delete or Update operations not supported so skipping'} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') ################## SSM BOOTSRAP HANDLER ############### MGNC9OutputBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 MGNC9SSMDocument: Type: AWS::SSM::Document Properties: Tags: - Key: Environment Value: AWS Example DocumentFormat: YAML DocumentType: Command Content: schemaVersion: '2.2' description: Bootstrap Cloud9 Instance mainSteps: - action: aws:runShellScript name: C9bootstrap inputs: runCommand: - "#!/bin/bash" - date - echo LANG=en_US.utf-8 >> /etc/environment - echo LC_ALL=en_US.UTF-8 >> /etc/environment - . /home/ec2-user/.bashrc - yum -y remove aws-cli; yum -y install sqlite telnet jq strace tree gcc glibc-static python3 python3-pip gettext bash-completion - echo '=== CONFIGURE default python version ===' - PATH=$PATH:/usr/bin - alternatives --set python /usr/bin/python3.7 - echo '=== INSTALL and CONFIGURE default software components ===' - sudo -H -u ec2-user bash -c "pip install --user -U boto boto3 botocore awscli aws-sam-cli" - echo '=== Resizing the Instance volume' - !Sub SIZE=${Cloud9InstanceVolumeSize} - !Sub REGION=${AWS::Region} - | INSTANCEID=$(curl http://169.254.169.254/latest/meta-data/instance-id) VOLUMEID=$(aws ec2 describe-instances \ --instance-id $INSTANCEID \ --query "Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId" \ --output text --region $REGION) aws ec2 modify-volume --volume-id $VOLUMEID --size $SIZE --region $REGION while [ \ "$(aws ec2 describe-volumes-modifications \ --volume-id $VOLUMEID \ --filters Name=modification-state,Values="optimizing","completed" \ --query "length(VolumesModifications)"\ --output text --region $REGION)" != "1" ]; do sleep 1 done if [ $(readlink -f /dev/xvda) = "/dev/xvda" ] then sudo growpart /dev/xvda 1 STR=$(cat /etc/os-release) SUB="VERSION_ID=\"2\"" if [[ "$STR" == *"$SUB"* ]] then sudo xfs_growfs -d / else sudo resize2fs /dev/xvda1 fi else sudo growpart /dev/nvme0n1 1 STR=$(cat /etc/os-release) SUB="VERSION_ID=\"2\"" if [[ "$STR" == *"$SUB"* ]] then sudo xfs_growfs -d / else sudo resize2fs /dev/nvme0n1p1 fi fi - echo '=== CONFIGURE awscli and setting ENVIRONMENT VARS ===' - echo "complete -C '/usr/local/bin/aws_completer' aws" >> /home/ec2-user/.bashrc - mkdir /home/ec2-user/.aws - echo '[default]' > /home/ec2-user/.aws/config - echo 'output = json' >> /home/ec2-user/.aws/config - chmod 600 /home/ec2-user/.aws/config && chmod 600 /home/ec2-user/.aws/credentials - echo 'PATH=$PATH:/usr/local/bin' >> /home/ec2-user/.bashrc - echo 'export PATH' >> /home/ec2-user/.bashrc - echo '=== CLEANING /home/ec2-user ===' - for f in cloud9; do rm -rf /home/ec2-user/$f; done - chown -R ec2-user:ec2-user /home/ec2-user/ - echo '=== PREPARE REBOOT in 1 minute with at ===' - FILE=$(mktemp) && echo $FILE && echo '#!/bin/bash' > $FILE && echo 'reboot -f --verbose' >> $FILE && at now + 1 minute -f $FILE - !Sub echo "Running Command == wget -O main.zip ${MGNCodeZipUrl}" - !Sub wget -O main.zip "${MGNCodeZipUrl}" - unzip -u main.zip - cd aws-application-migration-service-monitoring-main - !Sub ./cfn_package.sh ${S3BucketName} - echo "Bootstrap completed with return code $?" MGNCloud9BootstrapAssociation: Type: AWS::SSM::Association DependsOn: - MGNC9BootstrapCustomResource Properties: Name: !Ref MGNC9SSMDocument OutputLocation: S3Location: OutputS3BucketName: !Ref MGNC9OutputBucket OutputS3KeyPrefix: bootstrapoutput Targets: - Key: tag:SSMBootstrap Values: - Active ################## INSTANCE ##################### MGNC9InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - Ref: MGNC9Role MGNC9Instance: Type: AWS::Cloud9::EnvironmentEC2 Properties: Description: "Cloud 9 environment to install MGN pre-reqs" AutomaticStopTimeMinutes: 1800 InstanceType: Ref: Cloud9InstanceType Name: Ref: AWS::StackName Tags: - Key: SSMBootstrap Value: Active - Key: Environment Value: MGN-Cloud9 ################## LAMBDA SSM DOCUMENT RUN COMMAND FUNCTION ################ MGNCustomResourceSSMDocumentWait: Type: Custom::MGNCustomResourceSSMDocumentWait Properties: Tags: - Key: RUNCOMMANDWAIT Value: Active ServiceToken: Fn::GetAtt: - MGNSSMWaitLambdaFunction - Arn Region: Ref: AWS::Region StackName: Ref: AWS::StackName EnvironmentId: Ref: MGNC9Instance S3BucketName: Ref: S3BucketName S3ObjectName: 'central_account_monitoring_resources_packaged.yaml' MGNSSMWaitLambdaFunction: Type: AWS::Lambda::Function DependsOn: MGNC9SSMDocument Properties: FunctionName: MGNSSMWaitLambdaFunction Tags: - Key: Environment Value: AWS Example Handler: index.lambda_handler Role: Fn::GetAtt: - MGNC9LambdaExecutionRole - Arn Runtime: python3.7 MemorySize: 256 Timeout: '900' Code: ZipFile: | from __future__ import print_function import boto3 import json import os import time import traceback import cfnresponse import sys def fetch_object(bucket_name, object_name, region, iter=180): try: s3 = boto3.client('s3', region_name=region) while True: try: response = s3.get_object( Bucket=bucket_name, Key=object_name ) return response except s3.exceptions.NoSuchKey: print("Waiting for packaged CloudFormation template called: " + object_name + " in the bucket: " + bucket_name) time.sleep(5) iter -= 1 if iter == 0: raise TimeoutError("The Systems Manager Run Command Operation To Package CFN via Cloud9 Timed Out, Exiting...") continue except s3.exceptions.InvalidObjectState as error: raise error except Exception as error: raise error def lambda_handler(event, context): print('Event Info') print(json.dumps(event)) responseData = {"status": 'Success'} # cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') # logger.info('context: {}'.format(context)) region = event['ResourceProperties']['Region'] responseData = {} if event['RequestType'] == 'Create': try: bucket_name = event['ResourceProperties']['S3BucketName'] object_name = event['ResourceProperties']['S3ObjectName'] response = fetch_object(bucket_name, object_name, region) print("The object has been created in the bucket: " + event['ResourceProperties']['S3BucketName'] + " in " + event['ResourceProperties']['Region']) cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') except Exception as e: print("Error running the SSM Document") print(traceback.format_exc()) responseData = {'Error': str(e)} cfnresponse.send(event, context, cfnresponse.FAILED, responseData, 'CustomResourcePhysicalID') else: responseData = {'Success': 'Delete or Update operations not supported so skipping'} cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, 'CustomResourcePhysicalID') Outputs: Cloud9IDE: Value: Fn::Join: - '' - - https:// - Ref: AWS::Region - ".console.aws.amazon.com/cloud9/ide/" - Ref: MGNC9Instance - "?region=" - Ref: AWS::Region