This article is a guest post from Olaf Conijn, the creator of org-formation.
In the first two parts of this series, we learned how to manage your AWS Organizations using Infrastructure as Code (IaC) (Part 1) and how to create a continuous deployment pipeline for changes to your Organizations (Part 2). In the final installment of this series, we will look at org-formation-specific extensions to the AWS CloudFormation IAC language that make AWS CloudFormation aware of the organizational context within the AWS account. We’ll also create references to other resources across different AWS accounts and regions.
Org-formation Annotated CloudFormation templates
Another feature of org-formation is the ability to add organization-aware annotations to regular CloudFormation templates. Regular CloudFormation has no knowledge of organization resources and only supports specifying resources within a template that all need deployment to the same target account and region.
For each individual resource within a template, org-formation allows us to specify which account and region to deploy the resource. The mechanism by which we specify where to deploy a resource is the same Organization Binding as used within a tasks file. This means that resources within a template can be bound to multiple account/region combinations (e.g., by specifying the binding
Note that this is different from CloudFormation StackSets. With the StackSet feature of CloudFormation, you can execute a template in different target accounts and regions. The template, however, will always be the same for all targets. In practice, this means that for any unique set of resources, you must create a new CloudFormation template, resulting in a lot of work spent managing the relationships between these templates.
When executing the
org-formation update-stacks command or adding an
update-stacks task to a task file, org-formation will generate a CloudFormation template for each target you specified within your bindings. It will also create the resources bound to that target using CloudFormation.
> org-formation update-stacks template.yml --stack-name my-stack
The following is an example of an Annotated CloudFormation template:
The previous template creates a
Budget resource for every account in the organization with a tag
BudgetAlarmThreshold. In the properties of this resource, various references to the organization.yml file are used:
BudgetNameof the Budget resource is a composite of budget and the value of the IAM alias in the created account. This is useful for identifying to which AWS account a Budget notification applies.
Amountof the BudgetLimit specifies the value of the tag
BudgetAlarmThresholdof the Budget resource in the created account.
Addressof the Email Subscriber specifies the value of the tag
AccountOwnerEmailof the Budget resource in the created account.
Note that when resolving these references, the values are read from the organization.yml file that is included either by
Organization attribute, or by the tasks file. If we manually change the value of the tag in the AWS console, org-formation will not know. If we change the value of a tag in the organization.yml, then org-formation knows that it needs to run both
update-stacks for templates that reference tags. Also, the
Organization attribute does not require specification when including a template from within a tasks file. Overwrite attributes like
DefaultOrganizationBindingRegion and the bindings from within a tasks file.
A reference to
AWSAccount will resolve to the account the CloudFormation template executes in, much like
AWS::AccountId. However, we can refer to any account in the organization.yml file by its logical name (e.g.,
!GetAtt MyDevAccount.Tags.AccountOwnerEmail or
!Ref MyDevAccount) are also valid expressions, assuming we declared an account named MyDevAccount.
Cross account references in CloudFormation templates
As org-formation templates contain resources that will be deployed to multiple accounts, they can also contain the relationships (
!Ref or otherwise) between these resources.
The previous example demonstrates a CloudFormation template with three resources:
CloudTrail resource deploys to all accounts, and the
S3BucketPolicy will only generate in the
Executing org-formation creates a template for every account in the organization (the
CloudTrail resource is bound to all accounts). All these templates will contain a
CloudTrail resource. The template created for the
ComplianceAccount will additionally contain the
CloudTrail resource has a reference to the
S3Bucket resource, which is bound only to the
ComplianceAccount account. What org-formation will do for all accounts that do not have both resources is create a CloudFormation export in the template deployed to the ComplianceAccount and declare a parameter in the templates deployed to all other accounts. When deploying, org-formation will create a dependency between the templates, to ensure the right order of execution, and copy the value from the export into the parameter of the other templates when deploying these.
This example illustrates the fragments from deploying the template to the
The cross account expression (
!Ref S3Bucket) will be copied to the
Value of the output. This can be any expression, also
This example illustrates the fragments from the template that will be deployed all accounts, except for the
Note the removal of the
DependsOn attribute from the original template. Although org-formation understands the relationship between the templates, CloudFormation does not, and there is no use for the
DependsOn within the template deployed to CloudFormation. Being able to use references to organization resources and resources bound to different accounts allows us to create templates. These templates describe how to apply entire best practices and patterns to a multi-account setup. References also allow us to re-use these templates, as they do not contain account IDs or require you to deploy multiple CloudFormation templates.
Additional CloudFormation Annotations
Once we start modelling different parts of our resource baseline in CloudFormation, we will notice that we might need more than just the ability to refer to organization resources or resources across accounts/regions. Other features that can be useful are:
ForeachAccountattribute: Specifying a binding as the value of this attribute will create a copy of the resource for each account in the binding. This can be useful when setting up host names and certificates in our MasterAccount for each account that needs one, or when implementing Amazon GuardDuty and applying this to all accounts in our organization.
Fn::EnumTargetAccountsfunction: This function allows us to create an array of values for each account in a binding. Use this when setting up cross account IAM permissions that adhere to the principle of least privilege.
This is an example of the use of
In the example, a
Member resource generates for each account in the specified binding (
Accounts: '*'). When creating a resource for each account in the binding, use
CurrentAccount to resolve information about the account being iterated over. AWSAccount will still refer to the AWS account part of the target (in this case the MasterAccount). A full example on how to implement GuardDuty using org-formation can be found in GitHub.
This is an example of
Fn::EnumTargetAccounts, and how to create a resource policy and provide access to other accounts:
In this example, we created a
Bucket resource in
MyAccount. A BucketPolicy provides Get/List access to this bucket for all the accounts that are part of the
ReadAccessAccountBinding. In the same example, the task file supplies the
ReadAccountAccountBinding. The default specified in the template is an empty binding (no account will get access to the
Note that as the default is an empty binding, the
EnumTargetAccounts will generate an empty array and it is only possible to create a valid
BucketPolicy if there are more than zero accounts part of the
ReadAccountAccessBinding. The function
Fn::TargetAccount will return the number of accounts part of a binding, which can be used in a CloudFormation condition. We can find a more complete example on how to set up cross account access to Amazon S3 buckets in GitHub.
In this series, we learned about three features that the org-formation tool provides. Use each of these to set up and manage resources across AWS Organizations:
We wrote this article on version 0.9.6 of org-formation. For the most recent version, examples, and documentation, refer to the Github project page.
Feel free to engage, create issues, ask questions over Slack, provide feedback, and share your experiences.
The content and opinions in this post are those of the third-party author and AWS is not responsible for the content or accuracy of this post.