├── .github └── PULL_REQUEST_TEMPLATE.md ├── AccountCreationLambda.py ├── AccountCreationLambda.zip ├── Accountbaseline.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md └── accountbuilder.yml /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /AccountCreationLambda.py: -------------------------------------------------------------------------------- 1 | 2 | #### 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | # software and associated documentation files (the "Software"), to deal in the Software 7 | # without restriction, including without limitation the rights to use, copy, modify, 8 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | # permit persons to whom the Software is furnished to do so. 10 | # 11 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | #### 18 | 19 | 20 | #!/usr/bin/env python 21 | 22 | from __future__ import print_function 23 | import boto3 24 | import botocore 25 | import time 26 | import sys 27 | import argparse 28 | import os 29 | import urllib 30 | import json 31 | from botocore.vendored import requests 32 | 33 | '''AWS Organizations Create Account and Provision Resources via CloudFormation 34 | 35 | This module creates a new account using Organizations, then calls CloudFormation to deploy baseline resources within that account via a local tempalte file. 36 | 37 | ''' 38 | 39 | __version__ = '1.0' 40 | __author__ = '@VKK@' 41 | __email__ = 'vkkuchib@' 42 | 43 | def get_client(service): 44 | client = boto3.client(service) 45 | return client 46 | 47 | def create_account(event,accountname,accountemail,accountrole,access_to_billing,scp,root_id): 48 | account_id = 'None' 49 | client = get_client('organizations') 50 | 51 | try: 52 | print("Trying to create the account with {}".format(accountemail)) 53 | create_account_response = client.create_account(Email=accountemail, AccountName=accountname, 54 | RoleName=accountrole, 55 | IamUserAccessToBilling=access_to_billing) 56 | # while(create_account_response['CreateAccountStatus']['State'] is 'IN_PROGRESS'): 57 | # print(create_account_response['CreateAccountStatus']['State']) 58 | time.sleep(40) 59 | account_status = client.describe_create_account_status(CreateAccountRequestId=create_account_response['CreateAccountStatus']['Id']) 60 | print("Account Creation status: {}".format(account_status['CreateAccountStatus']['State'])) 61 | if(account_status['CreateAccountStatus']['State'] == 'FAILED'): 62 | print("Account Creation Failed. Reason : {}".format(account_status['CreateAccountStatus']['FailureReason'])) 63 | delete_respond_cloudformation(event, "FAILED", account_status['CreateAccountStatus']['FailureReason']) 64 | sys.exit(1) 65 | 66 | except botocore.exceptions.ClientError as e: 67 | print("In the except module. Error : {}".format(e)) 68 | delete_respond_cloudformation(event, "FAILED", "Account Creation Failed. Deleting Lambda Function." +e+ ".") 69 | 70 | time.sleep(10) 71 | create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) 72 | account_id = create_account_status_response.get('CreateAccountStatus').get('AccountId') 73 | while(account_id is None ): 74 | create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) 75 | account_id = create_account_status_response.get('CreateAccountStatus').get('AccountId') 76 | #move_response = client.move_account(AccountId=account_id,SourceParentId=root_id,DestinationParentId=organization_unit_id) 77 | return(create_account_response,account_id) 78 | 79 | 80 | def get_template(sourcebucket,baselinetemplate): 81 | 82 | s3 = boto3.resource('s3') 83 | try: 84 | obj = s3.Object(sourcebucket,baselinetemplate) 85 | return obj.get()['Body'].read().decode('utf-8') 86 | except botocore.exceptions.ClientError as e: 87 | print("Error accessing the source bucket. Error : {}".format(e)) 88 | return e 89 | 90 | 91 | 92 | def delete_default_vpc(credentials,currentregion): 93 | #print("Default VPC deletion in progress in {}".format(currentregion)) 94 | ec2_client = boto3.client('ec2', 95 | aws_access_key_id=credentials['AccessKeyId'], 96 | aws_secret_access_key=credentials['SecretAccessKey'], 97 | aws_session_token=credentials['SessionToken'], 98 | region_name=currentregion) 99 | 100 | vpc_response = ec2_client.describe_vpcs() 101 | for i in range(0,len(vpc_response['Vpcs'])): 102 | if((vpc_response['Vpcs'][i]['InstanceTenancy']) == 'default'): 103 | default_vpcid = vpc_response['Vpcs'][0]['VpcId'] 104 | 105 | subnet_response = ec2_client.describe_subnets() 106 | subnet_delete_response = [] 107 | default_subnets = [] 108 | for i in range(0,len(subnet_response['Subnets'])): 109 | if(subnet_response['Subnets'][i]['VpcId'] == default_vpcid): 110 | default_subnets.append(subnet_response['Subnets'][i]['SubnetId']) 111 | for i in range(0,len(default_subnets)): 112 | subnet_delete_response.append(ec2_client.delete_subnet(SubnetId=default_subnets[i],DryRun=False)) 113 | 114 | #print("Default Subnets" + currentregion + "Deleted.") 115 | 116 | igw_response = ec2_client.describe_internet_gateways() 117 | for i in range(0,len(igw_response['InternetGateways'])): 118 | for j in range(0,len(igw_response['InternetGateways'][i]['Attachments'])): 119 | if(igw_response['InternetGateways'][i]['Attachments'][j]['VpcId'] == default_vpcid): 120 | default_igw = igw_response['InternetGateways'][i]['InternetGatewayId'] 121 | #print(default_igw) 122 | detach_default_igw_response = ec2_client.detach_internet_gateway(InternetGatewayId=default_igw,VpcId=default_vpcid,DryRun=False) 123 | delete_internet_gateway_response = ec2_client.delete_internet_gateway(InternetGatewayId=default_igw) 124 | 125 | #print("Default IGW " + currentregion + "Deleted.") 126 | 127 | time.sleep(10) 128 | delete_vpc_response = ec2_client.delete_vpc(VpcId=default_vpcid,DryRun=False) 129 | print("Deleted Default VPC in {}".format(currentregion)) 130 | return delete_vpc_response 131 | 132 | def create_custom_vpc(credentials,stackregion,AZ1Name,AZ2Name,VPCCIDR,SubnetAPublicCIDR,SubnetBPublicCIDR,SubnetAPrivateCIDR,SubnetBPrivateCIDR,VPCName): 133 | #print(credentials,stackregion,AZ1Name,AZ2Name,VPCCIDR,SubnetAPublicCIDR,SubnetBPublicCIDR,SubnetAPrivateCIDR,SubnetBPrivateCIDR) 134 | ec2 = boto3.client('ec2', aws_access_key_id=credentials['AccessKeyId'], 135 | aws_secret_access_key=credentials['SecretAccessKey'], 136 | aws_session_token=credentials['SessionToken'], 137 | region_name=stackregion) 138 | 139 | # create VPC 140 | vpc = ec2.create_vpc(CidrBlock=VPCCIDR) 141 | # # we can assign a name to vpc, or any resource, by using tag 142 | #vpc.create_tags(Tags=[{"Key": "Name", "Value": VPCName}]) 143 | # vpc.wait_until_available() 144 | vpc_id = vpc['Vpc']['VpcId'] 145 | create_tags_response = ec2.create_tags(Resources = [vpc_id], Tags = [ {'Key' : 'Name' , 'Value' : VPCName}]) 146 | print("VPC ID : {}".format(vpc_id)) 147 | 148 | #create an EIP for NAT Gateway 149 | eipnatgwA = ec2.allocate_address(Domain='vpc') 150 | eipnatgwB = ec2.allocate_address(Domain='vpc') 151 | print("EIP for NatGW A : {}".format(eipnatgwA['AllocationId'])) 152 | print("EIP for NatGW B : {}".format(eipnatgwB['AllocationId'])) 153 | 154 | 155 | # create then attach internet gateway 156 | ig = ec2.create_internet_gateway() 157 | ig_id = ig['InternetGateway']['InternetGatewayId'] 158 | ec2.attach_internet_gateway(InternetGatewayId=ig_id,VpcId=vpc_id) 159 | print("InternetGateway : {}".format(ig_id)) 160 | 161 | 162 | # create a route table and a public route 163 | public_route_table = ec2.create_route_table(VpcId=vpc_id) 164 | public_route_table_id = public_route_table['RouteTable']['RouteTableId'] 165 | public_route = ec2.create_route(DestinationCidrBlock='0.0.0.0/0',GatewayId=ig_id,RouteTableId=public_route_table_id) 166 | print("Public Route Table ID : {}".format(public_route_table_id)) 167 | 168 | 169 | 170 | # create subnets 171 | publicsubnetA = ec2.create_subnet(AvailabilityZone=AZ1Name,CidrBlock=SubnetAPublicCIDR, VpcId=vpc_id) 172 | publicsubnetB = ec2.create_subnet(AvailabilityZone=AZ2Name,CidrBlock=SubnetBPublicCIDR, VpcId=vpc_id) 173 | public_subnetA_id = publicsubnetA['Subnet']['SubnetId'] 174 | public_subnetB_id = publicsubnetB['Subnet']['SubnetId'] 175 | print("Public SubnetA ID : {}".format(public_subnetA_id)) 176 | print("Public SubnetB ID : {}".format(public_subnetB_id)) 177 | 178 | privatesubnetA = ec2.create_subnet(AvailabilityZone=AZ1Name,CidrBlock=SubnetAPrivateCIDR, VpcId=vpc_id) 179 | privatesubnetB = ec2.create_subnet(AvailabilityZone=AZ2Name,CidrBlock=SubnetBPrivateCIDR, VpcId=vpc_id) 180 | private_subnetA_id = privatesubnetA['Subnet']['SubnetId'] 181 | private_subnetB_id = privatesubnetB['Subnet']['SubnetId'] 182 | print("Private SubnetA ID : {}".format(private_subnetA_id)) 183 | print("Private SubnetB ID : {}".format(private_subnetB_id)) 184 | 185 | 186 | # associate the route table with the subnet 187 | public_route_table_associationA = ec2.associate_route_table(SubnetId=public_subnetA_id,RouteTableId=public_route_table_id) 188 | public_route_table_associationB = ec2.associate_route_table(SubnetId=public_subnetB_id,RouteTableId=public_route_table_id) 189 | print("Public Route Table Association ID for Subnet A : {}".format(public_route_table_associationA['AssociationId'])) 190 | print("Public Route Table Association ID for Subnet B : {}".format(public_route_table_associationB['AssociationId'])) 191 | 192 | 193 | # create then attach a NAT gateway 194 | ngwsubnetA = ec2.create_nat_gateway(AllocationId=eipnatgwA['AllocationId'],SubnetId=public_subnetA_id) 195 | ngwsubnetB = ec2.create_nat_gateway(AllocationId=eipnatgwB['AllocationId'],SubnetId=public_subnetB_id) 196 | ngwsubnetA_id = ngwsubnetA['NatGateway']['NatGatewayId'] 197 | ngwsubnetB_id = ngwsubnetB['NatGateway']['NatGatewayId'] 198 | print("NAT GW in Subnet A : {}".format(ngwsubnetA_id)) 199 | print("NAT GW in Subnet B : {}".format(ngwsubnetB_id)) 200 | 201 | time.sleep(60) 202 | 203 | private_route_table_A = ec2.create_route_table(VpcId=vpc_id) 204 | private_route_table_A_id = private_route_table_A['RouteTable']['RouteTableId'] 205 | private_route_A = ec2.create_route(DestinationCidrBlock='0.0.0.0/0',NatGatewayId=ngwsubnetA_id,RouteTableId=private_route_table_A_id) 206 | print("Private Route Table A ID : {}".format(private_route_table_A_id)) 207 | 208 | private_route_table_B = ec2.create_route_table(VpcId=vpc_id) 209 | private_route_table_B_id = private_route_table_B['RouteTable']['RouteTableId'] 210 | private_route_B = ec2.create_route(DestinationCidrBlock='0.0.0.0/0',NatGatewayId=ngwsubnetB_id,RouteTableId=private_route_table_B_id) 211 | print("Private Route Table B ID : {}".format(private_route_table_B_id)) 212 | 213 | private_route_table_associationA = ec2.associate_route_table(SubnetId=private_subnetA_id,RouteTableId=private_route_table_A_id) 214 | private_route_table_associationB = ec2.associate_route_table(SubnetId=private_subnetB_id,RouteTableId=private_route_table_B_id) 215 | print("Private Route Table Association ID for Subnet A : {}".format(private_route_table_associationA['AssociationId'])) 216 | print("Private Route Table Association ID for Subnet B : {}".format(private_route_table_associationB['AssociationId'])) 217 | 218 | def deploy_resources(credentials, template, stackname, stackregion, adminusername, adminpassword,account_id,newrole_arn): 219 | 220 | datestamp = time.strftime("%d/%m/%Y") 221 | client = boto3.client('cloudformation', 222 | aws_access_key_id=credentials['AccessKeyId'], 223 | aws_secret_access_key=credentials['SecretAccessKey'], 224 | aws_session_token=credentials['SessionToken'], 225 | region_name=stackregion) 226 | print("Creating stack " + stackname + " in " + account_id) 227 | creating_stack = True 228 | try: 229 | while creating_stack is True: 230 | try: 231 | creating_stack = False 232 | create_stack_response = client.create_stack( 233 | StackName=stackname, 234 | TemplateBody=template, 235 | Parameters=[ 236 | { 237 | 'ParameterKey' : 'AdminUsername', 238 | 'ParameterValue' : adminusername 239 | }, 240 | { 241 | 'ParameterKey' : 'AdminPassword', 242 | 'ParameterValue' : adminpassword 243 | }, 244 | { 245 | 'ParameterKey' : 'NewRoleArn', 246 | 'ParameterValue' : newrole_arn 247 | } 248 | ], 249 | NotificationARNs=[], 250 | Capabilities=[ 251 | 'CAPABILITY_NAMED_IAM', 252 | ], 253 | OnFailure='ROLLBACK', 254 | Tags=[ 255 | { 256 | 'Key': 'ManagedResource', 257 | 'Value': 'True' 258 | }, 259 | { 260 | 'Key': 'DeployDate', 261 | 'Value': datestamp 262 | } 263 | ] 264 | ) 265 | except botocore.exceptions.ClientError as e: 266 | creating_stack = True 267 | print(e) 268 | print("Retrying...") 269 | time.sleep(10) 270 | 271 | stack_building = True 272 | print("Stack creation in process...") 273 | print(create_stack_response) 274 | while stack_building is True: 275 | event_list = client.describe_stack_events(StackName=stackname).get("StackEvents") 276 | stack_event = event_list[0] 277 | 278 | if (stack_event.get('ResourceType') == 'AWS::CloudFormation::Stack' and 279 | stack_event.get('ResourceStatus') == 'CREATE_COMPLETE'): 280 | stack_building = False 281 | print("Stack construction complete.") 282 | elif (stack_event.get('ResourceType') == 'AWS::CloudFormation::Stack' and 283 | stack_event.get('ResourceStatus') == 'ROLLBACK_COMPLETE'): 284 | stack_building = False 285 | print("Stack construction failed.") 286 | #sys.exit(1) 287 | else: 288 | print(stack_event) 289 | print("Stack building . . .") 290 | time.sleep(10) 291 | stack = client.describe_stacks(StackName=stackname) 292 | return stack 293 | except botocore.exceptions.ClientError as e: 294 | print("Error deploying stack.There might be an error either accessing the Source bucket or accessing the baseline template from the source bucket.Error : {}".format(e)) 295 | return e 296 | 297 | def assume_role(account_id, account_role): 298 | sts_client = boto3.client('sts') 299 | role_arn = 'arn:aws:iam::' + account_id + ':role/' + account_role 300 | assuming_role = True 301 | while assuming_role is True: 302 | try: 303 | assuming_role = False 304 | assumedRoleObject = sts_client.assume_role( 305 | RoleArn=role_arn, 306 | RoleSessionName="NewAccountRole" 307 | ) 308 | except botocore.exceptions.ClientError as e: 309 | assuming_role = True 310 | print(e) 311 | print("Retrying...") 312 | time.sleep(60) 313 | 314 | # From the response that contains the assumed role, get the temporary 315 | # credentials that can be used to make subsequent API calls 316 | return assumedRoleObject['Credentials'] 317 | 318 | def get_ou_name_id(root_id,organization_unit_name): 319 | 320 | ou_client = get_client('organizations') 321 | list_of_OU_ids = [] 322 | list_of_OU_names = [] 323 | ou_name_to_id = {} 324 | 325 | list_of_OUs_response = ou_client.list_organizational_units_for_parent(ParentId=root_id) 326 | 327 | for i in list_of_OUs_response['OrganizationalUnits']: 328 | list_of_OU_ids.append(i['Id']) 329 | list_of_OU_names.append(i['Name']) 330 | 331 | if(organization_unit_name not in list_of_OU_names): 332 | print("The provided Organization Unit Name doesnt exist. Creating an OU named: {}".format(organization_unit_name)) 333 | try: 334 | ou_creation_response = ou_client.create_organizational_unit(ParentId=root_id,Name=organization_unit_name) 335 | for k,v in ou_creation_response.items(): 336 | for k1,v1 in v.items(): 337 | if(k1 == 'Name'): 338 | organization_unit_name = v1 339 | if(k1 == 'Id'): 340 | organization_unit_id = v1 341 | except botocore.exceptions.ClientError as e: 342 | print("Error in creating the OU: {}".format(e)) 343 | respond_cloudformation(event, "FAILED", { "Message": "Could not list out AWS Organization OUs. Account creation Aborted."}) 344 | 345 | else: 346 | for i in range(len(list_of_OU_names)): 347 | ou_name_to_id[list_of_OU_names[i]] = list_of_OU_ids[i] 348 | organization_unit_id = ou_name_to_id[organization_unit_name] 349 | 350 | return(organization_unit_name,organization_unit_id) 351 | 352 | def create_newrole(newrole,top_level_account,credentials,newrolepolicy): 353 | iam_client = boto3.client('iam',aws_access_key_id=credentials['AccessKeyId'], 354 | aws_secret_access_key=credentials['SecretAccessKey'], 355 | aws_session_token=credentials['SessionToken']) 356 | print("arn:aws:iam::"+top_level_account+":root") 357 | trust_policy_document = json.dumps( 358 | { 359 | "Version": "2012-10-17", 360 | "Statement": [ 361 | { 362 | "Effect": "Allow", 363 | "Principal": { 364 | "AWS": "arn:aws:iam::"+top_level_account+":root" 365 | }, 366 | "Action": "sts:AssumeRole" 367 | } 368 | ] 369 | } 370 | ) 371 | print(trust_policy_document) 372 | #new_role_policy = json.dumps(newrolepolicy) 373 | print(newrolepolicy) 374 | try: 375 | create_role_response = iam_client.create_role(RoleName=newrole,AssumeRolePolicyDocument=trust_policy_document,Description=newrole,MaxSessionDuration=3600) 376 | print(create_role_response['Role']['Arn']) 377 | 378 | except botocore.exceptions.ClientError as e: 379 | print("Error Occured in creating a role : {}",format(e)) 380 | try: 381 | #attach_policy_response = iam_client.attach_role_policy(RoleName=newrole,PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess') 382 | update_role_response = iam_client.put_role_policy(RoleName=newrole,PolicyName='NewRolePolicy',PolicyDocument=newrolepolicy) 383 | except botocore.exceptions.ClientError as e: 384 | print("Error attaching policy to the role : {}".format(e)) 385 | 386 | print("{},{},{}".format(newrole,top_level_account,credentials)) 387 | return create_role_response['Role']['Arn'] 388 | 389 | def selfinvoke(event,status): 390 | lambda_client = boto3.client('lambda') 391 | function_name = os.environ['AWS_LAMBDA_FUNCTION_NAME'] 392 | event['RequestType'] = status 393 | print('invoking itself ' + function_name) 394 | response = lambda_client.invoke(FunctionName=function_name, InvocationType='Event',Payload=json.dumps(event)) 395 | 396 | def respond_cloudformation(event, status, data=None): 397 | responseBody = { 398 | 'Status': status, 399 | 'Reason': 'See the details in CloudWatch Log Stream', 400 | 'PhysicalResourceId': event['ServiceToken'], 401 | 'StackId': event['StackId'], 402 | 'RequestId': event['RequestId'], 403 | 'LogicalResourceId': event['LogicalResourceId'], 404 | 'Data': data 405 | } 406 | 407 | print('Response = ' + json.dumps(responseBody)) 408 | print(event) 409 | requests.put(event['ResponseURL'], data=json.dumps(responseBody)) 410 | 411 | def delete_respond_cloudformation(event, status, message): 412 | responseBody = { 413 | 'Status': status, 414 | 'Reason': message, 415 | 'PhysicalResourceId': event['ServiceToken'], 416 | 'StackId': event['StackId'], 417 | 'RequestId': event['RequestId'], 418 | 'LogicalResourceId': event['LogicalResourceId'] 419 | } 420 | 421 | requests.put(event['ResponseURL'], data=json.dumps(responseBody)) 422 | lambda_client = get_client('lambda') 423 | function_name = os.environ['AWS_LAMBDA_FUNCTION_NAME'] 424 | print('Deleting resources and rolling back the stack.') 425 | lambda_client.delete_function(FunctionName=function_name) 426 | #requests.put(event['ResponseURL'], data=json.dumps(responseBody)) 427 | 428 | 429 | 430 | 431 | def main(event,context): 432 | print(event) 433 | client = get_client('organizations') 434 | accountname = os.environ['accountname'] 435 | accountemail = os.environ['accountemail'] 436 | newrole = os.environ['newrole'] 437 | newrolepolicy = os.environ['newrolepolicy'] 438 | organization_unit_name = os.environ['organizationunitname'] 439 | accountrole = 'OrganizationAccountAccessRole' 440 | stackname = os.environ['stackname'] 441 | stackregion = os.environ['stackregion'] 442 | adminusername = os.environ['adminusername'] 443 | adminpassword = os.environ['adminpassword'] 444 | sourcebucket = os.environ['sourcebucket'] 445 | baselinetemplate = os.environ['baselinetemplate'] 446 | AZ1Name = os.environ['AZ1Name'] 447 | AZ2Name = os.environ['AZ2Name'] 448 | VPCCIDR = os.environ['VPCCIDR'] 449 | SubnetAPublicCIDR = os.environ['SubnetAPublicCIDR'] 450 | SubnetBPublicCIDR = os.environ['SubnetBPublicCIDR'] 451 | SubnetAPrivateCIDR = os.environ['SubnetAPrivateCIDR'] 452 | SubnetBPrivateCIDR = os.environ['SubnetBPrivateCIDR'] 453 | VPCName = os.environ['VPCName'] 454 | access_to_billing = "DENY" 455 | scp = None 456 | #account_id = None 457 | 458 | 459 | RegiontoAZMap = { 460 | "ap-northeast-1": ["ap-northeast-1a","ap-northeast-1c"], 461 | "ap-northeast-2": [ "ap-northeast-2a","ap-northeast-2c"], 462 | "ap-northeast-3": [ "ap-northeast-3a" ], 463 | "ap-south-1": [ "ap-south-1a","ap-south-1b"], 464 | "ap-southeast-1": [ "ap-southeast-1a","ap-southeast-1b","ap-southeast-1c"], 465 | "ap-southeast-2": [ "ap-southeast-2a","ap-southeast-2b","ap-southeast-2c"], 466 | "ca-central-1": ["ca-central-1a","ca-central-1b"], 467 | "eu-central-1": ["eu-central-1a","eu-central-1b","eu-central-1c"], 468 | "eu-west-1": [ "eu-west-1a","eu-west-1b","eu-west-1c"], 469 | "eu-west-2": [ "eu-west-2a","eu-west-2b","eu-west-2c"], 470 | "eu-west-3": [ "eu-west-3a","eu-west-3b","eu-west-3c"], 471 | "sa-east-1": [ "sa-east-1a","sa-east-1c"], 472 | "us-east-1": [ "us-east-1a","us-east-1b","us-east-1c","us-east-1d","us-east-1e","us-east-1f"], 473 | "us-east-2": [ "us-east-2a","us-east-2b","us-east-2c"], 474 | "us-west-1": [ "us-west-1b","us-west-1c"], 475 | "us-west-2": [ "us-west-2a","us-west-2b","us-west-2c"] 476 | } 477 | 478 | if (event['RequestType'] == 'Create'): 479 | selfinvoke(event,'Wait') 480 | top_level_account = event['ServiceToken'].split(':')[4] 481 | org_client = get_client('organizations') 482 | 483 | preferred_az_list = RegiontoAZMap[stackregion] 484 | if(AZ1Name in preferred_az_list and AZ2Name in preferred_az_list): 485 | print("The selected AZs and the stackregion are compatible.") 486 | else: 487 | print("{} and {} are not in the selected stack region: {}".format(AZ1Name,AZ2Name,stackregion)) 488 | # delete_respond_cloudformation(event, "FAILED", { 489 | # #"Message":"AZ1Name: "+AZ1Name+" AZ2Name: "+AZ2Name+" is not from the selected region:"+stackregion+".Stack Regions and VPC AZ's should be in the same AWS region."}) 490 | # "Message" : "The selected AWS Region and AZs wont match.The AZs selected for deploying the VPC should be in the "+stackregion+".", 491 | # }) 492 | # if(not accountname or not accountemail or not newrole or not newrolepolicy or not organization_unit_name or not stackname or not stackregion or not adminusername or not adminpassword or not sourcebucket or not baselinetemplate or not AZ1Name or not AZ2Name or not VPCCIDR or not SubnetAPublicCIDR or not SubnetBPublicCIDR or not SubnetAPrivateCIDR or not SubnetBPrivateCIDR): 493 | # print("Please provide all parameter values and try again.") 494 | # delete_respond_cloudformation(event, "FAILED", {"Message":"Provide all the parameters and launch the product again"}) 495 | try: 496 | list_roots_response = org_client.list_roots() 497 | #print(list_roots_response) 498 | root_id = list_roots_response['Roots'][0]['Id'] 499 | except: 500 | root_id = "Error" 501 | 502 | if root_id is not "Error": 503 | print("Creating new account: " + accountname + " (" + accountemail + ")") 504 | 505 | ### List the available AWS Oranization OU's 506 | #if(organization_unit_name is not None): 507 | #(organization_unit_name,organization_unit_id) = get_ou_name_id(root_id,organization_unit_name) 508 | (create_account_response,account_id) = create_account(event,accountname,accountemail,accountrole,access_to_billing,scp,root_id) 509 | #print(create_account_response) 510 | print("Created acount:{}\n".format(account_id)) 511 | 512 | 513 | #attach_policy_response = org_client.attach_policy(PolicyId=scp_id,TargetId=account_id) 514 | credentials = assume_role(account_id, accountrole) 515 | 516 | #print("Deploying resources from " + templatefile + " as " + stackname + " in " + stackregion) 517 | # template = get_template(sourcebucket,baselinetemplate) 518 | # stack = deploy_resources(credentials, template, stackname, stackregion, adminusername, adminpassword,account_id) 519 | # print(stack) 520 | 521 | ec2_client = get_client('ec2') 522 | try: 523 | custom_vpc_id = create_custom_vpc(credentials,stackregion,AZ1Name,AZ2Name,VPCCIDR,SubnetAPublicCIDR,SubnetBPublicCIDR,SubnetAPrivateCIDR,SubnetBPrivateCIDR,VPCName) 524 | except botocore.exceptions.ClientError as e: 525 | print("There might be an error creating the custom VPC. Error : {}".format(e)) 526 | try: 527 | newrole_arn = create_newrole(newrole,top_level_account,credentials,newrolepolicy) 528 | except botocore.exceptions.ClientError as e: 529 | print("Error creating the specified role. Error : {}".format(e)) 530 | newrole_arn = "arn:aws:iam::"+account_id+":role/"+newrole 531 | 532 | print(newrole_arn) 533 | template = get_template(sourcebucket,baselinetemplate) 534 | stack = deploy_resources(credentials, template, stackname, stackregion, adminusername, adminpassword,account_id,newrole_arn) 535 | print(stack) 536 | 537 | print("Resources deployment for account " + account_id + " (" + accountemail + ") complete !!") 538 | 539 | regions = [] 540 | regions_response = ec2_client.describe_regions() 541 | for i in range(0,len(regions_response['Regions'])): 542 | regions.append(regions_response['Regions'][i]['RegionName']) 543 | for r in regions: 544 | try: 545 | #print('In the VPC deletion block - {}'.format(r)) 546 | delete_vpc_response = delete_default_vpc(credentials,r) 547 | except botocore.exceptions.ClientError as e: 548 | print("An error occured while deleting Default VPC in {}. Error: {}".format(r,e)) 549 | i+=1 550 | 551 | root_id = client.list_roots().get('Roots')[0].get('Id') 552 | #print(root_id) 553 | #print('Outside try block - {}'.format(organization_unit_name)) 554 | 555 | if(organization_unit_name!='None'): 556 | try: 557 | (organization_unit_name,organization_unit_id) = get_ou_name_id(root_id,organization_unit_name) 558 | move_response = org_client.move_account(AccountId=account_id,SourceParentId=root_id,DestinationParentId=organization_unit_id) 559 | 560 | except Exception as ex: 561 | template = "An exception of type {0} occurred. Arguments:\n{1!r} " 562 | message = template.format(type(ex).__name__, ex.args) 563 | print(message) 564 | if scp is not None: 565 | attach_policy_response = client.attach_policy(PolicyId=scp, TargetId=account_id) 566 | print("Attach policy response "+str(attach_policy_response)) 567 | #respond_cloudformation(event, "SUCCESS", { "Message": "Account Created! URL : https://" +account_id+".signin.aws.amazon.com/console", "AccountID" : account_id, "LoginURL" : "https://console.aws.amazon.com", "Username" : adminusername }) 568 | respond_cloudformation(event, "SUCCESS", { "Message": "Account Created!", 569 | "LoginURL" : "https://"+account_id+".signin.aws.amazon.com/console?region="+stackregion+"#", 570 | "AccountID" : account_id, 571 | "Username" : adminusername, 572 | "Role" : newrole, 573 | "Stackregion": stackregion }) 574 | else: 575 | print("Cannot access the AWS Organization ROOT. Contact the master account Administrator for more details.") 576 | #sys.exit(1) 577 | delete_respond_cloudformation(event, "FAILED", "Cannot access the AWS Organization ROOT. Contact the master account Administrator for more details.Deleting Lambda Function.") 578 | 579 | if(event['RequestType'] == 'Update'): 580 | print("Template in Update Status") 581 | respond_cloudformation(event, "SUCCESS", { "Message": "Resource update successful!" }) 582 | #respond_cloudformation(event, "SUCCESS", { "Message": "Account Created!","Login URL : "https://" +account_id+".signin.aws.amazon.com/console", "AccountID" : account_id, "Username" : adminusername, "Role" : newrole }) 583 | 584 | 585 | # elif(event['RequestType'] == 'Wait'): 586 | # # account_status = 'IN_PROGRESS' 587 | # # create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) 588 | # # while account_status == 'IN_PROGRESS': 589 | # # create_account_status_response = client.describe_create_account_status(CreateAccountRequestId=create_account_response.get('CreateAccountStatus').get('Id')) 590 | # # print("Create In Progress. Create Account Status Response : {} \n".format(create_account_status_response)) 591 | # # account_status = create_account_status_response.get('CreateAccountStatus').get('State') 592 | # # if account_status == 'SUCCEEDED': 593 | # # account_id = create_account_status_response.get('CreateAccountStatus').get('AccountId') 594 | # # print("Account Creation SUCCEEDED. Create Account Status Response for account URL : {}\n".format(create_account_status_response)) 595 | # # elif account_status == 'FAILED': 596 | # # print("Account creation failed: " + create_account_status_response.get('CreateAccountStatus').get('FailureReason')) 597 | # time.sleep(30) 598 | # respond_cloudformation(event, "Aborted", { "Message": "Retuned back form the wait condition !!" }) 599 | # exit() 600 | 601 | 602 | elif(event['RequestType'] == 'Delete'): 603 | try: 604 | delete_respond_cloudformation(event, "SUCCESS", "Delete Request Initiated. Deleting Lambda Function.") 605 | except: 606 | print("Couldnt initiate delete response.") 607 | 608 | -------------------------------------------------------------------------------- /AccountCreationLambda.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/account-factory/eaa7762a387a0af92bea23270fa9846955ea39b0/AccountCreationLambda.zip -------------------------------------------------------------------------------- /Accountbaseline.yml: -------------------------------------------------------------------------------- 1 | # /* 2 | # * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # * 4 | # * Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # * software and associated documentation files (the "Software"), to deal in the Software 6 | # * without restriction, including without limitation the rights to use, copy, modify, 7 | # * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # * permit persons to whom the Software is furnished to do so. 9 | # * 10 | # * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # */ 17 | 18 | 19 | 20 | 21 | AWSTemplateFormatVersion: 2010-09-09 22 | Description: Baseline IAM resources for new account 23 | 24 | Parameters: 25 | AdminUsername: 26 | Type: String 27 | Description: Username for the Admin user 28 | AdminPassword: 29 | Type: String 30 | NoEcho: true 31 | MinLength: 1 32 | Description: Password for the Admin user 33 | NewRoleArn: 34 | Type: String 35 | Description: ARN of the NewRole 36 | 37 | 38 | Resources: 39 | Admin: 40 | Type: 'AWS::IAM::User' 41 | Properties: 42 | UserName: !Ref AdminUsername 43 | Path: / 44 | LoginProfile: 45 | Password: !Ref AdminPassword 46 | 47 | Administrators: 48 | Type: 'AWS::IAM::Group' 49 | Properties: 50 | ManagedPolicyArns: 51 | - "arn:aws:iam::aws:policy/AdministratorAccess" 52 | GroupName: Administrators 53 | Path: / 54 | 55 | 56 | AddUserToGroup: 57 | Type: 'AWS::IAM::UserToGroupAddition' 58 | Properties: 59 | GroupName: !Ref Administrators 60 | Users: 61 | - !Ref Admin 62 | 63 | EC2LinuxProduct: 64 | Type: "AWS::ServiceCatalog::CloudFormationProduct" 65 | Properties: 66 | AcceptLanguage: "en" 67 | Description: "This product builds one Amazon Linux EC2 instance and create a SSM patch baseline, maintenance window, and patch task to scan for and install operating system updates the EC2 instance." 68 | Distributor: "Amazon" 69 | Name: "Amazon Elastic Compute Cloud (EC2) Linux" 70 | Owner: "IT Services" 71 | SupportEmail: "support@yourcompany.com" 72 | SupportUrl: "https://www.amazon.com" 73 | SupportDescription: "Support Description" 74 | ProvisioningArtifactParameters: 75 | - 76 | Description: "June 2018" 77 | Name: "June 2018" 78 | Info: 79 | LoadTemplateFromURL : "https://raw.githubusercontent.com/aws-samples/aws-service-catalog-reference-architectures/master/ec2/sc-ec2-linux-ra.json" 80 | 81 | EC2WindowsProduct: 82 | Type: "AWS::ServiceCatalog::CloudFormationProduct" 83 | Properties: 84 | AcceptLanguage: "en" 85 | Description: "This product builds one Amazon Windows EC2 instance and create a SSM patch baseline, maintenance window, and patch task to scan for and install operating system updates the EC2 instance." 86 | Distributor: "Amazon" 87 | Name: "Amazon Elastic Compute Cloud (EC2) Windows" 88 | Owner: "IT Services" 89 | SupportEmail: "support@yourcompany.com" 90 | SupportUrl: "https://www.amazon.com" 91 | SupportDescription: "Support Description" 92 | ProvisioningArtifactParameters: 93 | - 94 | Description: "June 2018" 95 | Name: "June 2018" 96 | Info: 97 | LoadTemplateFromURL : "https://raw.githubusercontent.com/aws-samples/aws-service-catalog-reference-architectures/master/ec2/sc-ec2-windows-ra.json" 98 | 99 | VPCProduct: 100 | Type: "AWS::ServiceCatalog::CloudFormationProduct" 101 | Properties: 102 | AcceptLanguage: "en" 103 | Description: "This product builds one Amazon VPC with Public Subnets, Private Subnets, Route Tables, NAT Gateway and Internet Gateway" 104 | Distributor: "Amazon" 105 | Name: "Amazon VPC" 106 | Owner: "IT Services" 107 | SupportEmail: "support@yourcompany.com" 108 | SupportUrl: "https://www.amazon.com" 109 | SupportDescription: "Support Description" 110 | ProvisioningArtifactParameters: 111 | - 112 | Description: "June 2018" 113 | Name: "June 2018" 114 | Info: 115 | LoadTemplateFromURL : "https://raw.githubusercontent.com/aws-samples/aws-service-catalog-reference-architectures/master/vpc/sc-vpc-ra.json" 116 | 117 | EMRProduct: 118 | Type: "AWS::ServiceCatalog::CloudFormationProduct" 119 | Properties: 120 | AcceptLanguage: "en" 121 | Description: "EMR product creates an Amazon Elastic MapReduce cluster in the VPC and Subnets selected by the end user. A remote access security group is also created to allow for a bastion host to connect to the instances used by EMR via SSH." 122 | Distributor: "Amazon" 123 | Name: "EMR Product" 124 | Owner: "IT Services" 125 | SupportEmail: "support@yourcompany.com" 126 | SupportUrl: "https://www.amazon.com" 127 | SupportDescription: "Support Description" 128 | ProvisioningArtifactParameters: 129 | - 130 | Description: "June 2018" 131 | Name: "June 2018" 132 | Info: 133 | LoadTemplateFromURL: "https://raw.githubusercontent.com/aws-samples/aws-service-catalog-reference-architectures/master/emr/sc-emr-ra.json" 134 | 135 | RDSMySQLProduct: 136 | Type: "AWS::ServiceCatalog::CloudFormationProduct" 137 | Properties: 138 | AcceptLanguage: "en" 139 | Description: "This product allows the user to launch a RDS MySQL DB as either single instance databases or multi-availability zone databases." 140 | Distributor: "Amazon" 141 | Name: "RDS MySQL Product" 142 | Owner: "IT Services" 143 | SupportEmail: "support@yourcompany.com" 144 | SupportUrl: "https://www.amazon.com" 145 | SupportDescription: "Support Description" 146 | ProvisioningArtifactParameters: 147 | - 148 | Description: "June 2018" 149 | Name: "June 2018" 150 | Info: 151 | LoadTemplateFromURL: "https://raw.githubusercontent.com/aws-samples/aws-service-catalog-reference-architectures/master/rds/sc-rds-mssql-ra.json" 152 | 153 | RDSMariaDBProduct: 154 | Type: "AWS::ServiceCatalog::CloudFormationProduct" 155 | Properties: 156 | AcceptLanguage: "en" 157 | Description: "This product allows the user to launch a RDS Maria DB as either single instance databases or multi-availability zone databases." 158 | Distributor: "Amazon" 159 | Name: RDS MariaDB Product 160 | Owner: "IT Services" 161 | SupportEmail: "support@yourcompany.com" 162 | SupportUrl: "https://www.amazon.com" 163 | SupportDescription: "Support Description" 164 | ProvisioningArtifactParameters: 165 | - 166 | Description: "June 2018" 167 | Name: "June 2018" 168 | Info: 169 | LoadTemplateFromURL: "https://raw.githubusercontent.com/aws-samples/aws-service-catalog-reference-architectures/master/rds/sc-rds-mariadb-ra.json" 170 | 171 | RDSPostGreSqlProduct: 172 | Type: "AWS::ServiceCatalog::CloudFormationProduct" 173 | Properties: 174 | AcceptLanguage: "en" 175 | Description: "This product allows the user to launch a RDS POSTGRE SQL as either single instance databases or multi-availability zone databases." 176 | Distributor: "Amazon" 177 | Name: "RDS POSTGRE SQL Product" 178 | Owner: "IT Services" 179 | SupportEmail: "support@yourcompany.com" 180 | SupportUrl: "https://www.amazon.com" 181 | SupportDescription: "Support Description" 182 | ProvisioningArtifactParameters: 183 | - 184 | Description: "June 2018" 185 | Name: "June 2018" 186 | Info: 187 | LoadTemplateFromURL: "https://raw.githubusercontent.com/aws-samples/aws-service-catalog-reference-architectures/master/rds/sc-rds-postgresql-ra.json" 188 | 189 | S3BucketProduct: 190 | Type: "AWS::ServiceCatalog::CloudFormationProduct" 191 | Properties: 192 | AcceptLanguage: "en" 193 | Description: "This product allows the user to create an S3 Bucket locking down the access to a specific CIDR." 194 | Distributor: "Amazon" 195 | Name: "S3 Bucket Product" 196 | Owner: "IT Services" 197 | SupportEmail: "support@yourcompany.com" 198 | SupportUrl: "https://www.amazon.com" 199 | SupportDescription: "Support Description" 200 | ProvisioningArtifactParameters: 201 | - 202 | Description: "June 2018" 203 | Name: "June 2018" 204 | Info: 205 | LoadTemplateFromURL: "https://raw.githubusercontent.com/aws-samples/aws-service-catalog-reference-architectures/master/s3/sc-s3-cidr-ra.json" 206 | 207 | BaselinePortfolio: 208 | Type: "AWS::ServiceCatalog::Portfolio" 209 | Properties: 210 | DisplayName: "FirstPortfolio" 211 | AcceptLanguage: "en" 212 | ProviderName: "Amazon" 213 | 214 | LinuxAssociation: 215 | Type: "AWS::ServiceCatalog::PortfolioProductAssociation" 216 | Properties: 217 | ProductId: !Ref EC2LinuxProduct 218 | PortfolioId: !Ref BaselinePortfolio 219 | 220 | WindowsAssociation: 221 | Type: "AWS::ServiceCatalog::PortfolioProductAssociation" 222 | Properties: 223 | ProductId: !Ref EC2WindowsProduct 224 | PortfolioId: !Ref BaselinePortfolio 225 | 226 | VPCAssociation: 227 | Type: "AWS::ServiceCatalog::PortfolioProductAssociation" 228 | Properties: 229 | ProductId: !Ref VPCProduct 230 | PortfolioId: !Ref BaselinePortfolio 231 | 232 | EMRAssociation: 233 | Type: "AWS::ServiceCatalog::PortfolioProductAssociation" 234 | Properties: 235 | ProductId: !Ref EMRProduct 236 | PortfolioId: !Ref BaselinePortfolio 237 | 238 | RDSMySQLAssociation: 239 | Type: "AWS::ServiceCatalog::PortfolioProductAssociation" 240 | Properties: 241 | ProductId: !Ref RDSMySQLProduct 242 | PortfolioId: !Ref BaselinePortfolio 243 | 244 | RDSMariaDBAssociation: 245 | Type: "AWS::ServiceCatalog::PortfolioProductAssociation" 246 | Properties: 247 | ProductId: !Ref RDSMariaDBProduct 248 | PortfolioId: !Ref BaselinePortfolio 249 | 250 | RDSPOSTGRESQLAssociation: 251 | Type: "AWS::ServiceCatalog::PortfolioProductAssociation" 252 | Properties: 253 | ProductId: !Ref RDSPostGreSqlProduct 254 | PortfolioId: !Ref BaselinePortfolio 255 | 256 | S3BucketAssociation: 257 | Type: "AWS::ServiceCatalog::PortfolioProductAssociation" 258 | Properties: 259 | ProductId: !Ref S3BucketProduct 260 | PortfolioId: !Ref BaselinePortfolio 261 | 262 | PortfolioPrincipalAssociationforAdmin: 263 | Type: "AWS::ServiceCatalog::PortfolioPrincipalAssociation" 264 | Properties: 265 | PrincipalARN: !GetAtt Admin.Arn 266 | PortfolioId: !Ref BaselinePortfolio 267 | PrincipalType: IAM 268 | 269 | 270 | PortfolioPrincipalAssociationforRole: 271 | Type: "AWS::ServiceCatalog::PortfolioPrincipalAssociation" 272 | Properties: 273 | PrincipalARN: !Ref NewRoleArn 274 | PortfolioId: !Ref BaselinePortfolio 275 | PrincipalType: IAM 276 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/account-factory/issues), or [recently closed](https://github.com/aws-samples/account-factory/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/account-factory/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/account-factory/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Account Factory 2 | 3 | This code enables end-users to create AWS accounts and setup a custom base configuration quickly in an automated way. 4 | 5 | ## License Summary 6 | 7 | This sample code is made available under a modified MIT license. See the LICENSE file. 8 | -------------------------------------------------------------------------------- /accountbuilder.yml: -------------------------------------------------------------------------------- 1 | # /* 2 | # * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # * 4 | # * Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # * software and associated documentation files (the "Software"), to deal in the Software 6 | # * without restriction, including without limitation the rights to use, copy, modify, 7 | # * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # * permit persons to whom the Software is furnished to do so. 9 | # * 10 | # * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # */ 17 | 18 | 19 | 20 | AWSTemplateFormatVersion: 2010-09-09 21 | Description: Account Builder Template. 22 | Metadata: 23 | 'AWS::CloudFormation::Interface': 24 | ParameterGroups: 25 | - Label: 26 | default: Parameters for the new created account 27 | Parameters: 28 | - accountemail 29 | - organizationunitname 30 | - accountname 31 | - adminusername 32 | - adminpassword 33 | - stackname 34 | - stackregion 35 | - sourcebucket 36 | - baselinetemplate 37 | - newrole 38 | - newrolepolicy 39 | - Label: 40 | default: Region Configuration 41 | Parameters: 42 | - AZ1Name 43 | - AZ2Name 44 | - Label: 45 | default: VPC Configuration 46 | Parameters: 47 | - VPCCIDR 48 | - VPCName 49 | - Label: 50 | default: Public Subnet Configuration 51 | Parameters: 52 | - SubnetAPublicCIDR 53 | - SubnetBPublicCIDR 54 | - Label: 55 | default: Private Subnet Configuration 56 | Parameters: 57 | - SubnetAPrivateCIDR 58 | - SubnetBPrivateCIDR 59 | 60 | Parameters: 61 | 62 | accountemail: 63 | Description: "Account Email address" 64 | Type: String 65 | AllowedPattern: ".+" 66 | ConstraintDescription: "Must provide a valid email address" 67 | 68 | accountname: 69 | Description: "Name of the new AWS Account Name" 70 | Type: String 71 | AllowedPattern: ".+" 72 | ConstraintDescription: "Provide the account name" 73 | 74 | organizationunitname: 75 | Description: "Name of the organization unit to which the account should be moved to." 76 | Type: String 77 | Default: "None" 78 | AllowedPattern: ".+" 79 | 80 | 81 | newrole: 82 | Description: "Role that needs to be created in the newly built account. Use OrganizationAccountAccessRole for assuming roles into the new account,in case this role creation fails." 83 | Default : "NewAccountAdminRole" 84 | Type: String 85 | AllowedPattern: ".+" 86 | 87 | 88 | newrolepolicy: 89 | Description: "Provide the policy to be attached to the above newrole. Json is only the valid format." 90 | Default : '{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"*","Resource":"*"}}' 91 | Type: String 92 | AllowedPattern: ".+" 93 | 94 | adminusername: 95 | Description: "An IAM user to be created in the created account." 96 | Type: String 97 | AllowedPattern: ".+" 98 | ConstraintDescription: "Must provide a username" 99 | 100 | adminpassword: 101 | Description: "Password for the IAM user in the created accout." 102 | Type: String 103 | NoEcho: True 104 | AllowedPattern: ".+" 105 | ConstraintDescription: "Must provide a password for the username" 106 | 107 | stackname: 108 | Description: "Name given to the stack deployed in the created account." 109 | Type: String 110 | AllowedPattern: ".+" 111 | #AllowedPattern: "^[a-zA-z0-9-]*" 112 | ConstraintDescription: "Must contain only letters, numbers, dashes and start with an alpha character" 113 | 114 | stackregion: 115 | Description: "Region for deploying the baseline template in the created account" 116 | Default: "us-west-2" 117 | Type: String 118 | AllowedPattern: ".+" 119 | AllowedValues: ["us-east-2","us-east-1","us-west-1","us-west-2","ap-northeast-1","ap-northeast-2","ap-northeast-3","ap-south-1","ap-southeast-1","ap-southeast-2","ca-central-1","cn-north-1","cn-northwest-1","eu-central-1","eu-west-1","eu-west-2","eu-west-3","sa-east-1"] 120 | ConstraintDescription: "Must be a valid AWS region" 121 | 122 | sourcebucket: 123 | Description: "Bucket holding the baseline template file" 124 | Type: String 125 | AllowedPattern: ".+" 126 | 127 | 128 | baselinetemplate: 129 | Description: "Baseline template to be deployed in the created account." 130 | Type: String 131 | AllowedPattern: ".+" 132 | 133 | AZ1Name: 134 | Description: Availability Zone 1 Name in Region 135 | Type: String 136 | AllowedPattern: ".+" 137 | AllowedValues: ["ap-south-1a","ap-south-1b","eu-west-3a","eu-west-3b","eu-west-3c","eu-west-2a","eu-west-2b","eu-west-2c","eu-west-1a","eu-west-1b","eu-west-1c","ap-northeast-3a","ap-northeast-2a","ap-northeast-2c","ap-northeast-1a","ap-northeast-1c","ap-northeast-1d","sa-east-1a","sa-east-1c","ca-central-1a","ca-central-1b","ap-southeast-1a","ap-southeast-1b","ap-southeast-1c","ap-southeast-2a","ap-southeast-2b","ap-southeast-2c","eu-central-1a","eu-central-1b","eu-central-1c","us-east-1a","us-east-1b","us-east-1c","us-east-1d","us-east-1e","us-east-1f","us-east-2a","us-east-2b","us-east-2c","us-west-1b","us-west-1c","us-west-2a","us-west-2b","us-west-2c"] 138 | 139 | AZ2Name: 140 | Description: Availability Zone 2 Name in Region 141 | Type: String 142 | AllowedPattern: ".+" 143 | AllowedValues: ["ap-south-1a","ap-south-1b","eu-west-3a","eu-west-3b","eu-west-3c","eu-west-2a","eu-west-2b","eu-west-2c","eu-west-1a","eu-west-1b","eu-west-1c","ap-northeast-3a","ap-northeast-2a","ap-northeast-2c","ap-northeast-1a","ap-northeast-1c","ap-northeast-1d","sa-east-1a","sa-east-1c","ca-central-1a","ca-central-1b","ap-southeast-1a","ap-southeast-1b","ap-southeast-1c","ap-southeast-2a","ap-southeast-2b","ap-southeast-2c","eu-central-1a","eu-central-1b","eu-central-1c","us-east-1a","us-east-1b","us-east-1c","us-east-1d","us-east-1e","us-east-1f","us-east-2a","us-east-2b","us-east-2c","us-west-1b","us-west-1c","us-west-2a","us-west-2b","us-west-2c"] 144 | 145 | VPCCIDR: 146 | Description: CIDR block for the VPC 147 | Type: String 148 | Default: 10.229.0.0/16 149 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 150 | AllowedPattern: >- 151 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ 152 | 153 | SubnetAPublicCIDR: 154 | Description: CIDR block for the public subnet in availability zone 155 | Type: String 156 | Default: 10.229.10.0/24 157 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 158 | AllowedPattern: >- 159 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ 160 | 161 | SubnetBPublicCIDR: 162 | Description: CIDR block for the public subnet in availability zone 163 | Type: String 164 | Default: 10.229.20.0/24 165 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 166 | AllowedPattern: >- 167 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ 168 | 169 | SubnetAPrivateCIDR: 170 | Description: CIDR block for the private subnet in availability zone 171 | Type: String 172 | Default: 10.229.30.0/24 173 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 174 | AllowedPattern: >- 175 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ 176 | 177 | SubnetBPrivateCIDR: 178 | Description: CIDR block for the private subnet in availability zone 179 | Type: String 180 | Default: 10.229.40.0/24 181 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 182 | AllowedPattern: >- 183 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ 184 | 185 | VPCName: 186 | Description: Name for the VPC 187 | Type: String 188 | Default: Custom_VPC 189 | AllowedPattern: ".+" 190 | ConstraintDescription: Provide the name for the VPC. 191 | 192 | Resources: 193 | AccountBuilderLambda: 194 | Type: "AWS::Lambda::Function" 195 | Properties: 196 | Handler: "AccountCreationLambda.main" 197 | #Handler: "index.main" 198 | Runtime: "python3.6" 199 | Role: !GetAtt LambdaExecuteRole.Arn 200 | Timeout: 600 201 | TracingConfig: 202 | Mode: "Active" 203 | Code: 204 | S3Bucket: !Ref sourcebucket 205 | S3Key: "AccountCreationLambda.zip" 206 | Environment: 207 | Variables: 208 | 'accountemail' : !Ref accountemail 209 | 'accountname' : !Ref accountname 210 | 'newrolepolicy': !Ref newrolepolicy 211 | 'newrole': !Ref 'newrole' 212 | 'organizationunitname': !Ref 'organizationunitname' 213 | 'stackname' : !Ref stackname 214 | 'stackregion' : !Ref stackregion 215 | 'adminusername' : !Ref adminusername 216 | 'adminpassword' : !Ref adminpassword 217 | 'sourcebucket' : !Ref sourcebucket 218 | 'baselinetemplate': !Ref baselinetemplate 219 | 'AZ1Name' : !Ref AZ1Name 220 | 'AZ2Name' : !Ref AZ2Name 221 | 'VPCCIDR' : !Ref VPCCIDR 222 | 'SubnetAPublicCIDR' : !Ref SubnetAPublicCIDR 223 | 'SubnetBPublicCIDR' : !Ref SubnetBPublicCIDR 224 | 'SubnetAPrivateCIDR' : !Ref SubnetAPrivateCIDR 225 | 'SubnetBPrivateCIDR' : !Ref SubnetBPrivateCIDR 226 | 'VPCName' : !Ref VPCName 227 | 228 | 229 | LambdaExecuteRole: 230 | Type: "AWS::IAM::Role" 231 | Properties: 232 | AssumeRolePolicyDocument: 233 | Version: "2012-10-17" 234 | Statement: 235 | Effect: "Allow" 236 | Principal: 237 | Service: 238 | - "lambda.amazonaws.com" 239 | Action: 240 | - "sts:AssumeRole" 241 | Path: "/" 242 | Policies: 243 | - PolicyName: LambdaAccessRole 244 | PolicyDocument: 245 | Version: '2012-10-17' 246 | Statement: 247 | Effect: Allow 248 | Action: "*" 249 | Resource: "*" 250 | 251 | TriggerLambda: 252 | Type: "Custom::TriggerLambda" 253 | DeletionPolicy: Retain 254 | DependsOn: 255 | - AccountBuilderLambda 256 | - LambdaExecuteRole 257 | Properties: 258 | ServiceToken: !GetAtt AccountBuilderLambda.Arn 259 | 260 | 261 | Outputs: 262 | Message: 263 | Description: Execution Status 264 | Value: !GetAtt 'TriggerLambda.Message' 265 | 266 | AccountID: 267 | Description: ID of the new account 268 | Value: !GetAtt 'TriggerLambda.AccountID' 269 | 270 | Username: 271 | Description: UserName to Login. 272 | Value: !GetAtt 'TriggerLambda.Username' 273 | 274 | LoginURL: 275 | Description: Login url 276 | Value: !GetAtt 'TriggerLambda.LoginURL' 277 | 278 | # VPCStatus: 279 | # Description: Status of the new account default VPC 280 | # Value: !GetAtt 'TriggerNetworkLambda.VPCStatus' 281 | 282 | # VPCId: 283 | # Description: VPC ID of the newly created VPC 284 | # Value: !GetAtt 'TriggerNetworkLambda.NewVPCId' 285 | --------------------------------------------------------------------------------