Deploying Security Hub across all Regions at Scale Using AWS CLI

John Byrd
5 min readMay 2, 2019

--

The Security Hub team has done a great job producing scripts for a multi account architecture. However, this relies on some python and a little tweaking.

If you have taken the time to read my Cross Account Scripting in an AWS Multi-Account Strategy article, then the scripts below will look familiar. I’m acknowledging that the scripting method used here may not seem very logical, but keep in mind that it’s built for quick reuse with little modifications. Please take the time to read the previously linked article for full explanation and pre-work required to make this happen.

Since the last article, I’ve cleaned up the script some, added some notations, and customized it for Security Hub. Using the deployment of Security Hub, I’d like to revisit this cross-account script and provide another use case for this powerful scripting solution.

Before beginning, let’s review Security Hub deployment. On an account that will aggregate data from all accounts, which will be referred to here as the MSA (Master SecHub Account), member accounts will need to be created and then invited to the Security Hub service. Once this invitation has been sent, the member account will have to accept the invitation.

In preparation, we’re going to produce 4 text files that will be used as inputs for the script. The names are only important for reference within the script. Feel free to change at your discretion.:

1. sechubmaster.txt — This text file will include only the account number of the MSA.

2. list-sechub.txt — To create and send the invitations, we’ll first need to produce a list of account numbers and email addresses. Separate the values with a tab and include no headings in this txt file.

3. list-all.txt — This file should consist of a single column of account numbers with no delimiter. This should look just like list-sechub.txt without the email addresses.

4. regions.txt — Just like the account list above, the target regions should be one per row and include all regions in which Security Hub will be deployed.

Note that the only thing not listed in these scripts is the initial enable-security-hub from the master account. Alternatively, logging into the console and enabling the service on the master account will be needed.

Now to visit the first script. The script will pull the account number and email address to create and invite the accounts. (Also available on GitHub here.)

## 'file' should contain the master security hub account only
## This account will be assumed and have commands run as the targets included in 'file2'
file="/home/ec2-user/sechubmaster.txt"
YELLOW='\033[1;33m'
RED='\033[1;31m'
PURP='\033[1;34m'
NC='\033[0m'
while IFS= read -r acctid
do
# Clears all affected env variables
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
# Assume role with predeployed IAM role
idp=$(aws sts assume-role --role-arn arn:aws:iam::$acctid:role/<ROLENAME> --role-session-name IDP)
# Sets env variables
export AWS_ACCESS_KEY_ID=$(echo $idp | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $idp | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $idp | jq -r .Credentials.SessionToken)
# Populate per account commands below this line
AcctNumber=$(aws sts get-caller-identity | jq -r .Account)
echo -e Attempting commands against ${RED}$AcctNumber${NC}…

# 'file2' should be a list of accounts and email addresses separated by a tab
file2="/home/ec2-user/list-sechub.txt"
while IFS= read -r TargetAcctId
do
id=$(grep "$TargetAcctId" $file2 | cut -f1)
email=$(grep "$TargetAcctId" $file2 | cut -f2)
echo -e …on ${YELLOW}$id${NC}…
file3="/home/ec2-user/regions.txt"
while IFS= read -r region
do
echo -e …in ${PURP}$region${NC}…
## All commands below will be executed as the accounts listed in 'file2'
aws securityhub create-members --account-details AccountId=$id,Email=$email --region $region
aws securityhub invite-members --account-ids $id --region $region
done < "$file3"
done < "$file2"
done < "$file"

Following the declaration of the region, the script will present you with two possible responses. The following means there was no previous invitation and it was successfully created.

{
“UnprocessedAccounts”: []
}
{
“UnprocessedAccounts”: []
}

If these keys have values, this means that there is either an outstanding invitation or the account is already a member of another Security Hub collective.

Upon the completion of the creation and invitations for the member accounts, we can now move to them and authenticate to each of them to accept the invitation. Note here, the enable-security-hub is included for the members. Here is the script to accomplish that (also available here).

## Inclusion of accounts in ‘file2’ that do not have an invite or have already accepted an invite receive the following error:
## “aws: error: argument --master-id: expected one argument”
## Update with list for accounts to be targeted
file=”/home/ec2-user/list-all.txt”
YELLOW=’\033[1;33m’
RED=’\033[1;31m’
PURP=’\033[0;34m’
NC=’\033[0m’
while IFS= read -r acctid
do
# Clears all affected env variables
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
# Assume role and print info to idp.txt
idp=$(aws sts assume-role --role-arn arn:aws:iam::$acctid:role/<ROLENAME> --role-session-name IDP)
# Sets env variables
export AWS_ACCESS_KEY_ID=$(echo $idp | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $idp | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $idp | jq -r .Credentials.SessionToken)
# Populate per account commands below this line
AcctNumber=$(aws sts get-caller-identity | jq -r .Account)
echo -e “Attempting commands against ${YELLOW}$AcctNumber${NC}…”
# ‘file2’ should include all regions where Security Hub will be deployed
file2=”/home/ec2-user/regions.txt”
while IFS= read -r region
do
# All commands below will be performed per each region listed in ‘file2’
echo -e “…in ${PURP}$region${NC}”
invitation=$(aws securityhub list-invitations --query ‘Invitations[*].[InvitationId]’ --output text --region $region)
masteracct=$(aws securityhub list-invitations --query ‘Invitations[*].[AccountId]’ --output text --region $region)
echo $masteracct
echo $invitation
aws securityhub enable-security-hub --region $region
aws securityhub accept-invitation --master-id $masteracct --invitation-id $invitation --region $region
done < “$file2
done < “$file”

Congratulations, logging back into the Master Security Hub Account, you should see the accounts listed as Enabled.

Once this is done, replace the enable-security-hub and accept-invitation with aws securityhub batch-enable-standards --standards-subscription-requests StandardsArn=”arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0",StandardsInput={} --regions $region in the last script to enable the CIS benchmarks. Note that this will create more than 20 config rules for evaluation in the regions listed in file.

Let me know if you run into any issues with, recommendations for, or questions about this solution.

--

--

John Byrd

Modernizing companies’ AWS security and governance programs at scale.