#### # Copyright 2019 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 from __future__ import print_function import boto3 import botocore import time import sys import argparse import os import urllib import json import requests '''AWS Organizations Create Account and Provision Resources via CloudFormation This module creates a new account using Organizations, then calls CloudFormation to deploy baseline resources within that account via a local tempalte file. ''' __version__ = '1.2' def get_client(service): client = boto3.client(service) return client def create_account(event,accountname,accountemail,accountrole,access_to_billing,scp,root_id): account_id = 'None' client = get_client('organizations') try: print("Trying to create the account with {}".format(accountemail)) create_account_response = client.create_gov_cloud_account(Email=accountemail, AccountName=accountname, RoleName=accountrole, IamUserAccessToBilling=access_to_billing) # while(create_account_response['CreateAccountStatus']['State'] is 'IN_PROGRESS'): # print(create_account_response['CreateAccountStatus']['State']) print(str(create_account_response)) time.sleep(40) account_status = client.describe_create_account_status(CreateAccountRequestId=create_account_response['CreateAccountStatus']['Id']) print("Account Creation status: {}".format(account_status['CreateAccountStatus']['State'])) if(account_status['CreateAccountStatus']['State'] == 'FAILED'): if(account_status['CreateAccountStatus']['FailureReason'] == 'EMAIL_ALREADY_EXISTS'): print("Account already vended, moving on") else: print("Account Creation Failed. Reason : {}".format(account_status['CreateAccountStatus']['FailureReason'])) delete_respond_cloudformation(event, "FAILED", account_status['CreateAccountStatus']['FailureReason']) sys.exit(1) except botocore.exceptions.ClientError as e: print("In the except module. Error : {}".format(e)) delete_respond_cloudformation(event, "FAILED", "Account Creation Failed. " +e+ ".") print("Waiting for commercial account to vend") time.sleep(20) create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) account_id = create_account_status_response.get('CreateAccountStatus').get('AccountId') govcloud_account_id = create_account_status_response.get('CreateAccountStatus').get('GovCloudAccountId') while(account_id is None ): print("Still Waiting for commercial account to vend") create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) print("Last Run was "+str(create_account_status_response)) account_id = create_account_status_response.get('CreateAccountStatus').get('AccountId') while(govcloud_account_id is None ): time.sleep(10) print("Still Waiting for govcloud account to vend") create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) govcloud_account_id = create_account_status_response.get('CreateAccountStatus').get('GovCloudAccountId') return(create_account_response,account_id, govcloud_account_id) def assume_role(account_id, account_role): sts_client = boto3.client('sts') role_arn = 'arn:aws:iam::' + account_id + ':role/' + account_role assuming_role = True while assuming_role is True: try: assuming_role = False assumedRoleObject = sts_client.assume_role( RoleArn=role_arn, RoleSessionName="NewAccountRole" ) except botocore.exceptions.ClientError as e: assuming_role = True print(e) print("Retrying...") time.sleep(60) # From the response that contains the assumed role, get the temporary # credentials that can be used to make subsequent API calls return assumedRoleObject['Credentials'] def get_ou_name_id(root_id,organization_unit_name): ou_client = get_client('organizations') list_of_OU_ids = [] list_of_OU_names = [] ou_name_to_id = {} list_of_OUs_response = ou_client.list_organizational_units_for_parent(ParentId=root_id) for i in list_of_OUs_response['OrganizationalUnits']: list_of_OU_ids.append(i['Id']) list_of_OU_names.append(i['Name']) if(organization_unit_name not in list_of_OU_names): print("The provided Organization Unit Name doesnt exist. Creating an OU named: {}".format(organization_unit_name)) try: ou_creation_response = ou_client.create_organizational_unit(ParentId=root_id,Name=organization_unit_name) for k,v in ou_creation_response.items(): for k1,v1 in v.items(): if(k1 == 'Name'): organization_unit_name = v1 if(k1 == 'Id'): organization_unit_id = v1 except botocore.exceptions.ClientError as e: print("Error in creating the OU: {}".format(e)) respond_cloudformation(event, "FAILED", { "Message": "Could not list out AWS Organization OUs. Account creation Aborted."}) else: for i in range(len(list_of_OU_names)): ou_name_to_id[list_of_OU_names[i]] = list_of_OU_ids[i] organization_unit_id = ou_name_to_id[organization_unit_name] return(organization_unit_name,organization_unit_id) 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)) lambda_client = get_client('lambda') function_name = os.environ['AWS_LAMBDA_FUNCTION_NAME'] print('Deleting resources and rolling back the stack.') #lambda_client.delete_function(FunctionName=function_name) #requests.put(event['ResponseURL'], data=json.dumps(responseBody)) def lambda_handler(event,context): print(event) client = get_client('organizations') accountname = event['ResourceProperties']['accountname'] accountemail = event['ResourceProperties']['accountemail'] organization_unit_name =event['ResourceProperties']['organizationunitname'] accountrole = 'OrganizationAccountAccessRole' access_to_billing = "DENY" scp = None #account_id = None RegiontoAZMap = { "ap-northeast-1": ["ap-northeast-1a","ap-northeast-1c"], "ap-northeast-2": [ "ap-northeast-2a","ap-northeast-2c"], "ap-northeast-3": [ "ap-northeast-3a" ], "ap-south-1": [ "ap-south-1a","ap-south-1b"], "ap-southeast-1": [ "ap-southeast-1a","ap-southeast-1b","ap-southeast-1c"], "ap-southeast-2": [ "ap-southeast-2a","ap-southeast-2b","ap-southeast-2c"], "ca-central-1": ["ca-central-1a","ca-central-1b"], "eu-central-1": ["eu-central-1a","eu-central-1b","eu-central-1c"], "eu-west-1": [ "eu-west-1a","eu-west-1b","eu-west-1c"], "eu-west-2": [ "eu-west-2a","eu-west-2b","eu-west-2c"], "eu-west-3": [ "eu-west-3a","eu-west-3b","eu-west-3c"], "sa-east-1": [ "sa-east-1a","sa-east-1c"], "us-east-1": [ "us-east-1a","us-east-1b","us-east-1c","us-east-1d","us-east-1e","us-east-1f"], "us-east-2": [ "us-east-2a","us-east-2b","us-east-2c"], "us-west-1": [ "us-west-1b","us-west-1c"], "us-west-2": [ "us-west-2a","us-west-2b","us-west-2c"] } if (event['RequestType'] == 'Create'): top_level_account = event['ServiceToken'].split(':')[4] print(top_level_account) org_client = get_client('organizations') try: list_roots_response = org_client.list_roots() print(list_roots_response) root_id = list_roots_response['Roots'][0]['Id'] print(root_id) except: root_id = "Error" if root_id is not "Error": print("Creating new account: " + accountname + " (" + accountemail + ")") ### List the available AWS Organization OU's #if(organization_unit_name is not None): #(organization_unit_name,organization_unit_id) = get_ou_name_id(root_id,organization_unit_name) (create_account_response,account_id,govcloud_account_id) = create_account(event,accountname,accountemail,accountrole,access_to_billing,scp,root_id) print(create_account_response) print("Created Commercial account:{}\n".format(account_id)) print("Created GovCloud account:{}\n".format(govcloud_account_id)) root_id = client.list_roots().get('Roots')[0].get('Id') #print(root_id) #print('Outside try block - {}'.format(organization_unit_name)) if(organization_unit_name!='None'): try: (organization_unit_name,organization_unit_id) = get_ou_name_id(root_id,organization_unit_name) move_response = org_client.move_account(AccountId=account_id,SourceParentId=root_id,DestinationParentId=organization_unit_id) except Exception as ex: template = "An exception of type {0} occurred. Arguments:\n{1!r} " message = template.format(type(ex).__name__, ex.args) print(message) respond_cloudformation(event, "SUCCESS", { "Message": "Account Created!", "AccountID" : account_id, "GovCloudAccountId" : govcloud_account_id}) else: print("Cannot access the AWS Organization ROOT. Contact the master account Administrator for more details.") #sys.exit(1) delete_respond_cloudformation(event, "FAILED", "Cannot access the AWS Organization ROOT. Contact the master account Administrator for more details.Deleting Lambda Function.") if(event['RequestType'] == 'Update'): print("Template in Update Status") respond_cloudformation(event, "SUCCESS", { "Message": "Resource update successful!" }) #respond_cloudformation(event, "SUCCESS", { "Message": "Account Created!","Login URL : "https://" +account_id+".signin.aws.amazon.com/console", "AccountID" : account_id, "Username" : adminusername, "Role" : newrole }) # elif(event['RequestType'] == 'Wait'): # # account_status = 'IN_PROGRESS' # # create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) # # while account_status == 'IN_PROGRESS': # # create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) # # print("Create In Progress. Create Account Status Response : {} \n".format(create_account_status_response)) # # account_status = create_account_status_response.get('CreateAccountStatus').get('State') # # if account_status == 'SUCCEEDED': # # account_id = create_account_status_response.get('CreateAccountStatus').get('AccountId') # # print("Account Creation SUCCEEDED. Create Account Status Response for account URL : {}\n".format(create_account_status_response)) # # elif account_status == 'FAILED': # # print("Account creation failed: " + create_account_status_response.get('CreateAccountStatus').get('FailureReason')) # time.sleep(30) # respond_cloudformation(event, "Aborted", { "Message": "Retuned back form the wait condition !!" }) # exit() elif(event['RequestType'] == 'Delete'): try: delete_respond_cloudformation(event, "SUCCESS", "Delete Request Initiated. Deleting Lambda Function.") except: print("Couldnt initiate delete response.")