Skip to content

Instantly share code, notes, and snippets.

@o2346
Last active April 9, 2021 02:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save o2346/e0fa3eeb8c67ff51660c354ee3cabdba to your computer and use it in GitHub Desktop.
Save o2346/e0fa3eeb8c67ff51660c354ee3cabdba to your computer and use it in GitHub Desktop.
Obtain regions supports AWS Security Hub, excluding ones not-opted-in

Helper scripts for AWS Security Hub

get_securityhub_regions_optedin.py

Obtain regions supports AWS Security Hub excluding ones not-opted-in. part of explanation for issue #42

findings2slack.sh

Automating procedures indicated following blog posts, with some modification

It supports single region by itself. In order to depoly for cross-region, issue a command like below

python3 ./get_securityhub_regions_optedin.py --output text | while read region; do
  AWS_DEFAULT_REGION=$region
  ./findings2slack.sh WORKSPACE_ID CHANNEL_ID
done
#!/bin/bash
# automate deployment of slack notifier described shown below
# https://aws.amazon.com/blogs/security/enabling-aws-security-hub-integration-with-aws-chatbot/
# usage:
# $0 SlackWorkSpaceID SlackChannelID [SlackWebhookURL]
# prerequisite
# obtain apropreate IDs beforehand
# be sure value of AWS_DEFAULT_REGION which one, as well as credentials like shown below
# export AWS_ACCESS_KEY_ID="XXXXXXXXXXXXXXXXXXXX"
# export AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# export AWS_SESSION_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# if SlackWebhookURL was given, following solution would additionaly be 'create-stack'ed
# https://aws.amazon.com/blogs/apn/how-to-enable-custom-actions-in-aws-security-hub/
# Worked:
# Ubuntu
# aws-cli/1.16.206 Python/3.5.2 Linux/4.10.0-38-generic botocore/1.15.44
# Dependencies:
# bc
# There is no point to go further if negative
aws securityhub describe-hub || exit 1
echo "attempting on $AWS_DEFAULT_REGION" >&2
# Examine if configuration for AWS Chatbot which is a global service is already configured
# if something like below found, assumes affirmative
# 'arn:aws:iam::111111111111:role/SecurityHubToAWSChatBot-ChatBotManageIAMRole-XXXXXXXXXXXX'
# Since I could not find any api which provicde directly access for AWS Chatbot
# So unfortunately tracing any belongings of the configuration like an iam role above
function might_SlackChannelConfig_configured_previously() {
local some=$(aws iam list-roles \
--query 'Roles[?contains(RoleName,`ChatBotManageIAMRole`)].{name:RoleName}' \
--output text)
if [ -n "$some" ]; then
return 0
else
return 1
fi
}
if might_SlackChannelConfig_configured_previously; then
readonly configureSlackChannel='false'
else
readonly configureSlackChannel='true'
fi
echo "configureSlackChannel=$configureSlackChannel" >&2
function get_stack() {
aws cloudformation describe-stacks \
--stack-name $1 \
--output text
}
#Wait CREATE_COMPLETE in exponential backoff
function await_completion() {
local statusis=`mktemp`
trap "[ -f $statusis ] && rm $statusis" ERR EXIT
seq 0 10 | xargs -I{} echo "2^{}-1" | bc | while read standby; do
sleep "`shuf -i 0-$standby -n 1`"
local state=$(aws cloudformation describe-stacks \
--stack-name $1 \
--query 'Stacks[].{StackName:StackName,StackStatus:StackStatus}' \
--output text)
if echo $state | grep 'IN_PROGRESS'; then
:
else
printf '0' > $statusis && break
fi
#[ -n "`get_stack $1 | grep ''`" ] && printf '0' > $statusis && break
printf '1' > $statusis
echo 'Stack is still not yet ready' >&2
done
[ "`cat $statusis`" = '1' ] && echo "[ERROR] Gave up. Something is wrong with the stack $1" >&2
return "`cat $statusis`"
}
function does_exists() {
aws cloudformation describe-stacks \
--stack-name $1 \
--query 'Stacks[?StackName==`'$1'`]' > /dev/null
}
function get_sns_topic() {
local arn=$(aws sns list-topics \
--query 'Topics[?contains(TopicArn,`SecurityHubToAWSChatBot-SNSTopicAWSChatBot`)].{arn:TopicArn}' \
--output text)
echo $arn
if [ -n "$arn" ]; then
return 0
else
return 1
fi
}
function is_target_defined() {
local target=$(aws events list-targets-by-rule --rule $1 --query 'Targets[?Id==`'$1'`].{Id:Id}' --output text)
if [ -n "$target" ]; then
return 0
else
return 1
fi
}
# Unnecessary for stack_2. stack_2 does not depend on this
# attempt only if webhookurl was given
if [ -n "$3" ]; then
readonly stack_1='EnableSecurityHubFindingsToSlack'
if does_exists $stack_1; then
aws cloudformation update-stack \
--stack-name $stack_1 \
--template-body "`curl https://raw.githubusercontent.com/aws-samples/aws-securityhub-to-slack/master/SecurityHubFindingsToSlack.json`" \
--parameters '[{"ParameterKey":"IncomingWebHookURL","ParameterValue":"'$3'"},{"ParameterKey":"SlackChannel","ParameterValue":"'$2'"}]' \
--capabilities CAPABILITY_NAMED_IAM
else
aws cloudformation create-stack \
--stack-name $stack_1 \
--template-body "`curl https://raw.githubusercontent.com/aws-samples/aws-securityhub-to-slack/master/SecurityHubFindingsToSlack.json`" \
--parameters '[{"ParameterKey":"IncomingWebHookURL","ParameterValue":"'$3'"},{"ParameterKey":"SlackChannel","ParameterValue":"'$2'"}]' \
--capabilities CAPABILITY_NAMED_IAM
await_completion $stack_1
fi
fi
readonly chatbotbody='ChatBotBodySecurityHubToSlack'
readonly chatbotbody_template="`curl https://raw.githubusercontent.com/o2346/aws-securityhub-to-slack/edge/ChatbotBody.yml`"
#readonly chatbotbody_template="`cat ~/Documents/aws-securityhub-to-slack/ChatbotBody.yml`"
#readonly chatbotbody_template="`curl https://raw.githubusercontent.com/aws-samples/aws-securityhub-to-slack/master/ChatbotBody.yml`"
if [ "$configureSlackChannel" = 'true' ]; then
if does_exists $chatbotbody; then
echo updating
aws cloudformation update-stack \
--stack-name $chatbotbody \
--template-body "$chatbotbody_template" \
--parameters '[{"ParameterKey":"SlackWorkSpaceID","ParameterValue":"'$1'"},{"ParameterKey":"SlackChannelID","ParameterValue":"'$2'"}]' \
--capabilities CAPABILITY_NAMED_IAM
await_completion $chatbotbody
else
echo creating
aws cloudformation create-stack \
--stack-name $chatbotbody \
--template-body "$chatbotbody_template" \
--parameters '[{"ParameterKey":"SlackWorkSpaceID","ParameterValue":"'$1'"},{"ParameterKey":"SlackChannelID","ParameterValue":"'$2'"}]' \
--capabilities CAPABILITY_NAMED_IAM
await_completion $chatbotbody
fi
fi
readonly stack_2='SecurityHubToAWSChatBot'
readonly stack_2_template="`curl https://raw.githubusercontent.com/o2346/aws-securityhub-to-slack/edge/SecurityHub_to_AWSChatBot.yml`"
#readonly stack_2_template="`cat ~/Documents/aws-securityhub-to-slack/SecurityHub_to_AWSChatBot.yml`"
#readonly stack_2_template="`curl https://raw.githubusercontent.com/aws-samples/aws-securityhub-to-slack/master/SecurityHub_to_AWSChatBot.yml`"
if does_exists $stack_2; then
echo updating
aws cloudformation update-stack \
--stack-name $stack_2 \
--template-body "$stack_2_template" \
--capabilities CAPABILITY_NAMED_IAM
await_completion $stack_2
else
echo creating
aws cloudformation create-stack \
--stack-name $stack_2 \
--template-body "$stack_2_template" \
--capabilities CAPABILITY_NAMED_IAM
await_completion $stack_2
fi
#https://github.com/boozallen/devsecops-example-helloworld/issues/1
readonly rule='All_SecurityHub_Findings_to_Slack'
if aws events describe-rule --name $rule > /dev/null; then
aws events list-targets-by-rule --rule $rule --query 'Targets[?Id==`'$rule'`].{Id:Id}' --output text | xargs -I{} aws events remove-targets --rule $rule --ids {}
aws events delete-rule --name $rule
fi
aws events put-rule --name $rule \
--event-pattern '{
"detail-type": ["Security Hub Findings - Imported"],
"source": ["aws.securityhub"],
"detail": {
"findings": {
"Severity": {
"Label": [
"CRITICAL"
]
},
"Compliance": {
"Status": [
"FAILED"
]
},
"Workflow": {
"Status": [ "NEW" ]
},
"RecordState": [ "ACTIVE" ]
}
}
}'
# Modify labels as your need
# "HIGH",
# "MEDIUM"
readonly topicarn="`get_sns_topic`"
aws events put-targets --rule $rule --targets "Id"="$rule","Arn"="$topicarn"
# memo
# would be better to support style like
# $0 -w SlackWorkSpaceID -c SlackChannelID -u SlackWebhookURL
#!/usr/bin/python3
#Obtain regions supports AWS Security Hub, excluding ones not-opted-in
#usage: ./this_script [--verbose] [--output csv|text]
from __future__ import print_function
import botocore
import boto3
import sys
import os
import argparse
import json
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', dest='verbose', default=False, action='store_true')
parser.add_argument('--output', default='csv', choices=['text','csv'])
args = parser.parse_args()
def console_error_verbose(*_args, **kwargs):
if args.verbose:
print(*_args, file=sys.stderr, **kwargs)
not_opted_ins = []
for line in os.popen("aws ec2 describe-regions --all-regions --query 'Regions[?OptInStatus==`not-opted-in`].{Name:RegionName}' --output text").readlines(): #https://janakiev.com/blog/python-shell-commands/
not_opted_ins.append(line.rstrip('\n'))
#I just want not-opted-in regions here. ec2 is actually non of my concern
console_error_verbose('ones not opted in:', not_opted_ins)
candidates = boto3.session.Session().get_available_regions('securityhub')
#This result unfortunately includes not-opted-in regions if such ones are there.
#It causes confusion to consider not-opted-in regions are even 'available'.
#Since this context is for particular account instead of general speaking of the service
console_error_verbose('supprots security hub but includes not-opted-ins: ', json.dumps(candidates, indent=2))
#filter out the ones not-opted-in
ones_optedin_available = list(filter(lambda k: k not in not_opted_ins, candidates)) #https://thispointer.com/python-filter-function-tutorial-examples/
console_error_verbose('The ones excluds not-opted-in', json.dumps(ones_optedin_available, indent=2))
#finally these are exactly what I want
if args.output == 'csv':
print(', '.join(ones_optedin_available))
exit(0)
if args.output == 'text':
print('\n'.join(ones_optedin_available))
exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment