Creating AWS Organizational Units and Accounts for Prod and Dev in CloudFormation
Here’s an AWS CloudFormation template and deployment script to set up Organizational Units and Accounts for Prod and Dev for a new project or department.
Deploying a CloudFormation stack from this template will require a user in the
Organization’s
management account
with the AdministratorAccess
permission set.
The created Organizational Unit (OU) and Account structure will look this:
Organization Root [pre-existing] ╚═ Management Account [pre-existing] ╚═ Project Parent OU ╚═ Project Prod OU ╚═ Project Prod Account ╚═ Project Dev OU ╚═ Project Dev Account
The OUs are not strictly necessary, but they help to group the whole project under a single OU, and to allow for other accounts to be added later within the Prod and Dev OUs if necessary.
The CloudFormation template looks like this:
---
AWSTemplateFormatVersion: '2010-09-09'
Description: >
Create AWS Organizational Units and an Accounts for a project.
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-organizations-organizationalunit.html
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-organizations-account.html
Parameters:
OrganizationRootId:
Type: String
Description: ID of the root Organization of the management account.
AllowedPattern: "^(r-[0-9a-z]{4,32})$"
ProjectName:
Type: String
Description: Overall name for this project, used in OU and Account names.
AllowedPattern: "^([a-zA-Z]+)$"
EmailDomain:
Type: String
Description: Domain name for AWS Account email addresses.
AllowedPattern: "^([a-zA-Z0-9]+).([a-zA-Z0-9\.]+)$"
Resources:
ProjectParentOrganizationalUnit:
Type: AWS::Organizations::OrganizationalUnit
Properties:
Name: !Sub "${ProjectName}ParentOU"
ParentId: !Ref OrganizationRootId
ProdOrganizationalUnit:
Type: AWS::Organizations::OrganizationalUnit
Properties:
Name: !Sub "${ProjectName}ProdOU"
ParentId: !Ref ProjectParentOrganizationalUnit
ProdAccount:
Type: AWS::Organizations::Account
Properties:
AccountName: !Sub "${ProjectName}Prod"
Email: !Sub "${ProjectName}_aws_prod@${EmailDomain}"
RoleName: !Sub "${ProjectName}ProdAdmin"
ParentIds:
- !Ref ProdOrganizationalUnit
DevOrganizationalUnit:
Type: AWS::Organizations::OrganizationalUnit
Properties:
Name: !Sub "${ProjectName}DevOU"
ParentId: !Ref ProjectParentOrganizationalUnit
DevAccount:
Type: AWS::Organizations::Account
Properties:
AccountName: !Sub "${ProjectName}Dev"
Email: !Sub "${ProjectName}_aws_dev@${EmailDomain}"
RoleName: !Sub "${ProjectName}DevAdmin"
ParentIds:
- !Ref DevOrganizationalUnit
Outputs:
ProjectParentOrganizationalUnitArn:
Description: ARN of the Project Parent Organizational Unit
Value: !GetAtt ProjectParentOrganizationalUnit.Arn
ProdOrganizationalUnitArn:
Description: ARN of the Prod Organizational Unit
Value: !GetAtt ProdOrganizationalUnit.Arn
ProdAccountArn:
Description: ARN of the Prod Account
Value: !GetAtt ProdAccount.Arn
DevOrganizationalUnitArn:
Description: ARN of the Dev Organizational Unit
Value: !GetAtt DevOrganizationalUnit.Arn
DevAccountArn:
Description: ARN of the Dev Account
Value: !GetAtt DevAccount.Arn
Here’s a sample deployment script to deploy the above template:
#!/usr/bin/env bash
set -e, -u, -x, -o pipefail
PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit; pwd -P )
TEMPLATE_PATH="${PARENT_PATH}/cloudformation.yml"
PARAMS_PATH="${PARENT_PATH}/config/organizational.params"
ls "${TEMPLATE_PATH}"
if [ ! -f "${TEMPLATE_PATH}" ]; then
echo "Cloudformation template file is missing"
fi
ls "${PARAMS_PATH}"
if [ ! -f "${PARAMS_PATH}" ]; then
echo "Params file is missing"
fi
if [ -z ${AWS_PROFILE+x} ]; then
echo "AWS_PROFILE is not set"
exit 1
fi
# shellcheck disable=SC1090
source "${PARAMS_PATH}"
if [ -z ${OrganizationRootId+x} ]; then
echo "OrganizationRootId is not set"
exit 1
fi
# shellcheck disable=SC2154
echo "OrganizationRootId: ${OrganizationRootId}"
# shellcheck disable=SC2154
echo "ProjectName: ${ProjectName}"
# https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/
# shellcheck disable=SC2046
aws cloudformation deploy \
--profile "${AWS_PROFILE}" \
--template-file "${PARENT_PATH}/cloudformation.yml" \
--stack-name "${ProjectName}-Organizational" \
--parameter-overrides OrganizationRootId="${OrganizationRootId}" $(cat "${PARAMS_PATH}")
That requires a params file at ./config/organizational.params
, which provides
the ProjectName
and EmailDomain
params, for example:
ProjectName=FoobarProject
EmailDomain=kensiosoftware.co.uk
This assumes that a catch-all email is set up on the specified domain, so that the CloudFormation template can make up specific email addresses for each account.