# /* # * Copyright 2020 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. # */ import json import time import boto3 import sys import traceback from botocore.exceptions import ClientError import requests local_org_client = boto3.client('organizations') local_sts_client = boto3.client('sts') def get_client(service): client = boto3.client(service) return client def issue_accept_invite(stslocalresponse, accountnumber,stackregion): # Issue Invite to child account childsession_local_id = stslocalresponse["Credentials"]["AccessKeyId"] childsession_local_key = stslocalresponse["Credentials"]["SecretAccessKey"] childsession_local_token = stslocalresponse["Credentials"]["SessionToken"] try: response = local_org_client.invite_account_to_organization( Target={ 'Id': accountnumber, 'Type': 'ACCOUNT' }, Notes='This invitation will be auto accepted by GovCloud Master. No action is needed.' ) except ClientError as e: if e.response['Error']['Code'] == 'DuplicateHandshakeException': print("Handshake Exists, Continuing") pass organizations_assumed_client = boto3.client( 'organizations', region_name=stackregion, aws_access_key_id=childsession_local_id, aws_secret_access_key=childsession_local_key, aws_session_token=childsession_local_token ) try: response = organizations_assumed_client.list_handshakes_for_account() handshakeId = response["Handshakes"][0]["Id"] # Accept invite in child account response = organizations_assumed_client.accept_handshake( HandshakeId=handshakeId ) except ClientError as e: if e.response['Error']['Code'] == 'HandshakeConstraintViolationException': print("Handshake Already Accepted, Continuing") accounts = [] try: paginator = local_org_client.get_paginator('list_accounts') for response in paginator.paginate(): for account in response['Accounts']: if account['Status'] == 'ACTIVE': accounts.append(account['Id']) else: print(f'Account Not Active: {account}. Skipping') print(f'Account List: {accounts}') except: traceback.print_exc() raise return accounts #NEW def trigger_vpc_deletion(stslocalresponse,sourcebucket,baselinetemplate,stackregion): # Save the details from assumed role into vars childsession_local_id = stslocalresponse["Credentials"]["AccessKeyId"] childsession_local_key = stslocalresponse["Credentials"]["SecretAccessKey"] childsession_local_token = stslocalresponse["Credentials"]["SessionToken"] ec2_client = boto3.client('ec2', aws_access_key_id=childsession_local_id, aws_secret_access_key=childsession_local_key, aws_session_token=childsession_local_token, region_name=stackregion) credentials = stslocalresponse #delete default vpc in every region regions = [] try: regions_response = ec2_client.describe_regions() print(regions_response) for i in range(0,len(regions_response['Regions'])): regions.append(regions_response['Regions'][i]['RegionName']) for r in regions: print(r) try: delete_vpc_response = delete_default_vpc(credentials,r) except ClientError as e: print("An error occured while triggering the Default VPC deletion in {}. Error: {}".format(r,e)) i+=1 return "SUCCESS" except ClientError as e: print("An error occured. Error Stack: {}".format(e)) sys.exit(0) #NEW def delete_default_vpc(credentials,currentregion): try: print("Default VPC deletion in progress in {}".format(currentregion)) ec2_client = boto3.client('ec2', aws_access_key_id=credentials['Credentials']['AccessKeyId'], aws_secret_access_key=credentials['Credentials']['SecretAccessKey'], aws_session_token=credentials['Credentials']['SessionToken'], region_name=currentregion) vpc_response = ec2_client.describe_vpcs() default_vpcid = '' for i in range(0,len(vpc_response['Vpcs'])): if((vpc_response['Vpcs'][i]['InstanceTenancy']) == 'default'): default_vpcid = vpc_response['Vpcs'][0]['VpcId'] if default_vpcid == '': print("Default VPC does not exist for this region {}".format(currentregion)) return ("There is no default VPC for this region {} ".format(currentregion)) subnet_response = ec2_client.describe_subnets() subnet_delete_response = [] default_subnets = [] for i in range(0,len(subnet_response['Subnets'])): if(subnet_response['Subnets'][i]['VpcId'] == default_vpcid): default_subnets.append(subnet_response['Subnets'][i]['SubnetId']) for i in range(0,len(default_subnets)): subnet_delete_response.append(ec2_client.delete_subnet(SubnetId=default_subnets[i],DryRun=False)) default_igw = '' igw_response = ec2_client.describe_internet_gateways() for i in range(0,len(igw_response['InternetGateways'])): for j in range(0,len(igw_response['InternetGateways'][i]['Attachments'])): if(igw_response['InternetGateways'][i]['Attachments'][j]['VpcId'] == default_vpcid): default_igw = igw_response['InternetGateways'][i]['InternetGatewayId'] #print(default_igw) if default_igw is '': pass else: detach_default_igw_response = ec2_client.detach_internet_gateway(InternetGatewayId=default_igw,VpcId=default_vpcid,DryRun=False) delete_internet_gateway_response = ec2_client.delete_internet_gateway(InternetGatewayId=default_igw) #print("Default IGW " + currentregion + "Deleted.") time.sleep(10) delete_vpc_response = ec2_client.delete_vpc(VpcId=default_vpcid,DryRun=False) print("Deleted Default VPC in {}".format(currentregion)) return delete_vpc_response except ClientError as e: print(f'VPC deletion failed, need to create. {e}') #NEW def deploy_resources(credentials, baselinetemplate, sourcebucket, stackname, stackregion, ServiceCatalogUserName, ServiceCatalogUserPassword,account_id): ''' Create a CloudFormation stack of resources within the new account ''' datestamp = time.strftime("%d/%m/%Y") client = boto3.client('cloudformation', aws_access_key_id=credentials['Credentials']['AccessKeyId'], aws_secret_access_key=credentials['Credentials']['SecretAccessKey'], aws_session_token=credentials['Credentials']['SessionToken'], region_name=stackregion) template = get_template(sourcebucket,baselinetemplate) print("Creating stack " + stackname + " in " + account_id) time.sleep(120) retry_counter = 0 creating_stack = True while creating_stack is True: try: creating_stack = False create_stack_response = client.create_stack( StackName=stackname, TemplateBody=template, Parameters=[ { 'ParameterKey' : 'ServiceCatalogUserName', 'ParameterValue' : ServiceCatalogUserName }, { 'ParameterKey' : 'ServiceCatalogUserPassword', 'ParameterValue' : ServiceCatalogUserPassword } ], NotificationARNs=[], Capabilities=[ 'CAPABILITY_NAMED_IAM', ], OnFailure='ROLLBACK', Tags=[ { 'Key': 'ManagedResource', 'Value': 'True' }, { 'Key': 'DeployDate', 'Value': datestamp } ] ) except ClientError as e: creating_stack = True print(e) retry_counter += 1 if retry_counter >= 3: print("Stack encountered an issue, check stack in destination account") creating_stack = False print("Retrying...") time.sleep(10) stack_building = True print("Stack creation in process...") print(create_stack_response) while stack_building is True: event_list = client.describe_stack_events(StackName=stackname).get("StackEvents") stack_event = event_list[0] if (stack_event.get('ResourceType') == 'AWS::CloudFormation::Stack' and stack_event.get('ResourceStatus') == 'CREATE_COMPLETE'): stack_building = False print("Stack construction complete.") elif (stack_event.get('ResourceType') == 'AWS::CloudFormation::Stack' and stack_event.get('ResourceStatus') == 'ROLLBACK_COMPLETE'): stack_building = False print("Stack construction failed.") sys.exit(1) else: print(stack_event) print("Stack building . . .") time.sleep(10) stack = client.describe_stacks(StackName=stackname) return stack def get_template(sourcebucket,baselinetemplate): ''' Read a template file and return the contents ''' #print("Reading resources from " + templatefile) s3 = boto3.resource('s3') #obj = s3.Object('cf-to-create-lambda','5-newbaseline.yml') obj = s3.Object(sourcebucket,baselinetemplate) return obj.get()['Body'].read().decode('utf-8') def respond_cloudformation(event, status, data=None): responseBody = { 'Status': status, 'Reason': 'See the details in CloudWatch Log Stream', 'PhysicalResourceId': event['ServiceToken'], 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'], 'Data': data } print('Response = ' + json.dumps(responseBody)) print(event) requests.put(event['ResponseURL'], data=json.dumps(responseBody)) def delete_respond_cloudformation(event, status, message): responseBody = { 'Status': status, 'Reason': message, 'PhysicalResourceId': event['ServiceToken'], 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'] } requests.put(event['ResponseURL'], data=json.dumps(responseBody)) def lambda_handler(event, context): print(json.dumps(event)) if event['RequestType'] == 'Create': try: accountnumber = event['ResourceProperties']["AccountNumber"] templateurl = event['ResourceProperties']['ChildTemplateS3Url'] centrals3bucket = event['ResourceProperties']['CentralS3Bucket'] assume_role_name = event['ResourceProperties']['AssumeRoleName'] #NEW stackname = event['ResourceProperties']['StackName'] stackregion = event['ResourceProperties']['StackRegion'] ServiceCatalogUserName = event['ResourceProperties']['ServiceCatalogUserName'] ServiceCatalogUserPassword = event['ResourceProperties']['ServiceCatalogUserPassword'] sourcebucket = centrals3bucket baselinetemplate = templateurl # Assume Role in child account stslocalresponse = local_sts_client.assume_role( RoleArn=f'arn:aws-us-gov:iam::{accountnumber}:role/{assume_role_name}', RoleSessionName='childsession' ) #Accept AWS Organizations invite print("Inviting new account to AWS Organizations, and accepting it") org_account_list = issue_accept_invite(stslocalresponse, accountnumber,stackregion) print("Organizations account list: {} ".format(org_account_list)) #Delete default VPCs from all regions print("Deleting default vpc in all AWS GovCloud(US) regions") trigger_vpc_deletion(stslocalresponse,sourcebucket,baselinetemplate,stackregion) print("Deploy baseline AWS Service Catalog resources") deploy_resources(stslocalresponse, baselinetemplate, sourcebucket, stackname, stackregion, ServiceCatalogUserName, ServiceCatalogUserPassword,accountnumber) print("Respond to CloudFormation resource") respond_cloudformation(event, "SUCCESS", { "Message": "Account bootstrapped successfully", "AccountID" : accountnumber, "LoginURL" : "https://" +accountnumber+".signin.amazonaws-us-gov.com/console", "Username" : ServiceCatalogUserName }) except Exception as e: print(f'Onboarding was unable to successfully complete. Check Logs for failures and correct manually.{e}') delete_respond_cloudformation(event, "FAILED", f'Cannot complete onboarding process. {e}') elif event['RequestType'] in ('Update', 'Delete'): print("Template in Update/Delete Status") respond_cloudformation(event, "SUCCESS", {"Message": "Resource update/delete successful!"}) else: print(f'Unexpected event request from CloudFormation. RequestType {event["RequestType"]}')