In April 2020, SOMA Global, a leading provider of Public Safety as a Service (PSAAS), set out to update its computer aided design (CAD) platform to increase reliability to 99.999%, an industry first. SOMA Global adopted an account-based approach for tenant isolation to meet Criminal Justice Information Service (CJIS) regulations. The development and operations team required a robust continuous integration, continuous delivery (CI/CD) pipeline in GovCloud that could dynamically deploy their application in the various accounts to help SOMA Global achieve its goals.
All organizations require durable automated delivery of code for their applications. Organizations leverage multi-account CI/CD pipelines to deploy code. Traditional pipeline stages are predefined and static in nature.
By following this post, you will be setting up a tag-based dynamic multi-account CI/CD pipeline. Your pipeline will deploy a sample application governed across various accounts from a central Bitbucket Cloud repository. New code deployments will be directed to specific accounts by the tags you create. This solution uses services like AWS CloudFormation, AWS Lambda, Amazon DynamoDB, and AWS Step Functions and is applicable to GovCloud and commercial regions.
It’s recommended that you use a multi-account governance solution like AWS Control Tower or AWS Landing Zone. It can help save time by automating the set-up of an environment for running secure and scalable workloads while implementing an initial security baseline through the creation of core accounts and resources. It also provides a baseline environment to get started with a multi-account architecture, identity and access management, governance, data security, network design, and logging.
The following diagram illustrates the solution architecture.
The following steps represent the architectural progression:
- A commit to the master branch or a tag creation for a commit in any branch in the Atlassian Bitbucket Cloud code repository.
- This event triggers AWS CodeBuild via direct integration with Bitbucket. CodeBuild reads the commit information and builds the application per the provided buildspec.yml.
- CodeBuild does the following actions:
- The DynamoDB table has DynamoDB Streams enabled, which triggers a Lambda function to read the new item in the table.
- The function queries a separate DynamoDB table containing the mappings for the Git tags to AWS account IDs. These IDs and the build information are passed to the step function for the run to start.
- For every account passed, the step function runs a Lambda function that copies the artifacts stored in the S3 bucket in the CI/CD account to the target accounts. In our walkthrough, we include steps to copy artifacts to an S3 bucket that is used for a static website.
- For every AWS account ID passed into the step function, the Lambda function copies the artifacts to the target account’s S3 bucket.
- When the artifact lands in target account’s S3 bucket, AWS CodePipeline triggers the deployment of the application back-end via CloudFormation. In parallel, static website artifacts are copied to an S3 bucket that is used to host the application front-end.
Future improvements of this application are to create a static website to manage the mappings between AWS account IDs and Git tags stored in DynamoDB. In this version, the mappings are manually entered into DynamoDB via the AWS Management Console.
The code deliverables include the following:
- dynamic-cicd-repo.zip – This archive contains the source code for the sample application. Unzip it and upload its contents to your Bitbucket Cloud repository.
- cicd-grantpermission-function.zip – This archive contains the code for creating the cicd-grant-permission Lambda function in the main CI/CD account, which is used to grant access to CI/CD target accounts.
- cicd-mainaccount.yaml – This template is deployed in the main CI/CD account.
- cicd-targetaccount.yaml – This template is deployed in the target CI/CD accounts.
Before getting started, complete the following prerequisites:
- Access to 2 or more AWS accounts for setting up the dynamic multi-account pipeline.
- Bitbucket Cloud account and login credentials.
- Clone this repository and follow the installation instructions in the README.
The source must include the buildspec.yml and the template.yaml files for deployments.
The buildspec.yml file is a collection of build commands and related settings that CodeBuild uses to run a build. The build stage is configured to copy the packaged.yaml and front-end files for deployment to the main account’s CI/CD source artifact S3 bucket and to update the DynamoDB table that includes the metadata.
The template.yaml file is used to create the AWS CloudFormation package in the build stage of CodeBuild. AWS CloudFormation uses the packaged.yaml template to deploy and update resources in the target accounts.
The following screenshot shows the sample application stored in a Bitbucket repository under the main branch.
Main CI/CD account setup
To set up your main account, complete the following steps:
- Log in to your main CI/CD account and select the required region.
- Upload the cicd-grantpermission-function.zip file to an S3 bucket hosted in the same region for deployment.
- Launch the cicd-mainaccount.yaml template in your AWS CloudFormation console after providing the Bitbucket Repository URL and source credentials.
The following screenshots show the parameters for deploying cicd-mainaccount.yaml and the outputs section post stack creation.
Update the buildspec file in your repository with the respective oArtifactBucket and oCicdDynamoTable values found in the Outputs section.
To set up your target account:
- Ensure that the cicd-mainaccount.yaml deployed in the main CI/CD account has completed.
- Log in to your target CI/CD account and select the same region used in the main CI/CD account setup.
- Launch the cicd-targetaccount.yaml template in your AWS CloudFormation console after providing the parameters.
The following screenshots show the parameters for deploying cicd-targetaccount.yaml and the outputs section post stack creation.
Note the front-end API URL.
In the main CI/CD account, navigate to the Functions page on the Lambda console and select cicd-grant-permission function. Update the environment variable targetAccountId with the target account ID and invoke the function with the default test event to grant the target account access.
In the main CI/CD account, navigate to the Tables page and select the cicd-account-map-table DynamoDB table. Create items to define a tag and account ID relation as key-value pairs. You can list multiple accounts for a tag separated by a comma. Follow this example to write data to the table.
The deployment is triggered with a commit in the main branch in Bitbucket. This invokes a deployment to the account ID specified for the branch/main tag in cicd-account-map-table. The following screenshot shows the CodeBuild webhook event filter for commits.
The deployment is also triggered with a tag creation for a commit in any branch. Tags in Bitbucket must start with a prefix v, followed by the release version number, followed by the deployment tag, each separated with -. For example, v-1.1.1-internal invokes a deployment to the account ID or IDs specified for the internal tag in cicd-account-map-table. Tag examples in Bitbucket include v-1.1.1-qa, v-1.1.1-prod, v-1.1.1-release, and v-1.1.1-dev. The following screenshot shows the CodeBuild webhook event filter for tags.
If the tag created in Bitbucket doesn’t map to a tag in the cicd-account-map-table table, the event is dropped.
CodeBuild saves the commit ID, build ID, tag, and timestamp for every build in the cicd-codebuild-info-table DynamoDB table to track the deployments. You can query this table for the deployment history.
To deploy the new application build, tag a commit from the repository with a tag specific for an account or commit to the main branch.
You can track the CodeBuild project in your main CI/CD account.
The front-end code is deployed to the front-end bucket, which is served as a static website via API Gateway. The following screenshot shows the front-end application.
CodePipeline deploys the back-end resources via CloudFormation in your new target account. The following screenshots show the back-end CloudFormation stack outputs and the back-end API.
Note the back-end API URL.
Clean up your dynamic multi-account pipeline and related resources
To avoid ongoing charges for the resources you created following this post, you should delete:
- The CloudFormation stacks deployed in the main and target CI/CD accounts.
- Empty and delete the retained S3 buckets.
The solution created by AWS Professional Services for SOMA Global, as outlined in this post, allows them to dynamically deploy their application to hundreds of AWS accounts for tenant isolation and compliance. The ability to isolate clients in real-world situations and limit your blast radius of component failures to single customers is a major advantage of this solution. As Peter Quintas, CEO of SOMA Global, states, “The Dynamic CI/CD pipelines allows us the freedom to deploy changes to our customers and meets our stringent compliance regimes.”.
As a best practice, be sure to implement an enterprise multi-account structure using AWS Control Tower or similar. For more information, see Establishing your best practice AWS environment.