This repo provides an example solution enabling the provisioning of Control Tower-integrated AWS OUs and accounts via GitHub Actions.
The main focus of this repo is the OU and account vending automation.
To see more details about the Backstage integration or the automated resource provisioning, see the repo aws_environments.
The solution is split across two GitHub repos:
- aws_management (the repo you are currently in)
- aws_environments
Both repos have GitHUb actions which use Terraform to automate OU/account creation and resource provisioning.
Detailed explanations are available in the relevant sections in the README files.
The solution uses the following AWS Services:
- AWS Control Tower managed OUs and accounts keep the AWS usage centrally governed and compliant.
- AWS Organizations to keep access, costs and management of Workload accounts restricted.
- AWS Service Catalog to programmatically provision AWS accounts.
- AWS CloudFormation StackSets to inject continuous integration and continuous delivery (CI/CD) roles into newly provisioned accounts.
- AWS IAM Identity Center to automatically assign user groups to newly provisioned accounts.
- Amazon Route 53 to delegate DNS from the Zone Apex account to newly vended accounts (Optional).
As part of the initial setup, the Management account requires an IAMRole and setup of GitHub as a trusted OIDC provider.
This is described below in Management resources setup.
With Terraform and the aws-cli included in the ubuntu-2204 GitHub image, only an AWS account is required to proceed. Fork this repo and follow the steps below to use it in your own AWS management account. All you need outside of this repo is an AWS account with an IAMUser/IAMRole with the AdministratorAccess managed policy attached.
The repo is designed to work in a Control Tower managed AWS setup.
The first step would be to enable Control Tower in your management account.
The solution uses CloudFormation StackSets to inject the IAM roles necessary for further automation.
The second step therefore, is to enable Cloud Formation trusted access for AWS Organizations.
Optionally, a Route53 Hosted Zone can be used to allow DNS Delegation to be performed into the newly created accounts.
For this the account would need a Route53 Hosted Zone configured.
If you want to skip the Route53 step then comment out account_resources/dns.tf.
Directory _terraform contains a simple declaration to provision an S3 bucket and DynamodDB lock table for Terraform.
The S3 Bucket it creates is shared between the Terraform projects, management_resources, ou_resources and account_resources.
A partial backend file is also generated, and used in the three above-mentioned repos, with independent keys.
This should be applied once by an account user with permissions to assume the AWSAdministratorAccess role.
The default workspace can be used.
Contains the resources to enable the automation within the central management account. GitHub is configured as an OpenID Connect (OIDC) Provider, to avoid storage of AWS credentials. The IAMRole cicd_role grants permissions for Control Tower, Service Catalog, and other automation needs. Finally, the IAMRole [cicd_role] is added as a principal to the service catalog Control Tower portfolio.
This should be applied once by an account user with permissions to assume the AWSAdministratorAccess role.
The default workspace can be used.
Backstage sources are not part of this repo, and detailed setup instructions are also not provided.
If you want to industrialize the setup after trying it out, there are deployment instructions available at the official Backstage documentation.
For this simple example, it's enough to launch Backstage locally.
Just follow the getting-started instructions provided by Backstage to get the sources.
Before you start Backstage continue with the configuration here.
This setup requires GitHub actions to be installed, so that the GitHub MR can be created.
From backstage root dir call:
yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-github
and add to /packages/backend/src/index.ts
:
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
To be able to discover the Catalog Entities from GitHub (and to trigger the eventual MR), Backstage requires a Token with the scopes (repo, workflow) for GitHub.
For this simple scenario, it is enough to create a PAT and set it as an environment variable (GITHUB_TOKEN).
There is a special catalog entry for the management account, so that the automation knows where Control Tower and the root OU are.
Update the contents of the file to correctly reflect your management account ID and root OU.
Finally, copy over the example app-config.yaml from this repo.
catalog:
orphanStrategy: delete
processingInterval: { minutes: 3 }
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow: [Component, System, API, Resource, Location, Domain, Template]
locations:
- type: url
target: https://github.com/<<YOUR_GITHUB_ORG>>/aws_management/blob/main/catalog-info.yaml
- type: url
target: https://github.com/<<YOUR_GITHUB_ORG>>/aws_environments/blob/main/catalog-info.yaml
Make sure you modify the catalog location URLs to point to your own GitHub clones/forks of these repos.
Its time to start Backstage.
cd my-backstage-app # your app name
yarn dev
If everything has gone to plan, you should see Backstage starting up and available on your localhost with two entries in the catalog, AWS Management and AWS Environments. To understand what the Catalog is, please check the Backstage documentation.
The OUs and accounts created in the management repo are represented in the Backstage Catalog based on the Backstage System Model.
Any OU you create in AWS via Backstage will have a catalog entry here.
Any account you create in AWS via Backstage will have a catalog entry here.
There are four templates ready to use in the two repositories.
OU and Account automation
Part of this repo and used to provision new OUs and accounts.
Environment automation
Part of the aws_environments repository, and will be described in detail in that repositories README.mdhttps://github.com/tamer84/aws_environments/README.md).
Interacting with the example is via Backstage Templates, which trigger GitHub Actions.
The templates can be accessed in the local running instance of Backstage.
The two workflows (OU and account) are defined in the .github/workflows directory.
Each workflow requires input variables, which are in turn passed to terraform as variables for the provisioning.
Before Terraform can be executed, the Runner needs to assume the cicd_role defined in management_resources.
For this the GitHub action aws-actions/configure-aws-credentials is used as follows:
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v4.0.2
with:
role-to-assume: arn:aws:iam::${{ env.ACCOUNT }}:role/service-role/cicd_role
role-session-name: GitHub_to_AWS_via_FederatedOIDC
aws-region: ${{ env.AWS_REGION }}
The role is confirmed using AWS STS:
- name: Sts GetCallerIdentity
run: |
aws sts get-caller-identity
Once the IAMRole is assumed terraform can be executed, for example the account:
- name: Create new Account via Terraform
run: |
cd account_resources
terraform init -backend-config=../_terraform/remote_backend.hcl
terraform workspace select -or-create $OU-$ENVIRONMENT
terraform apply --auto-approve -var parent_ou_id=$OU -var domain=$DOMAIN -var environment=$ENVIRONMENT -var sso_email=$OWNER
The logic in these two directories is based on the AWS YouTube video Programmatically Create an AWS Account with AWS Control Tower | Amazon Web Services .
Responsible for creating new Control Tower managed OUs.
- User launches the AWS OU Provision template.
- Backstage triggers the ou.yaml workflow.
- The new OU is created
- Backstage Catalog is updated
An OU is provisioned using the Terraform resource aws_organizations_organizational_unit.
The provisioned OU is not by default under Control Tower management, it needs to be explicitly brought under management.
For this, a Terraform null_resource resource is declared, to allow usage of aws-cli.
The accounts vended later within this new OU will be automatically injected with an IAMRole/IAMRolePolicy to allow automated environment provisioning.
This is achieved by creating a CloudFormation StackSet and setting it to auto deploy to new accounts within the OU.
The StackSet contains the IAMRole and configures GitHub as a trusted OIDC provider.
Responsible for creating new Control Tower managed accounts within managed OUs.
- User launches the AWS Account Provision template.
- Backstage triggers the account.yaml workflow.
- The new account is created
- The StackSet injects the IAMRole and configures GitHub as an OIDC provider
- Backstage Catalog is updated
An account is provisioned using the Terraform null_resource resource, to allow usage of aws-cli servicecatalog commands.
Details about the process can be seen at the linked YouTube video.
Optionally, dns.tf creates a dynamic DNS entry for the new account.
This is supported if there is a Route53 Hosted Zone in the management account.
It should be commented out if your test AWS Management account does not include a DNS Hosted Zone.
To demonstrate provisioning resources in the newly vended account using the IAMRole injected via the OU StackSet, the account-alias is set.
resource "aws_iam_account_alias" "alias" {
provider = aws.workload
account_alias = "${local.ou_name}-${terraform.workspace}-${data.aws_caller_identity.current.account_id}"
}
Notice the provider is specified as aws.workload.
This is defined in the providers.tf file as follows:
provider "aws" {
region = var.region
alias = "workload"
assume_role {
# The role ARN for CICD
role_arn = "arn:aws:iam::${data.external.get_account_id.result.Id}:role/service-role/cicd_role"
}
default_tags {
tags = {
Terraform = "true"
}
}
}
The alias is created by assuming the IAMRole cicd_role within the newly provisioned account.
For each provisioned OU or account, Terraform generates a Backstage Catalog Entity file.
The files are committed by the GitHub action at the end of a successful run into the .backstage directory.
At the root of the repo is a file called catalog-info.yaml, which contains a Backstage Entity of type Location:
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: iac-management-files
description: IAC systems collection
spec:
targets:
- .backstage/*/*.yaml
This tells Backstage to scan the .backstage directory for Catalog Entities.
Now that Control Tower OUs and accounts can be provisioned via GutHub actions, it's time to provision resources into the accounts via Backstage.
Go see the repo aws_environments to learn how to configure resources for environments.