Automate external system updates for AWS Control Tower account creation

Enterprises often use AWS Control Tower for managing and vending accounts at scale. With a large number of accounts getting provisioned, you might encounter the common problem of tracking, cost tagging, and baselining the accounts using external systems at scale. This post demonstrates how AWS Managed Services (AMS) have used Amazon EventBridge and AWS Lambda with AWS Control Tower to solve this for our customers.

Solution overview

A customer using AMS raised the question: how can AMS be notified automatically to initiate the account onboarding of a newly vended account without manually notifying the assigned AMS delivery manager? This post demonstrates the solution built to meet that challenge. We use AMS onboarding as a use case to demonstrate the capability, which can be extended to update other systems such as an external CMDB or inventory system, notify another managed service provider, or notify an external billing or chargeback mechanism.

The following diagram illustrates the automated workflow from AWS Control Tower account creation to AMS onboarding request.

The workflow consists of the following steps:

  1. A request is made to vend a new account using AWS Control Tower.
  2. EventBridge uses an event rule to monitor for a CreateManagedAccount API call.
  3. When the rule is triggered, it calls a Lambda function, with the required details like account ID and account name.
  4. The Lambda function triggers an AWS Support case to AMS to initiate the onboarding process for the account, with the required request parameters.
  5. If the Lambda function encounters any errors, it automatically sends failure notifications to an Amazon Simple Notification Service (Amazon SNS) topic for immediate alerting and operational visibility.

Prerequisites

This solution requires a basic understanding of Python and Lambda to deploy this solution and modify the Lambda function to meet your use case. If you deploy this solution to use with AMS, an AWS Enterprise Support subscription is required; for other use cases, AWS Business Support provides the required access to Support API for creating a support case.

For this post, we use the following versions:

  • AWS Control Tower 3.x
  • Python 3.12 or later
  • Boto3 1.37.0 or later

Implement the solution

The implementation shown in this post uses AWS CloudFormation, but you can use another infrastructure as code (IaC) tool.

The provided CloudFormation template includes an AWS Identity and Access Management (IAM) role that a Lambda function assumes to have the rights to open tickets with AWS Support. The function creates a ticket to demonstrate the capability of the solution without having customer specifics within it. For a real-world use case, this code would confirm the new account has a set of IAM roles created and meets other security standards, look for tags, and use that information to populate the data back to the appropriate systems.

The CloudFormation template also creates the EventBridge rule and the permissions required for it to invoke the Lambda function.

The following code is broken down into snippets to help with clarity and explainability. For implementation, combine these into a single CloudFormation file and use that as a single deployment stack.

The first step is to define the Lambda runtime. Note the Python default version here, which might vary based on your use case:

# CloudFormation template version declaration
AWSTemplateFormatVersion: '2010-09-09'

# Template description explaining the purpose
Description: >-
  CloudFormation template to create EventBridge rule for Control Tower 
  CreateManagedAccount API that will create a case to the AMS Support 
  to start AMS Onboarding

# Template parameters section
Parameters:
  # Python runtime version parameter for Lambda function
  LambdaRuntimePython:
    Type: String
    Default: python3.13
    Description: Lambda function runtime 

The next step is to add the resources needed, starting with the IAM role to grant permissions to the Lambda function. For simplicity, the example code focuses on the ability to open a support case and allow for logging. You should customize this code with the appropriate permissions for what you want to do, while retaining the principles of least privilege. We also create the log group to capture failures and an SNS topic to send these failures to for quick notifications. It is best practice to encrypt logs and SNS topics so there is a section to create keys in AWS Key Management Service (AWS KMS) as well.

Resources:
  # IAM Role for Lambda
  CreateSupportCaseLambdaRole:
    Type: AWS::IAM::Role
    Properties:     
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  # IAM Policy
  CreateSupportCaseLambdaRolePolicy:
    Type: 'AWS::IAM::RolePolicy'
    Properties:
      PolicyName: !Sub '${AWS::StackName}-CreateSupportCaseLambdaRolePolicy'
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - support:CreateCase
            Resource: '*'
          - Effect: Allow
            Action:
              - sns:Publish
            Resource: !Ref OnboardingFailureTopic
          - Effect: Allow
            Action:
              - kms:Decrypt
              - kms:GenerateDataKey
            Resource: !GetAtt OnboardingKMSKey.Arn
      RoleName: !Ref CreateSupportCaseLambdaRole
  CreateSupportCaseFunctionLogGroup:
    DeletionPolicy: Retain
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: 'amsCreateSupportCaseFunction-Logs'
      RetentionInDays: 30
      KmsKeyId: !GetAtt OnboardingKMSKey.Arn
    # SNS Topic for Lambda failures
  OnboardingFailureTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: 'amsOnboardingFailureNotifications'
      DisplayName: 'AMS Onboarding Lambda Failure Notifications'
      KmsMasterKeyId: !Ref OnboardingKMSKey
# KMS Key for encryption
  OnboardingKMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: 'KMS key for AMS onboarding resources encryption'
      EnableKeyRotation: true
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Allow CloudWatch Logs
            Effect: Allow
            Principal:
              Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:DescribeKey'
            Resource: '*'
  OnboardingKMSKeyAlias:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: 'alias/ams-onboarding-encryption'      
      TargetKeyId: !Ref OnboardingKMSKey

The next resource to be added is the Lambda function. The simplified code in this example shows how to create an AWS Support case to AMS, which can be replaced or updated to call other APIs.

The key actions are to know the account ID for the account created and other useful information, such as the account name, and then submitting a ticket, which triggers an AMS onboarding workflow. Within the function, we handle failure conditions by logging them and sending them to an SNS topic for quick notification of the failure.

You can extend this solution to meet your specific needs.

  # Lambda Function
  CreateSupportCaseFunction:
    Type: AWS::Lambda::Function
    DependsOn: CreateSupportCaseLambdaRolePolicy
    Properties:
      FunctionName: 'amsCreateSupportCaseOnAccountProvisioning'      
      LoggingConfig:
        LogGroup: !Ref CreateSupportCaseFunctionLogGroup
      Handler: index.lambda_handler
      Role: !GetAtt CreateSupportCaseLambdaRole.Arn
      KmsKeyArn: !GetAtt OnboardingKMSKey.Arn
      Environment:
        Variables:
          SNS_TOPIC_ARN: !Ref OnboardingFailureTopic
      DeadLetterConfig:
        TargetArn: !Ref OnboardingFailureTopic
      Code:
        ZipFile: |
          import boto3
          import botocore
          import json
          import logging
          import os
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)
          def send_error_to_sns(error_message, event, context):
              sns_topic_arn = os.environ.get('SNS_TOPIC_ARN')
              if sns_topic_arn:
                  try:
                      sns = boto3.client('sns')
                      sns.publish(
                          TopicArn=sns_topic_arn,
                          Subject='AMS Onboarding Lambda Error',
                          Message=f"Function: {context.function_name}nError: {error_message}nEvent: {json.dumps(event)}"
                      )
                  except Exception:
                      pass
          def lambda_handler(event, context):
                          
              try:
                  support = boto3.client('support')
                  logger.info("event_details: " + json.dumps(event))
                  # Extract account details from the event
                  service_event_details = event['detail']['serviceEventDetails']
                  logger.info("service_event_details: " + json.dumps(service_event_details))
                  account_details = service_event_details['createManagedAccountStatus']['account']
                  logger.info("account_details: " + json.dumps(account_details))
                  account_id = account_details['accountId']
                  account_name = account_details['accountName']
                  # Create support case
                  response = support.create_case(
                      subject=f"New Account Created via Control Tower - Account ID: {account_id} || Account Name: {account_name}",
                      serviceCode='service-ams-operations-service-request',
                      severityCode='low',
                      categoryCode='onboarding-or-offboarding',
                      communicationBody=f"A new account has been created via ControlTower/LZA. Proceed with AMS Onboarding for this account and engage CA/CSDM.nnnAccount to be Onboarded to AMS:nnAccount ID: {account_id}nAccount Name: {account_name}"
                  )
                  return {
                      'statusCode': 200,
                      'body': json.dumps(response)
                  }
              except KeyError as e:
                  error_message = f"Missing required field in event: {str(e)}"
                  logger.error(error_message)
                  send_error_to_sns(error_message, event, context)
                  return {
                      'statusCode': 400,
                      'body': json.dumps({'error': error_message})
                  }
              except botocore.exceptions.ClientError as e:
                  error_message = f"AWS API error: {str(e)}"
                  logger.error(error_message)
                  send_error_to_sns(error_message, event, context)
                  return {
                      'statusCode': 500,
                      'body': json.dumps({'error': error_message})
                  }
              except Exception as e:
                  error_message = f"Unexpected error: {str(e)}"
                  logger.error(error_message)
                  send_error_to_sns(error_message, event, context)
                  return {
                      'statusCode': 500,
                      'body': json.dumps({'error': error_message})
                  }
      Runtime: !Ref LambdaRuntimePython
      Timeout: 30
      MemorySize: 128
      ReservedConcurrentExecutions: 1

Now that the IAM role and Lambda function are set up, the template creates the EventBridge rule to invoke the Lambda function. This rule triggers the Lambda function, identified by the Amazon Resource Name (ARN), when an AWS Control Tower lifecycle event of type CreateManagedAccount occurs:

# EventBridge Rule
  CreateAccountEventRule:
    Type: AWS::Events::Rule
    Properties:
      Name: 'amsCreateSupportCaseEventsRule'
      Description: "EventBridge rule to capture Control Tower CreateManagedAccount API calls"
      EventPattern:
        source:
          - "aws.controltower"
        detail-type:
          - "AWS Service Event via CloudTrail"
        detail:
          eventName:
            - "CreateManagedAccount"
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CreateSupportCaseFunction.Arn
          Id: "CreateSupportCaseTarget"

Finally, the template grants permission for EventBridge to invoke the Lambda function:

# Lambda Permission for EventBridge
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref CreateSupportCaseFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt CreateAccountEventRule.Arn

You now have a straightforward mechanism to provide consistency and minimize extra work.

Adapt the solution

The code in this post focuses on the AMS account onboarding use case. You can adapt this for other use cases where the external system has an API that can be called. For example:

  • Inventory systems – Modify the Lambda function to make API calls to your inventory management system
  • CMDB updates – Integrate with your CMDB to add new account entries automatically
  • Security checks – Trigger automated security scans or compliance checks for new accounts
  • Billing systems – Update billing or chargeback systems with new account information

Clean up

To avoid ongoing charges, delete the following resources when you no longer need them:

Conclusion

This solution can help keep external systems updated with your AWS account creation process. By using AWS services like EventBridge and Lambda, you can make sure the necessary systems are updated promptly and consistently when new accounts are created through AWS Control Tower.

This approach can significantly streamline your workflows and help support your cloud governance processes, for example when integrating with managed services like AMS, updating internal databases, or triggering security processes.

To get started, you can use the provided CloudFormation template and customize the Lambda function to fit your specific needs. Remember to test thoroughly in a non-production environment before deploying to your production setup.


About the authors

Hammad Raza is a Senior Cloud Architect at AWS Managed Services. With expertise in designing and implementing enterprise-scale AWS solutions, he helps organizations transform complex business requirements into scalable, secure, and cost-effective cloud solutions. Hammad’s passion lies in designing cost-effective and operationally efficient architectures, leveraging AWS Managed Services to help customers achieve their business goals.

Yomesh Shah is a Senior Solutions Architect at AWS. He brings 25 years of experience helping enterprises maximize the value of their IT investments by leveraging optimization, automation, and process improvement. He currently helps AWS customers leverage and apply scalable AWS Support solutions. Yomesh also holds a patent for the design of a Managed Services control plane in the cloud (US11856055B2)

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top