The Power of Control Tower Controls
Control Tower provides plethora features when trying to assist you to manage your multi-account environment. One of these features is the usage of the so-called Controls.
Control Tower Controls assist you in setting up guardrails to make your environment more secure and ensure governance across all OUs and accounts.
These Controls can be split up into three different groups:
- Preventive Controls → With the usage of Service Control Policies this type of control prevent you doing something by setting a Deny to a specific action. An example for a Preventive Control is the Disallow Creation of Access Keys for the Root User Control
- Detective Controls → This creates a Config Rule checking if the corresponding resource has a non compliant state which then can be leveraged to a remediation action. An example Control here is Detect Whether Unrestricted Incoming TCP Traffic is Allowed
- Proactive Controls → The setting of IAM Permissions is used here to proactively make sure that specific actions for a specific service or user can not be done. An example is Require AWS Lambda function policies to prohibit public access.
The Bad News
When using a Control Tower environment with a nested OU structure, all underlying OUs inherit the Preventive Controls set at a higher level.
Unfortunately this inheritance does not occur for Detective and Proactive Controls. As AWS continues to release new Controls and your environment expands, this can result in some OUs not having the full range of necessary Controls enabled. To avoid generating an overhead by activating the Controls on every underlying OU individually, read on to see how I resolved this issue.
Solving the Problem
The infrastructure is built as shown in the image below. The main actions are implemented with the usage of an AWS Step Function.
We have two main topics to have a look here:
- Inherit activated Controls to all underlying OUs
- Making sure new OUs and accounts also get the corresponding Controls
Let’s look at Step 1 first:
First of all is to make sure we have a place to document all the Controls we want to activate in our environment. I used the SSM Parameter store as it lets you save values in a JSON format, which we will work with in this solution. This Control list is set in the Cloudformation code and then deployed to the parameter store.
As soon as a change happens to this parameter in the code, a Lambda Custom Resource gets triggered. Custom Resources in Cloudformation are mostly connected with a Lambda function which gets executes as soon as the Custom Resource gets updated. So in case we update our Control List later, the Lambda function gets executed. The function the starts the execution of a Step Function which is used to enable all the Controls on all OUs in the following way.
The step function first executes a parallel state:
- First State: Reading out the SSM Parameter value by using the GetParameter API call and transforming the JSON to an array with a separate Lambda function. This is done to let the Step Function be able to loop over the different Control values set in the Parameter Store.
- Second State: Execute a Lambda function reading out all of OUs of the organization. In case an OU should be left out, it will not be part of the output here.
Both outputs are passed to the following Map State which then first loops over all Controls which were read out earlier from the parameter store.
An inner loop then loops over all OUs followed by a Pass State combining the Control value and the OU value. This will get passed to the EnableControl API call to activate the Control to the corresponding OU. Unfortunately, as the automation runs in to an error in case the Control has already been activated an catch expression was inserted here to not let the Step Function fail and continue with the next OU.
The Map States then loop over every Control and over every OU making sure all combinations were handled.
But how do we make sure that also newly created OUs will get the Controls?
This is done in Step 2:
For this case, we just need to implement an EventBridge rule listening to the ManageOrganizationalUnit CloudTrail event, handing this over to the Step function used above. As Control Tower takes some time to also register the OU and rolling out all necessary stacks, a Wait State is set in the beginning of the Step Function. This makes sure that all prior needed steps are already finished when the Control Rollout takes place.
How to deploy
Check out my Github for all necessary code to this solution!
Of course, the code should be deployed on the account where Control Tower has been activated. When deploying the template you will need to insert the Controls in an ARN format, divided by commata as seen in the screenshot below. As SSM Parameter Store saves everything as a String, make sure to not forget the apostrophes.
During the deployment the Custom Resource will already get triggered, so take a look at the Step Function to see how the Controls are getting enabled!