mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 23:05:05 +00:00
This change adds a -m option which configures the --max-items API parameter for large AWS resources. Currently, SNS topic subscriptions are limited to the default of 100 items. SNS topics can easily surpass 100,000 subscriptions which is too many to show by default. Since check 3.15 is confirming that subscribers exist - not what they actually are - it's a waste to display all 100,000 entries.
55 KiB
Executable File
55 KiB
Executable File
#!/bin/bash
# Prowler is a tool that provides automate auditing and hardening guidance of an AWS account.
# It is based on AWS-CLI commands. It follows guidelines present in the CIS Amazon
# Web Services Foundations Benchmark at:
# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf
# This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0
# International Public License. The link to the license terms can be found at
# https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
#
# Author: Toni de la Fuente - @ToniBlyx / Alfresco Software Inc.
# Prowler - Iron Maiden
#
# Walking through the city, looking oh so pretty
# I've just got to find my way
# See the ladies flashing
# All there legs and lashes
# I've just got to find my way...
# Exit if a pipeline results in an error.
# set -ue
# set -o pipefail
# set -vx
# Exits if any error is found
# set -e
# Colors
NORMAL=""
WARNING="" # Bad (red)
SECTION="" # Section (yellow)
NOTICE="" # Notice (yellow)
OK="" # Ok (green)
BAD="" # Bad (red)
CYAN=""
BLUE=""
BROWN=""
DARKGRAY=""
GRAY=""
GREEN=""
MAGENTA=""
PURPLE=""
RED=""
YELLOW=""
WHITE=""
# Set the defaults for these getopts variables
PROFILE="default"
REGION="us-east-1"
FILTERREGION=""
MAXITEMS=100
# Command usage menu
usage(){
echo -e "\nUSAGE:
`basename $0` -p <profile> -r <region> [ -h ]
Options:
-p <profile> specify your AWS profile to use (i.e.: default)
-r <region> specify an AWS region to direct API requests to (i.e.: us-east-1)
-c <checknum> specify a check number or group from the AWS CIS benchmark (i.e.: check11 for check 1.1 or check3 for entire section 3)
-f <filterregion> specify an AWS region to run checks against (i.e.: us-west-1)
-m <maxitems> specify the maximum number of items to return for long-running requests (default: 100)
-h this help
"
exit
}
while getopts "hp:r:c:f:m:" OPTION; do
case $OPTION in
h )
usage
exit 1
;;
p )
PROFILE=$OPTARG
;;
r )
REGION=$OPTARG
;;
c )
CHECKNUMBER=$OPTARG
;;
f )
FILTERREGION=$OPTARG
;;
m )
MAXITEMS=$OPTARG
;;
: )
echo -e "\n$RED ERROR!$NORMAL -$OPTARG requires an argument\n"
exit 1
;;
? )
echo -e "\n$RED ERROR!$NORMAL Invalid option"
usage
exit 1
;;
esac
done
# Functions to manage dates depending on OS
if [[ "$OSTYPE" == "linux-gnu" ]]; then
# function to compare in days, usage how_older_from_today date
# date format %Y-%m-%d
how_older_from_today()
{
DATE_TO_COMPARE=$1
TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s)
DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s)
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
echo $DAYS_SINCE
}
# function to convert from timestamp to date, usage timestamp_to_date timestamp
# output date format %Y-%m-%d
timestamp_to_date()
{
# remove fractions of a second
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
echo $OUTPUT_DATE
}
decode_report()
{
base64 -d
}
elif [[ "$OSTYPE" == "darwin"* ]]; then
# BSD/OSX coommands compatibility
how_older_from_today()
{
DATE_TO_COMPARE=$1
TODAY_IN_DAYS=$(date +%s)
DATE_FROM_IN_DAYS=$(date -jf %Y-%m-%d $DATE_TO_COMPARE +%s)
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
echo $DAYS_SINCE
}
timestamp_to_date()
{
# remove fractions of a second
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
OUTPUT_DATE=$(date -r $TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
echo $OUTPUT_DATE
}
decode_report()
{
base64 -D
}
elif [[ "$OSTYPE" == "cygwin" ]]; then
# POSIX compatibility layer and Linux environment emulation for Windows
how_older_from_today()
{
DATE_TO_COMPARE=$1
TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s)
DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s)
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
echo $DAYS_SINCE
}
timestamp_to_date()
{
# remove fractions of a second
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
echo $OUTPUT_DATE
}
decode_report()
{
base64 -d
}
else
echo "Unknown Operating System"
exit
fi
if [[ ! -f ~/.aws/credentials ]]; then
echo -e "\n$RED ERROR!$NORMAL AWS credentials file not found (~/.aws/credentials). Run 'aws configure' first. \n"
return 1
fi
# AWS-CLI variables
AWSCLI=$(which aws)
if [ -z "${AWSCLI}" ]; then
echo -e "\n$RED ERROR!$NORMAL AWS-CLI (aws command) not found. Make sure it is installed correctly and in your \$PATH\n"
exit
fi
# if this script runs in an AWS instance
# INSTANCE_PROFILE=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/)
# AWS_ACCESS_KEY_ID=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g')
# AWS_SECRET_ACCESS_KEY_ID=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g')
# AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
# AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY_ID}
prowlerBanner() {
echo -e "$CYAN _"
echo -e " _ __ _ __ _____ _| | ___ _ __"
echo -e " | '_ \| '__/ _ \ \ /\ / / |/ _ \ '__|"
echo -e " | |_) | | | (_) \ V V /| | __/ |"
echo -e " | .__/|_| \___/ \_/\_/ |_|\___|_|"
echo -e " |_|$NORMAL$BLUE CIS based AWS Account Hardening Tool$NORMAL\n"
}
# Get whoami in AWS, who is the user running this shell script
getWhoami() {
echo -e "\nThis report is being generated using credentials below:\n"
echo -e "AWS-CLI Profile: $NOTICE[$PROFILE]$NORMAL AWS API Region: $NOTICE[$REGION]$NORMAL AWS Filter Region: $NOTICE[${FILTERREGION:-all}]\n"
$AWSCLI sts get-caller-identity --output table --profile $PROFILE --region $REGION
}
printCurrentDate(){
echo -e "\nDate: $NOTICE$(date)$NORMAL"
}
printColorsCode(){
echo -e "\nColors Code for results: $NOTICE INFORMATIVE$NORMAL,$OK OK (RECOMMENDED VALUE)$NORMAL, $BAD WARNING (FIX REQUIRED)$NORMAL \n"
}
# Generate Credential Report
genCredReport() {
echo -en '\nGenerating AWS IAM Credential Report...'
until $AWSCLI iam generate-credential-report --output text --query 'State' --profile $PROFILE --region $REGION |grep -m 1 "COMPLETE"; do
sleep 1
echo -n "."
done
}
# Save report to a file, decode it, deletion at finish and after every single check, acb stands for AWS CIS Benchmark
saveReport(){
TEMP_REPORT_FILE=/tmp/.acb
$AWSCLI iam get-credential-report --query 'Content' --output text --profile $PROFILE --region $REGION | decode_report > $TEMP_REPORT_FILE
}
# Delete temporary report file
cleanTemp(){
rm -fr $TEMP_REPORT_FILE
}
# Get a list of all available AWS Regions
REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \
--output text \
--profile $PROFILE \
--region $REGION \
--region-names $FILTERREGION)
infoReferenceLong(){
# Report review note:
echo -e " $BLUE \n*********************************$NORMAL"
echo -e " $NOTICE For more information: $NORMAL"
echo -e " $NOTICE https://benchmarks.cisecurity.org/tools2/amazon/CIS_Amazon_Web_Services_Foundations_Benchmark_v1.1.0.pdf$NORMAL"
echo -e " $NOTICE For bugs or feedback: $NORMAL"
echo -e " $NOTICE https://github.com/Alfresco/aws-cis-security-benchmark/issues$NORMAL"
}
infoReferenceShort(){
# Report review note:
echo -e " $NOTICE http://bit.ly/2g3PEf7$NORMAL"
}
prowlerBanner
printCurrentDate
getWhoami
printColorsCode
genCredReport
saveReport
check11(){
TITLE11="$BLUE 1.1$NORMAL Avoid the use of the root account (Scored). Last time root account was used
(password last used, access_key_1_last_used, access_key_2_last_used): "
COMMAND11=$(cat $TEMP_REPORT_FILE| grep '<root_account>' | cut -d, -f5,11,16 | sed 's/,/,\ /g')
echo -e "\n$TITLE11"
echo -e " $NOTICE $COMMAND11 $NORMAL"
}
check12(){
TITLE12="$BLUE 1.2$NORMAL Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)"
# List users with password enabled
COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }')
COMMAND12=$(
for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do
cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$8 }' |grep -w $i| grep false | awk '{ print $1 }'|tr '\n' ' ';
done)
echo -e "\n$TITLE12"
if [[ $COMMAND12 ]]; then
echo -e " List of users with Password enabled but MFA disabled:"
echo -e " $RED $COMMAND12 $NORMAL"
else
echo -e " $OK OK! No users found with Password enabled and MFA disabled $NORMAL"
fi
}
check13(){
TITLE13="$BLUE 1.3$NORMAL Ensure credentials unused for 90 days or greater are disabled (Scored)"
echo -e "\n$TITLE13 "
COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }')
if [[ $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED ]]; then
COMMAND13=$(
for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do
cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$5 }' |grep $i| awk '{ print $1 }'|tr '\n' ' ';
done)
# list of users that have used password
USERS_PASSWORD_USED=$($AWSCLI iam list-users --query "Users[?PasswordLastUsed].UserName" --output text --profile $PROFILE --region $REGION)
if [[ $USERS_PASSWORD_USED ]]; then
# look for users with a password last used more or equal to 90 days
for i in $USERS_PASSWORD_USED; do
DATEUSED=$($AWSCLI iam list-users --query "Users[?UserName=='$i'].PasswordLastUsed" --output text --profile $PROFILE --region $REGION | cut -d'T' -f1)
HOWOLDER=$(how_older_from_today $DATEUSED)
if [ $HOWOLDER -gt "90" ];then
echo " $RED $i $NORMAL"
else
echo " $OK OK! User \"$i\" found with unused credentials for 90 days or greater $NORMAL"
fi
done
fi
else
echo " $OK OK! No users found with password enabled $NORMAL"
fi
}
check14(){
TITLE14="$BLUE 1.4$NORMAL Ensure access keys are rotated every 90 days or less (Scored)" # also checked by Security Monkey
LIST_OF_USERS_WITH_ACCESS_KEY1=$(cat $TEMP_REPORT_FILE| awk -F, '{ print $1, $9 }' |grep "\ true" | awk '{ print $1 }')
LIST_OF_USERS_WITH_ACCESS_KEY2=$(cat $TEMP_REPORT_FILE| awk -F, '{ print $1, $14 }' |grep "\ true" | awk '{ print $1 }')
echo -e "\n$TITLE14 "
echo -e " Users with access key 1 older than 90 days: "
for user in $LIST_OF_USERS_WITH_ACCESS_KEY1; do
# check access key 1
DATEROTATED1=$(cat $TEMP_REPORT_FILE | grep $user| awk -F, '{ print $10 }' | grep -v "N/A" | awk -F"T" '{ print $1 }')
HOWOLDER=$(how_older_from_today $DATEROTATED1)
if [ $HOWOLDER -gt "90" ];then
echo -e " $RED $user $NORMAL"
fi
done
echo -e " Users with access key 2 older than 90 days: "
for user in $LIST_OF_USERS_WITH_ACCESS_KEY2; do
# check access key 2
DATEROTATED2=$(cat $TEMP_REPORT_FILE | grep $user| awk -F, '{ print $10 }' | grep -v "N/A" | awk -F"T" '{ print $1 }')
HOWOLDER=$(how_older_from_today $DATEROTATED2)
if [ $HOWOLDER -gt "90" ];then
echo -e " $RED $user $NORMAL"
fi
done
}
check15(){
TITLE15="$BLUE 1.5$NORMAL Ensure IAM password policy requires at least one uppercase letter (Scored)"
COMMAND15=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireUppercaseCharacters') # must be true
echo -e "\n$TITLE15 "
if [[ $COMMAND15 == "true" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check16(){
TITLE16="$BLUE 1.6$NORMAL Ensure IAM password policy require at least one lowercase letter (Scored)"
COMMAND16=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireLowercaseCharacters') # must be true
echo -e "\n$TITLE16 "
if [[ $COMMAND16 == "true" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check17(){
TITLE17="$BLUE 1.7$NORMAL Ensure IAM password policy require at least one symbol (Scored)"
COMMAND17=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireSymbols') # must be true
echo -e "\n$TITLE17 "
if [[ $COMMAND17 == "true" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check18(){
TITLE18="$BLUE 1.8$NORMAL Ensure IAM password policy require at least one number (Scored)"
COMMAND18=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireNumbers') # must be true
echo -e "\n$TITLE18 "
if [[ $COMMAND18 == "true" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check19(){
TITLE19="$BLUE 1.9$NORMAL Ensure IAM password policy requires minimum length of 14 or greater (Scored)"
COMMAND19=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.MinimumPasswordLength')
echo -e "\n$TITLE19 "
if [[ $COMMAND19 -gt "13" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check110(){
TITLE110="$BLUE 1.10$NORMAL Ensure IAM password policy prevents password reuse (Scored)"
COMMAND110=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.PasswordReusePrevention' --output text)
echo -e "\n$TITLE110 "
if [[ $COMMAND110 ]];then
if [[ $COMMAND110 -gt "23" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! It is not set or it is set lower than 24 $NORMAL"
fi
else
echo -e " $RED WARNING! It is not set $NORMAL"
fi
}
check111(){
TITLE111="$BLUE 1.11$NORMAL Ensure IAM password policy expires passwords within 90 days or less (Scored)"
COMMAND111=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION | grep MaxPasswordAge | awk -F: '{ print $2 }'|sed 's/\ //g'|sed 's/,/ /g')
echo -e "\n$TITLE111 "
if [[ $COMMAND111 ]];then
if [ $COMMAND111 == "90" ];then
echo -e " $OK OK! $NORMAL"
fi
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check112(){
TITLE112="$BLUE 1.12$NORMAL Ensure no root account access key exists (Scored)"
# ensure the access_key_1_active and access_key_2_active fields are set to FALSE.
ROOTKEY1=$(cat $TEMP_REPORT_FILE |grep root_account|awk -F',' '{ print $9 }')
ROOTKEY2=$(cat $TEMP_REPORT_FILE |grep root_account|awk -F',' '{ print $14 }')
echo -e "\n$TITLE112 "
if [ $ROOTKEY1 == "false" ];then
echo -e " $OK OK! $NORMAL No access key 1 found "
else
echo -e " $RED Found access key 1 $NORMAL"
fi
if [ $ROOTKEY2 == "false" ];then
echo -e " $OK OK! $NORMAL No access key 2 found "
else
echo -e " $RED Found access key 2 $NORMAL"
fi
}
check113(){
TITLE113="$BLUE 1.13$NORMAL Ensure MFA is enabled for the root account (Scored)"
COMMAND113=$($AWSCLI iam get-account-summary --profile $PROFILE --region $REGION |grep AccountMFAEnabled | awk -F': ' '{ print $2 }'|sed 's/,//')
echo -e "\n$TITLE113"
if [ $COMMAND113 == "1" ]; then
echo " $OK OK! $NORMAL Virtual MFA is enabled. "
else
echo " $RED WARNING! MFA is not ENABLED for root account $NORMAL"
fi
}
check114(){
TITLE114="$BLUE 1.14$NORMAL Ensure hardware MFA is enabled for the root account (Scored)"
COMMAND113=$($AWSCLI iam get-account-summary --profile $PROFILE --region $REGION |grep AccountMFAEnabled | awk -F': ' '{ print $2 }'|sed 's/,//')
echo -e "\n$TITLE114"
if [ $COMMAND113 == "1" ]; then
COMMAND114=$($AWSCLI iam list-virtual-mfa-devices --profile $PROFILE --region $REGION --query 'VirtualMFADevices' --output text|grep :root |wc -l)
if [ $COMMAND114 == "1" ]; then
echo " $OK OK! $NORMAL Virtual MFA is enabled. "
else
echo " $OK OK! $NORMAL Hardware MFA is enabled. "
fi
else
echo " $RED WARNING! MFA is not ENABLED for root account $NORMAL"
fi
}
check115(){
TITLE115="$BLUE 1.15$NORMAL Ensure security questions are registered in the AWS account (Not Scored)"
# No command available
echo -e "\n$TITLE115"
echo -e " $NOTICE No command available for check 1.14"
echo -e " Login to the AWS Console as root, click on the Account "
echo -e " Name -> My Account -> Configure Security Challenge Questions $NORMAL"
}
check116(){
TITLE115="$BLUE 1.16$NORMAL Ensure IAM policies are attached only to groups or roles (Scored)"
echo -e "\n$TITLE116"
LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text --profile $PROFILE --region $REGION)
echo -e " Users with policy attached to them instead to groups: (it may take few seconds...) "
for user in $LIST_USERS;do
USER_POLICY=$($AWSCLI iam list-attached-user-policies --output text --profile $PROFILE --region $REGION --user-name $user)
if [[ $USER_POLICY ]]; then
echo -e " $RED $user $NORMAL"
fi
done
}
check117(){
TITLE117="$BLUE 1.17$NORMAL Enable detailed billing (Scored)"
# No command available
echo -e "\n$TITLE117 "
echo -e " $NOTICE No command available for check 1.17"
echo -e " See section 1.17 on the CIS Benchmark guide for details $NORMAL"
infoReferenceShort
}
check118(){
TITLE118="$BLUE 1.18$NORMAL Ensure IAM Master and IAM Manager roles are active (Scored)"
echo -e "\n$TITLE118 "
FINDMASTERANDMANAGER=$($AWSCLI iam list-roles --profile $PROFILE --region $REGION --query "Roles[*].{RoleName:RoleName}" --output text | grep -E 'Master|Manager'| tr '\n' ' ')
if [[ $FINDMASTERANDMANAGER ]];then
echo -e " $NOTICE Found next roles as possible IAM Master and IAM Manager candidates: $NORMAL"
echo -e " $NOTICE $FINDMASTERANDMANAGER $NORMAL"
echo -e "\n $NOTICE INFO: run the commands below to check their policies with section 1.18 in the guide... $NORMAL"
for role in $FINDMASTERANDMANAGER;do
# find inline policies in found roles
INLINEPOLICIES=$($AWSCLI iam list-role-policies --role-name $role --profile $PROFILE --region $REGION --query "PolicyNames[*]" --output text)
for policy in $INLINEPOLICIES;do
echo " $NOTICE $AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION$NORMAL"
done
# find attached policies in found roles
ATTACHEDPOLICIES=$($AWSCLI iam list-attached-role-policies --role-name $role --profile $PROFILE --region $REGION --query "AttachedPolicies[*]" --output text)
for policy in $ATTACHEDPOLICIES;do
echo " $NOTICE $AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION$NORMAL"
done
done
else
echo -e " $RED WARNING! IAM Master and IAM Manager roles not found$NORMAL"
fi
}
check119(){
TITLE119="$BLUE 1.19$NORMAL Maintain current contact details (Scored)"
# No command available
echo -e "\n$TITLE119 "
echo -e " $NOTICE No command available for check 1.19"
echo -e " See section 1.19 on the CIS Benchmark guide for details $NORMAL"
infoReferenceShort
}
check120(){
TITLE120="$BLUE 1.20$NORMAL Ensure security contact information is registered (Scored)"
# No command available
echo -e "\n$TITLE120 "
echo -e " $NOTICE No command available for check 1.20"
echo -e " See section 1.20 on the CIS Benchmark guide for details $NORMAL"
infoReferenceShort
}
check121(){
TITLE121="$BLUE 1.21$NORMAL Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)"
echo -e "\n$TITLE121 "
echo -e " $NOTICE No command available for check 1.21"
echo -e " See section 1.21 on the CIS Benchmark guide for details $NORMAL"
infoReferenceShort
}
check122(){
TITLE122="$BLUE 1.22$NORMAL Ensure a support role has been created to manage incidents with AWS Support (Scored)"
echo -e "\n$TITLE122 "
SUPPORTPOLICYARN=$($AWSCLI iam list-policies --query "Policies[?PolicyName == 'AWSSupportAccess'].Arn" --profile $PROFILE --region $REGION --output text)
if [[ $SUPPORTPOLICYARN ]];then
for policyarn in $SUPPORTPOLICYARN;do
POLICYTOSHOW=$($AWSCLI iam list-entities-for-policy --policy-arn $SUPPORTPOLICYARN --profile $PROFILE --region $REGION --output text)
if [[ $POLICYTOSHOW ]];then
echo -e " $OK $POLICYTOSHOW $NORMAL"
echo -e " $NOTICE Make sure your team can create a Support case with AWS $NORMAL"
else
echo -e " $RED Support Policy not applied to any Group, User or Role $NORMAL"
fi
done
else
echo -e " $RED WARNING! No Support Policy found$NORMAL"
fi
}
check123(){
TITLE123="$BLUE 1.23$NORMAL Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)"
echo -e "\n$TITLE123 "
LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text --profile $PROFILE --region $REGION)
# List of USERS with KEY1 last_used_date as N/A
LIST_USERS_KEY1_NA=$(for user in $LIST_USERS; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$11 }'|grep N/A |awk '{ print $1 }'; done)
LIST_USERS_KEY1_ACTIVE=$(for user in $LIST_USERS_KEY1_NA; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$9 }'|grep "true$"|awk '{ print $1 }'|sed 's/[:blank:]+/,/g' ; done)
if [[ $LIST_USERS_KEY1_ACTIVE ]]; then
echo -e " $NOTICE List of users with Access Key 1 never used:$NORMAL"
echo -e " $RED $LIST_USERS_KEY1_ACTIVE $NORMAL"
else
echo -e " $OK No users found with Access Key 1 never used $NORMAL"
fi
# List of USERS with KEY2 last_used_date as N/A
LIST_USERS_KEY2_NA=$(for user in $LIST_USERS; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$16 }'|grep N/A |awk '{ print $1 }' ; done)
LIST_USERS_KEY2_ACTIVE=$(for user in $LIST_USERS_KEY2_NA; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$14 }'|grep "true$" |awk '{ print $1 }' ; done)
if [[ $LIST_USERS_KEY2_ACTIVE ]]; then
echo -e " $NOTICE List of users with Access Key 2 never used:$NORMAL"
echo -e " $RED $LIST_USERS_KEY2_ACTIVE $NORMAL"
else
echo -e " $OK No users found with Access Key 2 never used $NORMAL"
fi
}
check124(){
TITLE124="$BLUE 1.24$NORMAL Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)"
echo -e "\n$TITLE124"
LIST_CUSTOM_POLICIES=$($AWSCLI iam list-policies --output text --profile $PROFILE --region $REGION|grep 'arn:aws:iam::[0-9]\{12\}:'|awk '{ print $2 }')
if [[ $LIST_CUSTOM_POLICIES ]]; then
echo -e " $NOTICE Looking for custom policies: (skipping default policies, it may take few seconds...)$NORMAL"
for policy in $LIST_CUSTOM_POLICIES; do
POLICY_VERSION=$($AWSCLI iam list-policies --profile $PROFILE --region $REGION --query 'Policies[*].[Arn,DefaultVersionId]' --output text|grep -w $policy |awk '{ print $2}')
POLICY_WITH_FULL=$($AWSCLI iam get-policy-version --output text --policy-arn $policy --version-id $POLICY_VERSION --query "PolicyVersion.Document.Statement[?Effect == 'Allow' && contains(Resource, '*') && contains (Action, '*')]" --profile $PROFILE --region $REGION)
if [[ $POLICY_WITH_FULL ]]; then
POLICIES_ALLOW_LIST="$POLICIES_ALLOW_LIST $policy"
fi
done
if [[ $POLICIES_ALLOW_LIST ]]; then
echo -e " $NOTICE List of custom policies: $NORMAL"
for policy in $POLICIES_ALLOW_LIST; do
echo " $RED $policy $NORMAL"
done
else
echo " $OK No custom policy found that allow full \"*:*\" administrative privileges $NORMAL"
fi
else
echo " $OK No custom policies found $NORMAL"
fi
}
check21(){
TITLE21="$BLUE 2.1$NORMAL Ensure CloudTrail is enabled in all regions (Scored)"
echo -e "\n$TITLE21"
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].Name' --output text)
if [[ $LIST_OF_TRAILS ]];then
for trail in $LIST_OF_TRAILS;do
MULTIREGION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].IsMultiRegionTrail' --output text --trail-name-list $trail)
if [[ $MULTIREGION_TRAIL_STATUS == 'False' ]];then
echo -e " $RED WARNING! $trail trail in $REGION is not enabled in multi region mode$NORMAL"
else
echo -e " $OK OK! $trail trail in $REGION is enabled for all regions$NORMAL"
fi
done
else
echo -e " $RED WARNING! No CloudTrail trails found!$NORMAL"
fi
}
check22(){
TITLE22="$BLUE 2.2$NORMAL Ensure CloudTrail log file validation is enabled (Scored)"
echo -e "\n$TITLE22"
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].Name' --output text)
if [[ $LIST_OF_TRAILS ]];then
for trail in $LIST_OF_TRAILS;do
LOGFILEVALIDATION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].LogFileValidationEnabled' --output text --trail-name-list $trail)
if [[ $LOGFILEVALIDATION_TRAIL_STATUS == 'False' ]];then
echo -e " $RED WARNING! $trail trail in $REGION has not log file validation enabled$NORMAL"
else
echo -e " $OK OK! $trail trail in $REGION has log file validation enabled$NORMAL"
fi
done
else
echo -e " $RED WARNING! No CloudTrail trails found!$NORMAL"
fi
}
check23(){
TITLE23="$BLUE 2.3$NORMAL Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)"
echo -e "\n$TITLE23"
CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text --profile $PROFILE --region $REGION)
if [[ $CLOUDTRAILBUCKET ]];then
for bucket in $CLOUDTRAILBUCKET;do
CLOUDTRAILBUCKET_HASALLPERMISIONS=$($AWSCLI s3api get-bucket-acl --bucket $bucket --query 'Grants[?Grantee.URI==`http://acs.amazonaws.com/groups/global/AllUsers`]' --profile $PROFILE --region $REGION --output text)
if [[ $CLOUDTRAILBUCKET_HASALLPERMISIONS ]];then
echo -e " $RED WARNING! check your $bucket CloudTrail bucket ACL and Policy!$NORMAL"
else
echo -e " $OK OK! Bucket $bucket is set correctly $NORMAL"
fi
done
else
echo -e " $RED WARNING! No CloudTrail bucket found!$NORMAL"
fi
}
check24(){
TITLE24="$BLUE 2.4$NORMAL Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)"
echo -e "\n$TITLE24"
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].Name' --output text)
if [[ $LIST_OF_TRAILS ]];then
for trail in $LIST_OF_TRAILS;do
TRAIL_REGION=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --trail-name-list "$trail" --query 'trailList[*].HomeRegion' --output text)
LATESTDELIVERY_TIMESTAMP=$($AWSCLI cloudtrail get-trail-status --name $trail --profile $PROFILE --region $TRAIL_REGION --query 'LatestCloudWatchLogsDeliveryTime' --output text|grep -v None)
if [[ ! $LATESTDELIVERY_TIMESTAMP ]];then
echo -e " $RED $trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)$NORMAL"
else
LATESTDELIVERY_DATE=$(timestamp_to_date $LATESTDELIVERY_TIMESTAMP)
HOWOLDER=$(how_older_from_today $LATESTDELIVERY_DATE)
if [ $HOWOLDER -gt "1" ];then
echo -e " $RED $trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)$NORMAL"
else
echo -e " $OK $trail trail has been logging during the last 24h (it is in $TRAIL_REGION)$NORMAL"
fi
fi
done
else
echo -e " $RED WARNING! No CloudTrail trails found!$NORMAL"
fi
}
check25(){
TITLE25="$BLUE 2.5$NORMAL Ensure AWS Config is enabled in all regions (Scored)"
echo -e "\n$TITLE25"
for regx in $REGIONS; do
CHECK_AWSCONFIG_STATUS=$($AWSCLI configservice get-status --profile $PROFILE --region $regx | grep "recorder: ON")
if [[ $CHECK_AWSCONFIG_STATUS ]];then
echo -e " $OK Region $regx has AWS Config recorder: ON $NORMAL"
else
echo -e " $RED WARNING! Region $regx has AWS Config disabled or not configured$NORMAL"
fi
done
}
check26(){
TITLE26="$BLUE 2.6$NORMAL Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)"
echo -e "\n$TITLE26"
CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text --profile $PROFILE --region $REGION)
if [[ $CLOUDTRAILBUCKET ]];then
for bucket in $CLOUDTRAILBUCKET;do
CLOUDTRAILBUCKET_LOGENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket --profile $PROFILE --region $REGION --query 'LoggingEnabled.TargetBucket' --output text|grep -v None)
if [[ $CLOUDTRAILBUCKET_LOGENABLED ]];then
echo -e " $OK OK! It is enabled in $bucket $NORMAL"
else
echo -e " $RED WARNING! access logging is not enabled in $bucket CloudTrail S3 bucket!$NORMAL"
fi
done
else
echo -e " $RED WARNING! CloudTrail bucket not found!$NORMAL"
fi
}
check27(){
TITLE27="$BLUE 2.7$NORMAL Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)"
echo -e "\n$TITLE27"
CLOUDTRAILNAME=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].Name' --output text --profile $PROFILE --region $REGION)
if [[ $CLOUDTRAILNAME ]];then
for trail in $CLOUDTRAILNAME;do
CLOUDTRAILENC_ENABLED=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --trail $trail --query 'trailList[*].KmsKeyId' --output text)
if [[ $CLOUDTRAILENC_ENABLED ]];then
echo -e " $OK OK! KMS key found for $trail $NORMAL"
else
echo -e " $RED WARNING! encryption is not enabled in your CloudTrail trail $trail, KMS key not found!$NORMAL"
fi
done
else
echo -e " $RED WARNING! CloudTrail bucket doesn't exist!$NORMAL"
fi
}
check28(){
TITLE28="$BLUE 2.8$NORMAL Ensure rotation for customer created CMKs is enabled (Scored)"
echo -e "\n$TITLE28"
for regx in $REGIONS; do
CHECK_KMS_KEYLIST=$($AWSCLI kms list-keys --profile $PROFILE --region $regx --output text --query 'Keys[*].KeyId')
if [[ $CHECK_KMS_KEYLIST ]];then
for key in $CHECK_KMS_KEYLIST; do
CHECK_KMS_KEY_TYPE=$($AWSCLI kms describe-key --key-id $key --profile $PROFILE --region $regx --query 'KeyMetadata.Origin' | sed 's/["]//g')
if [[ $CHECK_KMS_KEY_TYPE == "EXTERNAL" ]];then
echo -e " $BLUE Key $key in Region $regx Customer Uploaded Key Material.$NORMAL"
else
CHECK_KMS_KEY_ROTATION=$($AWSCLI kms get-key-rotation-status --key-id $key --profile $PROFILE --region $regx --output text)
CHECK_KMS_DEFAULT_KEY=$($AWSCLI kms describe-key --key-id $key --profile $PROFILE --region $regx --query 'KeyMetadata.Description' | sed -n '/Default master key that protects my /p')
if [[ $CHECK_KMS_KEY_ROTATION == "True" ]];then
echo -e " $OK OK! Key $key in Region $regx is set correctly$NORMAL"
elif [[ $CHECK_KMS_KEY_ROTATION == "False" && $CHECK_KMS_DEFAULT_KEY ]];then
echo -e " $NOTICE Region $regx key $key is an AWS default master key and cannot be deleted nor modified.$NORMAL"
else
echo -e " $RED WARNING! Key $key in Region $regx is not set to rotate!!!$NORMAL"
fi
fi
done
else
echo -e " $NOTICE Region $regx doesn't have encryption keys $NORMAL"
fi
done
}
check31(){
TITLE31="$BLUE 3.1$NORMAL Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)"
echo -e "\n$TITLE31 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep AccessDenied)
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for Access Denied enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check32(){
TITLE32="$BLUE 3.2$NORMAL Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)"
echo -e "\n$TITLE32 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'userIdentity.sessionContext.attributes.mfaAuthenticated.*true')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for sign-in Console without MFA enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check33(){
TITLE33="$BLUE 3.3$NORMAL Ensure a log metric filter and alarm exist for usage of root account (Scored)"
echo -e "\n$TITLE33 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION |grep -E 'userIdentity.*Root.*AwsServiceEvent')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for usage of root account enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check34(){
TITLE34="$BLUE 3.4$NORMAL Ensure a log metric filter and alarm exist for IAM policy changes (Scored)"
echo -e "\n$TITLE34 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'DeleteGroupPolicy.*DeleteRolePolicy.*DeleteUserPolicy.*PutGroupPolicy.*PutRolePolicy.*PutUserPolicy.*CreatePolicy.*DeletePolicy.*CreatePolicyVersion.*DeletePolicyVersion.*AttachRolePolicy.*DetachRolePolicy.*AttachUserPolicy.*DetachUserPolicy.*AttachGroupPolicy.*DetachGroupPolicy')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for IAM policy changes enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check35(){
TITLE35="$BLUE 3.5$NORMAL Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)"
echo -e "\n$TITLE35 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateTrail.*UpdateTrail.*DeleteTrail.*StartLogging.*StopLogging')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for CloudTrail configuration changes enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check36(){
TITLE36="$BLUE 3.6$NORMAL Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)"
echo -e "\n$TITLE36 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'ConsoleLogin.*Failed')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for usage of root account enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check37(){
TITLE37="$BLUE 3.7$NORMAL Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)"
echo -e "\n$TITLE37 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'kms.amazonaws.com.*DisableKey.*ScheduleKeyDeletion')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check38(){
TITLE38="$BLUE 3.8$NORMAL Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)"
echo -e "\n$TITLE38 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 's3.amazonaws.com.*PutBucketAcl.*PutBucketPolicy.*PutBucketCors.*PutBucketLifecycle.*PutBucketReplication.*DeleteBucketPolicy.*DeleteBucketCors.*DeleteBucketLifecycle.*DeleteBucketReplication')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check39(){
TITLE39="$BLUE 3.9$NORMAL Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)"
echo -e "\n$TITLE39 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'config.amazonaws.com.*StopConfigurationRecorder.*DeleteDeliveryChannel.*PutDeliveryChannel.*PutConfigurationRecorder')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check310(){
TITLE310="$BLUE 3.10$NORMAL Ensure a log metric filter and alarm exist for security group changes (Scored)"
echo -e "\n$TITLE310 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'AuthorizeSecurityGroupIngress.*AuthorizeSecurityGroupEgress.*RevokeSecurityGroupIngress.*RevokeSecurityGroupEgress.*CreateSecurityGroup.*DeleteSecurityGroup')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check311(){
TITLE311="$BLUE 3.11$NORMAL Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)"
echo -e "\n$TITLE311 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateNetworkAcl.*CreateNetworkAclEntry.*DeleteNetworkAcl.*DeleteNetworkAclEntry.*ReplaceNetworkAclEntry.*ReplaceNetworkAclAssociation')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check312(){
TITLE312="$BLUE 3.12$NORMAL Ensure a log metric filter and alarm exist for changes to network gateways (Scored)"
echo -e "\n$TITLE312 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateCustomerGateway.*DeleteCustomerGateway.*AttachInternetGateway.*CreateInternetGateway.*DeleteInternetGateway.*DetachInternetGateway')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check313(){
TITLE313="$BLUE 3.13$NORMAL Ensure a log metric filter and alarm exist for route table changes (Scored)"
echo -e "\n$TITLE313 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateRoute.*CreateRouteTable.*ReplaceRoute.*ReplaceRouteTableAssociation.*DeleteRouteTable.*DeleteRoute.*DisassociateRouteTable')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check314(){
TITLE314="$BLUE 3.14$NORMAL Ensure a log metric filter and alarm exist for VPC changes (Scored)"
echo -e "\n$TITLE314 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateVpc.*DeleteVpc.*ModifyVpcAttribute.*AcceptVpcPeeringConnection.*CreateVpcPeeringConnection.*DeleteVpcPeeringConnection.*RejectVpcPeeringConnection.*AttachClassicLinkVpc.*DetachClassicLinkVpc.*DisableVpcClassicLink.*EnableVpcClassicLink')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check315(){
TITLE315="$BLUE 3.15$NORMAL Ensure appropriate subscribers to each SNS topic (Not Scored)"
echo -e "\n$TITLE315 "
for regx in $REGIONS; do
TOPICS_LIST=$($AWSCLI sns list-topics --profile $PROFILE --region $regx --output text --query 'Topics[*].TopicArn')
if [[ $TOPICS_LIST ]];then
for topic in $TOPICS_LIST; do
CHECK_TOPIC_LIST=$($AWSCLI sns list-subscriptions-by-topic --topic-arn $topic --profile $PROFILE --region $regx --query 'Subscriptions[*].{Endpoint:Endpoint,Protocol:Protocol}' --output text --max-items $MAXITEMS | grep -v "None")
if [[ $CHECK_TOPIC_LIST ]]; then
TOPIC_SHORT=$(echo $topic | awk -F: '{ print $7 }')
echo -e " $NOTICE Region $regx with Topic $TOPIC_SHORT: $NORMAL "
echo -e " $NOTICE - Suscription: $CHECK_TOPIC_LIST $NORMAL"
else
echo -e " $RED WARNING! No suscription found in: Region $regx and Topic $topic $NORMAL"
echo -e " $RED - Region $regx and Topic $topic $NORMAL"
fi
done
else
echo -e " $NOTICE Region $regx doesn't have topics $NORMAL"
fi
done
}
check41(){
TITLE41="$BLUE 4.1$NORMAL Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)"
echo -e "\n$TITLE41 "
for regx in $REGIONS; do
SG_LIST=$($AWSCLI ec2 describe-security-groups --filters "Name=ip-permission.to-port,Values=22" --query 'SecurityGroups[?length(IpPermissions[?ToPort==`22` && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupName: GroupName}' --profile $PROFILE --region $regx --output text)
if [[ $SG_LIST ]];then
for SG in $SG_LIST;do
echo -e " $RED Found Security Group: $SG open to 0.0.0.0/0 in Region $regx $NORMAL "
done
else
echo -e " $OK OK! No Security Groups found in $regx with port 22 TCP open to 0.0.0.0/0 $NORMAL "
fi
done
}
check42(){
TITLE42="$BLUE 4.2$NORMAL Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)"
echo -e "\n$TITLE42 "
for regx in $REGIONS; do
SG_LIST=$($AWSCLI ec2 describe-security-groups --filters "Name=ip-permission.to-port,Values=3389" --query 'SecurityGroups[?length(IpPermissions[?ToPort==`3389` && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupName: GroupName}' --profile $PROFILE --region $regx --output text)
if [[ $SG_LIST ]];then
for SG in $SG_LIST;do
echo -e " $RED Found Security Group: $SG open to 0.0.0.0/0 in Region $regx $NORMAL "
done
else
echo -e " $OK OK! No Security Groups found in $regx with port 3389 TCP open to 0.0.0.0/0 $NORMAL "
fi
done
}
check43(){
TITLE43="$BLUE 4.3$NORMAL Ensure VPC Flow Logging is Enabled in all VPCs (Scored)"
echo -e "\n$TITLE43 "
for regx in $REGIONS; do
CHECK_FL=$($AWSCLI ec2 describe-flow-logs --profile $PROFILE --region $regx --query 'FlowLogs[?FlowLogStatus==`ACTIVE`].LogGroupName' --output text)
if [[ $CHECK_FL ]];then
for FL in $CHECK_FL;do
echo -e " $OK OK! VPCFlowLog is enabled for LogGroupName: $FL in Region $regx $NORMAL "
done
else
echo -e " $RED WARNING! No VPCFlowLog has been found in Region $regx $NORMAL "
fi
done
}
check44(){
TITLE44="$BLUE 4.4$NORMAL Ensure the default security group of every VPC restricts all traffic (Scored)"
echo -e "\n$TITLE44 "
for regx in $REGIONS; do
CHECK_SGDEFAULT=$($AWSCLI ec2 describe-security-groups --profile $PROFILE --region $regx --filters Name=group-name,Values='default' --query 'SecurityGroups[*].{IpPermissions:IpPermissions,IpPermissionsEgress:IpPermissionsEgress,GroupId:GroupId}' --output text |grep 0.0.0.0)
if [[ $CHECK_SGDEFAULT ]];then
echo -e " $RED WARNING! Default Security Groups found that allow 0.0.0.0 IN or OUT traffic in Region $regx $NORMAL "
else
echo -e " $OK OK! No Default Security Groups open to 0.0.0.0 found in Region $regx $NORMAL "
fi
done
}
check45(){
#set -xe
TITLE45="$BLUE 4.5$NORMAL Ensure routing tables for VPC peering are \"least access\" (Not Scored)"
echo -e "\n$TITLE45 "
echo -e " $NOTICE Looking for VPC peering in all regions... $NORMAL "
for regx in $REGIONS; do
LIST_OF_VPCS_PEERING_CONNECTIONS=$($AWSCLI ec2 describe-vpc-peering-connections --output text --profile $PROFILE --region $regx --query 'VpcPeeringConnections[*].VpcPeeringConnectionId')
if [[ $LIST_OF_VPCS_PEERING_CONNECTIONS ]];then
echo -e " $NOTICE $regx: $LIST_OF_VPCS_PEERING_CONNECTIONS, review its routing tables $NORMAL "
#LIST_OF_VPCS=$($AWSCLI ec2 describe-vpcs --profile $PROFILE --region $regx --query 'Vpcs[*].VpcId' --output text)
#aws ec2 describe-route-tables --filter "Name=vpc-id,Values=vpc-0213e864" --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" --profile $PROFILE --region $regx
# for vpc in $LIST_OF_VPCS; do
# VPCS_WITH_PEERING=$($AWSCLI ec2 describe-route-tables --filter "Name=vpc-id,Values=$vpc" --profile $PROFILE --region $regx --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" |grep GatewayId|grep pcx-)
# done
#echo $VPCS_WITH_PEERING
else
echo -e " $OK $regx: No VPC peering found $NORMAL "
fi
done
}
callCheck(){
if [[ $CHECKNUMBER ]];then
case "$CHECKNUMBER" in
check11) check11;cleanTemp;exit;;
check12) check12;cleanTemp;exit;;
check13) check13;cleanTemp;exit;;
check14) check14;cleanTemp;exit;;
check15) check15;cleanTemp;exit;;
check16) check16;cleanTemp;exit;;
check17) check17;cleanTemp;exit;;
check18) check18;cleanTemp;exit;;
check19) check19;cleanTemp;exit;;
check110) check110;cleanTemp;exit;;
check111) check111;cleanTemp;exit;;
check112) check112;cleanTemp;exit;;
check113) check113;cleanTemp;exit;;
check114) check114;cleanTemp;exit;;
check115) check115;cleanTemp;exit;;
check116) check116;cleanTemp;exit;;
check117) check117;cleanTemp;exit;;
check118) check118;cleanTemp;exit;;
check119) check119;cleanTemp;exit;;
check120) check120;cleanTemp;exit;;
check121) check121;cleanTemp;exit;;
check122) check122;cleanTemp;exit;;
check123) check123;cleanTemp;exit;;
check124) check124;cleanTemp;exit;;
check21) check21;cleanTemp;exit;;
check22) check22;cleanTemp;exit;;
check23) check23;cleanTemp;exit;;
check24) check24;cleanTemp;exit;;
check25) check25;cleanTemp;exit;;
check26) check26;cleanTemp;exit;;
check27) check27;cleanTemp;exit;;
check28) check28;cleanTemp;exit;;
check31) check31;cleanTemp;exit;;
check32) check32;cleanTemp;exit;;
check33) check33;cleanTemp;exit;;
check34) check34;cleanTemp;exit;;
check35) check35;cleanTemp;exit;;
check36) check36;cleanTemp;exit;;
check37) check37;cleanTemp;exit;;
check38) check38;cleanTemp;exit;;
check39) check39;cleanTemp;exit;;
check310) check310;cleanTemp;exit;;
check311) check311;cleanTemp;exit;;
check312) check312;cleanTemp;exit;;
check313) check313;cleanTemp;exit;;
check314) check314;cleanTemp;exit;;
check315) check315;cleanTemp;exit;;
check41) check41;cleanTemp;exit;;
check42) check42;cleanTemp;exit;;
check43) check43;cleanTemp;exit;;
check44) check44;cleanTemp;exit;;
check45) check45;cleanTemp;exit;;
check1) check11;check12;check13;check14;check15;check16;check17;check18;check19;check110;check111;check112;check113;check114;check115;check116;check117;check118;check119;check120;check121;check122;check123;check124;cleanTemp;exit;;
check2) check21;check22;check23;check24;check25;check26;check27;check28;cleanTemp;exit;;
check3) check31;check32;check33;check34;check35;check36;check37;check38;check39;check310;check311;check312;check313;check314;check315;cleanTemp;exit;;
check4) check41;check42;check43;check44;check45;cleanTemp;exit;;
* ) echo -e "\n$RED ERROR! Use a valid check name (i.e. check41) $NORMAL\n";exit;;
esac
fi
}
callCheck
TITLE1="$BLUE 1 Identity and Access Management *********************************$NORMAL"
echo -e "\n\n$TITLE1 "
check11
check12
check13
check14
check15
check16
check17
check18
check19
check110
check111
check112
check113
check114
check115
check119
check120
check121
check122
check123
check124
TITLE2="$BLUE 2 Logging ********************************************************$NORMAL"
echo -e "\n\n$TITLE2 "
check21
check22
check23
check24
check25
check26
check27
check28
TITLE3="$BLUE 3 Monitoring *****************************************************"
echo -e "\n\n$TITLE3 "
# 3 Monitoring check commands / Mostly covered by SecurityMonkey
check31
check32
check33
check34
check35
check36
check37
check38
check39
check310
check311
check312
check313
check314
check315
TITLE4="$BLUE 4 Networking **************************************************$NORMAL"
echo -e "\n\n$TITLE4 "
check41
check42
check43
check44
check45
infoReferenceLong
cleanTemp
# Prowler is a tool that provides automate auditing and hardening guidance of an AWS account.
# It is based on AWS-CLI commands. It follows guidelines present in the CIS Amazon
# Web Services Foundations Benchmark at:
# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf
# This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0
# International Public License. The link to the license terms can be found at
# https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
#
# Author: Toni de la Fuente - @ToniBlyx / Alfresco Software Inc.
# Prowler - Iron Maiden
#
# Walking through the city, looking oh so pretty
# I've just got to find my way
# See the ladies flashing
# All there legs and lashes
# I've just got to find my way...
# Exit if a pipeline results in an error.
# set -ue
# set -o pipefail
# set -vx
# Exits if any error is found
# set -e
# Colors
NORMAL=""
WARNING="" # Bad (red)
SECTION="" # Section (yellow)
NOTICE="" # Notice (yellow)
OK="" # Ok (green)
BAD="" # Bad (red)
CYAN=""
BLUE=""
BROWN=""
DARKGRAY=""
GRAY=""
GREEN=""
MAGENTA=""
PURPLE=""
RED=""
YELLOW=""
WHITE=""
# Set the defaults for these getopts variables
PROFILE="default"
REGION="us-east-1"
FILTERREGION=""
MAXITEMS=100
# Command usage menu
usage(){
echo -e "\nUSAGE:
`basename $0` -p <profile> -r <region> [ -h ]
Options:
-p <profile> specify your AWS profile to use (i.e.: default)
-r <region> specify an AWS region to direct API requests to (i.e.: us-east-1)
-c <checknum> specify a check number or group from the AWS CIS benchmark (i.e.: check11 for check 1.1 or check3 for entire section 3)
-f <filterregion> specify an AWS region to run checks against (i.e.: us-west-1)
-m <maxitems> specify the maximum number of items to return for long-running requests (default: 100)
-h this help
"
exit
}
while getopts "hp:r:c:f:m:" OPTION; do
case $OPTION in
h )
usage
exit 1
;;
p )
PROFILE=$OPTARG
;;
r )
REGION=$OPTARG
;;
c )
CHECKNUMBER=$OPTARG
;;
f )
FILTERREGION=$OPTARG
;;
m )
MAXITEMS=$OPTARG
;;
: )
echo -e "\n$RED ERROR!$NORMAL -$OPTARG requires an argument\n"
exit 1
;;
? )
echo -e "\n$RED ERROR!$NORMAL Invalid option"
usage
exit 1
;;
esac
done
# Functions to manage dates depending on OS
if [[ "$OSTYPE" == "linux-gnu" ]]; then
# function to compare in days, usage how_older_from_today date
# date format %Y-%m-%d
how_older_from_today()
{
DATE_TO_COMPARE=$1
TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s)
DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s)
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
echo $DAYS_SINCE
}
# function to convert from timestamp to date, usage timestamp_to_date timestamp
# output date format %Y-%m-%d
timestamp_to_date()
{
# remove fractions of a second
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
echo $OUTPUT_DATE
}
decode_report()
{
base64 -d
}
elif [[ "$OSTYPE" == "darwin"* ]]; then
# BSD/OSX coommands compatibility
how_older_from_today()
{
DATE_TO_COMPARE=$1
TODAY_IN_DAYS=$(date +%s)
DATE_FROM_IN_DAYS=$(date -jf %Y-%m-%d $DATE_TO_COMPARE +%s)
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
echo $DAYS_SINCE
}
timestamp_to_date()
{
# remove fractions of a second
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
OUTPUT_DATE=$(date -r $TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
echo $OUTPUT_DATE
}
decode_report()
{
base64 -D
}
elif [[ "$OSTYPE" == "cygwin" ]]; then
# POSIX compatibility layer and Linux environment emulation for Windows
how_older_from_today()
{
DATE_TO_COMPARE=$1
TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s)
DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s)
DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
echo $DAYS_SINCE
}
timestamp_to_date()
{
# remove fractions of a second
TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
echo $OUTPUT_DATE
}
decode_report()
{
base64 -d
}
else
echo "Unknown Operating System"
exit
fi
if [[ ! -f ~/.aws/credentials ]]; then
echo -e "\n$RED ERROR!$NORMAL AWS credentials file not found (~/.aws/credentials). Run 'aws configure' first. \n"
return 1
fi
# AWS-CLI variables
AWSCLI=$(which aws)
if [ -z "${AWSCLI}" ]; then
echo -e "\n$RED ERROR!$NORMAL AWS-CLI (aws command) not found. Make sure it is installed correctly and in your \$PATH\n"
exit
fi
# if this script runs in an AWS instance
# INSTANCE_PROFILE=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/)
# AWS_ACCESS_KEY_ID=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g')
# AWS_SECRET_ACCESS_KEY_ID=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g')
# AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
# AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY_ID}
prowlerBanner() {
echo -e "$CYAN _"
echo -e " _ __ _ __ _____ _| | ___ _ __"
echo -e " | '_ \| '__/ _ \ \ /\ / / |/ _ \ '__|"
echo -e " | |_) | | | (_) \ V V /| | __/ |"
echo -e " | .__/|_| \___/ \_/\_/ |_|\___|_|"
echo -e " |_|$NORMAL$BLUE CIS based AWS Account Hardening Tool$NORMAL\n"
}
# Get whoami in AWS, who is the user running this shell script
getWhoami() {
echo -e "\nThis report is being generated using credentials below:\n"
echo -e "AWS-CLI Profile: $NOTICE[$PROFILE]$NORMAL AWS API Region: $NOTICE[$REGION]$NORMAL AWS Filter Region: $NOTICE[${FILTERREGION:-all}]\n"
$AWSCLI sts get-caller-identity --output table --profile $PROFILE --region $REGION
}
printCurrentDate(){
echo -e "\nDate: $NOTICE$(date)$NORMAL"
}
printColorsCode(){
echo -e "\nColors Code for results: $NOTICE INFORMATIVE$NORMAL,$OK OK (RECOMMENDED VALUE)$NORMAL, $BAD WARNING (FIX REQUIRED)$NORMAL \n"
}
# Generate Credential Report
genCredReport() {
echo -en '\nGenerating AWS IAM Credential Report...'
until $AWSCLI iam generate-credential-report --output text --query 'State' --profile $PROFILE --region $REGION |grep -m 1 "COMPLETE"; do
sleep 1
echo -n "."
done
}
# Save report to a file, decode it, deletion at finish and after every single check, acb stands for AWS CIS Benchmark
saveReport(){
TEMP_REPORT_FILE=/tmp/.acb
$AWSCLI iam get-credential-report --query 'Content' --output text --profile $PROFILE --region $REGION | decode_report > $TEMP_REPORT_FILE
}
# Delete temporary report file
cleanTemp(){
rm -fr $TEMP_REPORT_FILE
}
# Get a list of all available AWS Regions
REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \
--output text \
--profile $PROFILE \
--region $REGION \
--region-names $FILTERREGION)
infoReferenceLong(){
# Report review note:
echo -e " $BLUE \n*********************************$NORMAL"
echo -e " $NOTICE For more information: $NORMAL"
echo -e " $NOTICE https://benchmarks.cisecurity.org/tools2/amazon/CIS_Amazon_Web_Services_Foundations_Benchmark_v1.1.0.pdf$NORMAL"
echo -e " $NOTICE For bugs or feedback: $NORMAL"
echo -e " $NOTICE https://github.com/Alfresco/aws-cis-security-benchmark/issues$NORMAL"
}
infoReferenceShort(){
# Report review note:
echo -e " $NOTICE http://bit.ly/2g3PEf7$NORMAL"
}
prowlerBanner
printCurrentDate
getWhoami
printColorsCode
genCredReport
saveReport
check11(){
TITLE11="$BLUE 1.1$NORMAL Avoid the use of the root account (Scored). Last time root account was used
(password last used, access_key_1_last_used, access_key_2_last_used): "
COMMAND11=$(cat $TEMP_REPORT_FILE| grep '<root_account>' | cut -d, -f5,11,16 | sed 's/,/,\ /g')
echo -e "\n$TITLE11"
echo -e " $NOTICE $COMMAND11 $NORMAL"
}
check12(){
TITLE12="$BLUE 1.2$NORMAL Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)"
# List users with password enabled
COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }')
COMMAND12=$(
for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do
cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$8 }' |grep -w $i| grep false | awk '{ print $1 }'|tr '\n' ' ';
done)
echo -e "\n$TITLE12"
if [[ $COMMAND12 ]]; then
echo -e " List of users with Password enabled but MFA disabled:"
echo -e " $RED $COMMAND12 $NORMAL"
else
echo -e " $OK OK! No users found with Password enabled and MFA disabled $NORMAL"
fi
}
check13(){
TITLE13="$BLUE 1.3$NORMAL Ensure credentials unused for 90 days or greater are disabled (Scored)"
echo -e "\n$TITLE13 "
COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }')
if [[ $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED ]]; then
COMMAND13=$(
for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do
cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$5 }' |grep $i| awk '{ print $1 }'|tr '\n' ' ';
done)
# list of users that have used password
USERS_PASSWORD_USED=$($AWSCLI iam list-users --query "Users[?PasswordLastUsed].UserName" --output text --profile $PROFILE --region $REGION)
if [[ $USERS_PASSWORD_USED ]]; then
# look for users with a password last used more or equal to 90 days
for i in $USERS_PASSWORD_USED; do
DATEUSED=$($AWSCLI iam list-users --query "Users[?UserName=='$i'].PasswordLastUsed" --output text --profile $PROFILE --region $REGION | cut -d'T' -f1)
HOWOLDER=$(how_older_from_today $DATEUSED)
if [ $HOWOLDER -gt "90" ];then
echo " $RED $i $NORMAL"
else
echo " $OK OK! User \"$i\" found with unused credentials for 90 days or greater $NORMAL"
fi
done
fi
else
echo " $OK OK! No users found with password enabled $NORMAL"
fi
}
check14(){
TITLE14="$BLUE 1.4$NORMAL Ensure access keys are rotated every 90 days or less (Scored)" # also checked by Security Monkey
LIST_OF_USERS_WITH_ACCESS_KEY1=$(cat $TEMP_REPORT_FILE| awk -F, '{ print $1, $9 }' |grep "\ true" | awk '{ print $1 }')
LIST_OF_USERS_WITH_ACCESS_KEY2=$(cat $TEMP_REPORT_FILE| awk -F, '{ print $1, $14 }' |grep "\ true" | awk '{ print $1 }')
echo -e "\n$TITLE14 "
echo -e " Users with access key 1 older than 90 days: "
for user in $LIST_OF_USERS_WITH_ACCESS_KEY1; do
# check access key 1
DATEROTATED1=$(cat $TEMP_REPORT_FILE | grep $user| awk -F, '{ print $10 }' | grep -v "N/A" | awk -F"T" '{ print $1 }')
HOWOLDER=$(how_older_from_today $DATEROTATED1)
if [ $HOWOLDER -gt "90" ];then
echo -e " $RED $user $NORMAL"
fi
done
echo -e " Users with access key 2 older than 90 days: "
for user in $LIST_OF_USERS_WITH_ACCESS_KEY2; do
# check access key 2
DATEROTATED2=$(cat $TEMP_REPORT_FILE | grep $user| awk -F, '{ print $10 }' | grep -v "N/A" | awk -F"T" '{ print $1 }')
HOWOLDER=$(how_older_from_today $DATEROTATED2)
if [ $HOWOLDER -gt "90" ];then
echo -e " $RED $user $NORMAL"
fi
done
}
check15(){
TITLE15="$BLUE 1.5$NORMAL Ensure IAM password policy requires at least one uppercase letter (Scored)"
COMMAND15=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireUppercaseCharacters') # must be true
echo -e "\n$TITLE15 "
if [[ $COMMAND15 == "true" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check16(){
TITLE16="$BLUE 1.6$NORMAL Ensure IAM password policy require at least one lowercase letter (Scored)"
COMMAND16=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireLowercaseCharacters') # must be true
echo -e "\n$TITLE16 "
if [[ $COMMAND16 == "true" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check17(){
TITLE17="$BLUE 1.7$NORMAL Ensure IAM password policy require at least one symbol (Scored)"
COMMAND17=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireSymbols') # must be true
echo -e "\n$TITLE17 "
if [[ $COMMAND17 == "true" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check18(){
TITLE18="$BLUE 1.8$NORMAL Ensure IAM password policy require at least one number (Scored)"
COMMAND18=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireNumbers') # must be true
echo -e "\n$TITLE18 "
if [[ $COMMAND18 == "true" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check19(){
TITLE19="$BLUE 1.9$NORMAL Ensure IAM password policy requires minimum length of 14 or greater (Scored)"
COMMAND19=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.MinimumPasswordLength')
echo -e "\n$TITLE19 "
if [[ $COMMAND19 -gt "13" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check110(){
TITLE110="$BLUE 1.10$NORMAL Ensure IAM password policy prevents password reuse (Scored)"
COMMAND110=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.PasswordReusePrevention' --output text)
echo -e "\n$TITLE110 "
if [[ $COMMAND110 ]];then
if [[ $COMMAND110 -gt "23" ]];then
echo -e " $OK OK! $NORMAL"
else
echo -e " $RED WARNING! It is not set or it is set lower than 24 $NORMAL"
fi
else
echo -e " $RED WARNING! It is not set $NORMAL"
fi
}
check111(){
TITLE111="$BLUE 1.11$NORMAL Ensure IAM password policy expires passwords within 90 days or less (Scored)"
COMMAND111=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION | grep MaxPasswordAge | awk -F: '{ print $2 }'|sed 's/\ //g'|sed 's/,/ /g')
echo -e "\n$TITLE111 "
if [[ $COMMAND111 ]];then
if [ $COMMAND111 == "90" ];then
echo -e " $OK OK! $NORMAL"
fi
else
echo -e " $RED WARNING! $NORMAL"
fi
}
check112(){
TITLE112="$BLUE 1.12$NORMAL Ensure no root account access key exists (Scored)"
# ensure the access_key_1_active and access_key_2_active fields are set to FALSE.
ROOTKEY1=$(cat $TEMP_REPORT_FILE |grep root_account|awk -F',' '{ print $9 }')
ROOTKEY2=$(cat $TEMP_REPORT_FILE |grep root_account|awk -F',' '{ print $14 }')
echo -e "\n$TITLE112 "
if [ $ROOTKEY1 == "false" ];then
echo -e " $OK OK! $NORMAL No access key 1 found "
else
echo -e " $RED Found access key 1 $NORMAL"
fi
if [ $ROOTKEY2 == "false" ];then
echo -e " $OK OK! $NORMAL No access key 2 found "
else
echo -e " $RED Found access key 2 $NORMAL"
fi
}
check113(){
TITLE113="$BLUE 1.13$NORMAL Ensure MFA is enabled for the root account (Scored)"
COMMAND113=$($AWSCLI iam get-account-summary --profile $PROFILE --region $REGION |grep AccountMFAEnabled | awk -F': ' '{ print $2 }'|sed 's/,//')
echo -e "\n$TITLE113"
if [ $COMMAND113 == "1" ]; then
echo " $OK OK! $NORMAL Virtual MFA is enabled. "
else
echo " $RED WARNING! MFA is not ENABLED for root account $NORMAL"
fi
}
check114(){
TITLE114="$BLUE 1.14$NORMAL Ensure hardware MFA is enabled for the root account (Scored)"
COMMAND113=$($AWSCLI iam get-account-summary --profile $PROFILE --region $REGION |grep AccountMFAEnabled | awk -F': ' '{ print $2 }'|sed 's/,//')
echo -e "\n$TITLE114"
if [ $COMMAND113 == "1" ]; then
COMMAND114=$($AWSCLI iam list-virtual-mfa-devices --profile $PROFILE --region $REGION --query 'VirtualMFADevices' --output text|grep :root |wc -l)
if [ $COMMAND114 == "1" ]; then
echo " $OK OK! $NORMAL Virtual MFA is enabled. "
else
echo " $OK OK! $NORMAL Hardware MFA is enabled. "
fi
else
echo " $RED WARNING! MFA is not ENABLED for root account $NORMAL"
fi
}
check115(){
TITLE115="$BLUE 1.15$NORMAL Ensure security questions are registered in the AWS account (Not Scored)"
# No command available
echo -e "\n$TITLE115"
echo -e " $NOTICE No command available for check 1.14"
echo -e " Login to the AWS Console as root, click on the Account "
echo -e " Name -> My Account -> Configure Security Challenge Questions $NORMAL"
}
check116(){
TITLE115="$BLUE 1.16$NORMAL Ensure IAM policies are attached only to groups or roles (Scored)"
echo -e "\n$TITLE116"
LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text --profile $PROFILE --region $REGION)
echo -e " Users with policy attached to them instead to groups: (it may take few seconds...) "
for user in $LIST_USERS;do
USER_POLICY=$($AWSCLI iam list-attached-user-policies --output text --profile $PROFILE --region $REGION --user-name $user)
if [[ $USER_POLICY ]]; then
echo -e " $RED $user $NORMAL"
fi
done
}
check117(){
TITLE117="$BLUE 1.17$NORMAL Enable detailed billing (Scored)"
# No command available
echo -e "\n$TITLE117 "
echo -e " $NOTICE No command available for check 1.17"
echo -e " See section 1.17 on the CIS Benchmark guide for details $NORMAL"
infoReferenceShort
}
check118(){
TITLE118="$BLUE 1.18$NORMAL Ensure IAM Master and IAM Manager roles are active (Scored)"
echo -e "\n$TITLE118 "
FINDMASTERANDMANAGER=$($AWSCLI iam list-roles --profile $PROFILE --region $REGION --query "Roles[*].{RoleName:RoleName}" --output text | grep -E 'Master|Manager'| tr '\n' ' ')
if [[ $FINDMASTERANDMANAGER ]];then
echo -e " $NOTICE Found next roles as possible IAM Master and IAM Manager candidates: $NORMAL"
echo -e " $NOTICE $FINDMASTERANDMANAGER $NORMAL"
echo -e "\n $NOTICE INFO: run the commands below to check their policies with section 1.18 in the guide... $NORMAL"
for role in $FINDMASTERANDMANAGER;do
# find inline policies in found roles
INLINEPOLICIES=$($AWSCLI iam list-role-policies --role-name $role --profile $PROFILE --region $REGION --query "PolicyNames[*]" --output text)
for policy in $INLINEPOLICIES;do
echo " $NOTICE $AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION$NORMAL"
done
# find attached policies in found roles
ATTACHEDPOLICIES=$($AWSCLI iam list-attached-role-policies --role-name $role --profile $PROFILE --region $REGION --query "AttachedPolicies[*]" --output text)
for policy in $ATTACHEDPOLICIES;do
echo " $NOTICE $AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION$NORMAL"
done
done
else
echo -e " $RED WARNING! IAM Master and IAM Manager roles not found$NORMAL"
fi
}
check119(){
TITLE119="$BLUE 1.19$NORMAL Maintain current contact details (Scored)"
# No command available
echo -e "\n$TITLE119 "
echo -e " $NOTICE No command available for check 1.19"
echo -e " See section 1.19 on the CIS Benchmark guide for details $NORMAL"
infoReferenceShort
}
check120(){
TITLE120="$BLUE 1.20$NORMAL Ensure security contact information is registered (Scored)"
# No command available
echo -e "\n$TITLE120 "
echo -e " $NOTICE No command available for check 1.20"
echo -e " See section 1.20 on the CIS Benchmark guide for details $NORMAL"
infoReferenceShort
}
check121(){
TITLE121="$BLUE 1.21$NORMAL Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)"
echo -e "\n$TITLE121 "
echo -e " $NOTICE No command available for check 1.21"
echo -e " See section 1.21 on the CIS Benchmark guide for details $NORMAL"
infoReferenceShort
}
check122(){
TITLE122="$BLUE 1.22$NORMAL Ensure a support role has been created to manage incidents with AWS Support (Scored)"
echo -e "\n$TITLE122 "
SUPPORTPOLICYARN=$($AWSCLI iam list-policies --query "Policies[?PolicyName == 'AWSSupportAccess'].Arn" --profile $PROFILE --region $REGION --output text)
if [[ $SUPPORTPOLICYARN ]];then
for policyarn in $SUPPORTPOLICYARN;do
POLICYTOSHOW=$($AWSCLI iam list-entities-for-policy --policy-arn $SUPPORTPOLICYARN --profile $PROFILE --region $REGION --output text)
if [[ $POLICYTOSHOW ]];then
echo -e " $OK $POLICYTOSHOW $NORMAL"
echo -e " $NOTICE Make sure your team can create a Support case with AWS $NORMAL"
else
echo -e " $RED Support Policy not applied to any Group, User or Role $NORMAL"
fi
done
else
echo -e " $RED WARNING! No Support Policy found$NORMAL"
fi
}
check123(){
TITLE123="$BLUE 1.23$NORMAL Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)"
echo -e "\n$TITLE123 "
LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text --profile $PROFILE --region $REGION)
# List of USERS with KEY1 last_used_date as N/A
LIST_USERS_KEY1_NA=$(for user in $LIST_USERS; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$11 }'|grep N/A |awk '{ print $1 }'; done)
LIST_USERS_KEY1_ACTIVE=$(for user in $LIST_USERS_KEY1_NA; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$9 }'|grep "true$"|awk '{ print $1 }'|sed 's/[:blank:]+/,/g' ; done)
if [[ $LIST_USERS_KEY1_ACTIVE ]]; then
echo -e " $NOTICE List of users with Access Key 1 never used:$NORMAL"
echo -e " $RED $LIST_USERS_KEY1_ACTIVE $NORMAL"
else
echo -e " $OK No users found with Access Key 1 never used $NORMAL"
fi
# List of USERS with KEY2 last_used_date as N/A
LIST_USERS_KEY2_NA=$(for user in $LIST_USERS; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$16 }'|grep N/A |awk '{ print $1 }' ; done)
LIST_USERS_KEY2_ACTIVE=$(for user in $LIST_USERS_KEY2_NA; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$14 }'|grep "true$" |awk '{ print $1 }' ; done)
if [[ $LIST_USERS_KEY2_ACTIVE ]]; then
echo -e " $NOTICE List of users with Access Key 2 never used:$NORMAL"
echo -e " $RED $LIST_USERS_KEY2_ACTIVE $NORMAL"
else
echo -e " $OK No users found with Access Key 2 never used $NORMAL"
fi
}
check124(){
TITLE124="$BLUE 1.24$NORMAL Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)"
echo -e "\n$TITLE124"
LIST_CUSTOM_POLICIES=$($AWSCLI iam list-policies --output text --profile $PROFILE --region $REGION|grep 'arn:aws:iam::[0-9]\{12\}:'|awk '{ print $2 }')
if [[ $LIST_CUSTOM_POLICIES ]]; then
echo -e " $NOTICE Looking for custom policies: (skipping default policies, it may take few seconds...)$NORMAL"
for policy in $LIST_CUSTOM_POLICIES; do
POLICY_VERSION=$($AWSCLI iam list-policies --profile $PROFILE --region $REGION --query 'Policies[*].[Arn,DefaultVersionId]' --output text|grep -w $policy |awk '{ print $2}')
POLICY_WITH_FULL=$($AWSCLI iam get-policy-version --output text --policy-arn $policy --version-id $POLICY_VERSION --query "PolicyVersion.Document.Statement[?Effect == 'Allow' && contains(Resource, '*') && contains (Action, '*')]" --profile $PROFILE --region $REGION)
if [[ $POLICY_WITH_FULL ]]; then
POLICIES_ALLOW_LIST="$POLICIES_ALLOW_LIST $policy"
fi
done
if [[ $POLICIES_ALLOW_LIST ]]; then
echo -e " $NOTICE List of custom policies: $NORMAL"
for policy in $POLICIES_ALLOW_LIST; do
echo " $RED $policy $NORMAL"
done
else
echo " $OK No custom policy found that allow full \"*:*\" administrative privileges $NORMAL"
fi
else
echo " $OK No custom policies found $NORMAL"
fi
}
check21(){
TITLE21="$BLUE 2.1$NORMAL Ensure CloudTrail is enabled in all regions (Scored)"
echo -e "\n$TITLE21"
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].Name' --output text)
if [[ $LIST_OF_TRAILS ]];then
for trail in $LIST_OF_TRAILS;do
MULTIREGION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].IsMultiRegionTrail' --output text --trail-name-list $trail)
if [[ $MULTIREGION_TRAIL_STATUS == 'False' ]];then
echo -e " $RED WARNING! $trail trail in $REGION is not enabled in multi region mode$NORMAL"
else
echo -e " $OK OK! $trail trail in $REGION is enabled for all regions$NORMAL"
fi
done
else
echo -e " $RED WARNING! No CloudTrail trails found!$NORMAL"
fi
}
check22(){
TITLE22="$BLUE 2.2$NORMAL Ensure CloudTrail log file validation is enabled (Scored)"
echo -e "\n$TITLE22"
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].Name' --output text)
if [[ $LIST_OF_TRAILS ]];then
for trail in $LIST_OF_TRAILS;do
LOGFILEVALIDATION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].LogFileValidationEnabled' --output text --trail-name-list $trail)
if [[ $LOGFILEVALIDATION_TRAIL_STATUS == 'False' ]];then
echo -e " $RED WARNING! $trail trail in $REGION has not log file validation enabled$NORMAL"
else
echo -e " $OK OK! $trail trail in $REGION has log file validation enabled$NORMAL"
fi
done
else
echo -e " $RED WARNING! No CloudTrail trails found!$NORMAL"
fi
}
check23(){
TITLE23="$BLUE 2.3$NORMAL Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)"
echo -e "\n$TITLE23"
CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text --profile $PROFILE --region $REGION)
if [[ $CLOUDTRAILBUCKET ]];then
for bucket in $CLOUDTRAILBUCKET;do
CLOUDTRAILBUCKET_HASALLPERMISIONS=$($AWSCLI s3api get-bucket-acl --bucket $bucket --query 'Grants[?Grantee.URI==`http://acs.amazonaws.com/groups/global/AllUsers`]' --profile $PROFILE --region $REGION --output text)
if [[ $CLOUDTRAILBUCKET_HASALLPERMISIONS ]];then
echo -e " $RED WARNING! check your $bucket CloudTrail bucket ACL and Policy!$NORMAL"
else
echo -e " $OK OK! Bucket $bucket is set correctly $NORMAL"
fi
done
else
echo -e " $RED WARNING! No CloudTrail bucket found!$NORMAL"
fi
}
check24(){
TITLE24="$BLUE 2.4$NORMAL Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)"
echo -e "\n$TITLE24"
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].Name' --output text)
if [[ $LIST_OF_TRAILS ]];then
for trail in $LIST_OF_TRAILS;do
TRAIL_REGION=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --trail-name-list "$trail" --query 'trailList[*].HomeRegion' --output text)
LATESTDELIVERY_TIMESTAMP=$($AWSCLI cloudtrail get-trail-status --name $trail --profile $PROFILE --region $TRAIL_REGION --query 'LatestCloudWatchLogsDeliveryTime' --output text|grep -v None)
if [[ ! $LATESTDELIVERY_TIMESTAMP ]];then
echo -e " $RED $trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)$NORMAL"
else
LATESTDELIVERY_DATE=$(timestamp_to_date $LATESTDELIVERY_TIMESTAMP)
HOWOLDER=$(how_older_from_today $LATESTDELIVERY_DATE)
if [ $HOWOLDER -gt "1" ];then
echo -e " $RED $trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)$NORMAL"
else
echo -e " $OK $trail trail has been logging during the last 24h (it is in $TRAIL_REGION)$NORMAL"
fi
fi
done
else
echo -e " $RED WARNING! No CloudTrail trails found!$NORMAL"
fi
}
check25(){
TITLE25="$BLUE 2.5$NORMAL Ensure AWS Config is enabled in all regions (Scored)"
echo -e "\n$TITLE25"
for regx in $REGIONS; do
CHECK_AWSCONFIG_STATUS=$($AWSCLI configservice get-status --profile $PROFILE --region $regx | grep "recorder: ON")
if [[ $CHECK_AWSCONFIG_STATUS ]];then
echo -e " $OK Region $regx has AWS Config recorder: ON $NORMAL"
else
echo -e " $RED WARNING! Region $regx has AWS Config disabled or not configured$NORMAL"
fi
done
}
check26(){
TITLE26="$BLUE 2.6$NORMAL Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)"
echo -e "\n$TITLE26"
CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text --profile $PROFILE --region $REGION)
if [[ $CLOUDTRAILBUCKET ]];then
for bucket in $CLOUDTRAILBUCKET;do
CLOUDTRAILBUCKET_LOGENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket --profile $PROFILE --region $REGION --query 'LoggingEnabled.TargetBucket' --output text|grep -v None)
if [[ $CLOUDTRAILBUCKET_LOGENABLED ]];then
echo -e " $OK OK! It is enabled in $bucket $NORMAL"
else
echo -e " $RED WARNING! access logging is not enabled in $bucket CloudTrail S3 bucket!$NORMAL"
fi
done
else
echo -e " $RED WARNING! CloudTrail bucket not found!$NORMAL"
fi
}
check27(){
TITLE27="$BLUE 2.7$NORMAL Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)"
echo -e "\n$TITLE27"
CLOUDTRAILNAME=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].Name' --output text --profile $PROFILE --region $REGION)
if [[ $CLOUDTRAILNAME ]];then
for trail in $CLOUDTRAILNAME;do
CLOUDTRAILENC_ENABLED=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --trail $trail --query 'trailList[*].KmsKeyId' --output text)
if [[ $CLOUDTRAILENC_ENABLED ]];then
echo -e " $OK OK! KMS key found for $trail $NORMAL"
else
echo -e " $RED WARNING! encryption is not enabled in your CloudTrail trail $trail, KMS key not found!$NORMAL"
fi
done
else
echo -e " $RED WARNING! CloudTrail bucket doesn't exist!$NORMAL"
fi
}
check28(){
TITLE28="$BLUE 2.8$NORMAL Ensure rotation for customer created CMKs is enabled (Scored)"
echo -e "\n$TITLE28"
for regx in $REGIONS; do
CHECK_KMS_KEYLIST=$($AWSCLI kms list-keys --profile $PROFILE --region $regx --output text --query 'Keys[*].KeyId')
if [[ $CHECK_KMS_KEYLIST ]];then
for key in $CHECK_KMS_KEYLIST; do
CHECK_KMS_KEY_TYPE=$($AWSCLI kms describe-key --key-id $key --profile $PROFILE --region $regx --query 'KeyMetadata.Origin' | sed 's/["]//g')
if [[ $CHECK_KMS_KEY_TYPE == "EXTERNAL" ]];then
echo -e " $BLUE Key $key in Region $regx Customer Uploaded Key Material.$NORMAL"
else
CHECK_KMS_KEY_ROTATION=$($AWSCLI kms get-key-rotation-status --key-id $key --profile $PROFILE --region $regx --output text)
CHECK_KMS_DEFAULT_KEY=$($AWSCLI kms describe-key --key-id $key --profile $PROFILE --region $regx --query 'KeyMetadata.Description' | sed -n '/Default master key that protects my /p')
if [[ $CHECK_KMS_KEY_ROTATION == "True" ]];then
echo -e " $OK OK! Key $key in Region $regx is set correctly$NORMAL"
elif [[ $CHECK_KMS_KEY_ROTATION == "False" && $CHECK_KMS_DEFAULT_KEY ]];then
echo -e " $NOTICE Region $regx key $key is an AWS default master key and cannot be deleted nor modified.$NORMAL"
else
echo -e " $RED WARNING! Key $key in Region $regx is not set to rotate!!!$NORMAL"
fi
fi
done
else
echo -e " $NOTICE Region $regx doesn't have encryption keys $NORMAL"
fi
done
}
check31(){
TITLE31="$BLUE 3.1$NORMAL Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)"
echo -e "\n$TITLE31 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep AccessDenied)
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for Access Denied enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check32(){
TITLE32="$BLUE 3.2$NORMAL Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)"
echo -e "\n$TITLE32 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'userIdentity.sessionContext.attributes.mfaAuthenticated.*true')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for sign-in Console without MFA enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check33(){
TITLE33="$BLUE 3.3$NORMAL Ensure a log metric filter and alarm exist for usage of root account (Scored)"
echo -e "\n$TITLE33 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION |grep -E 'userIdentity.*Root.*AwsServiceEvent')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for usage of root account enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check34(){
TITLE34="$BLUE 3.4$NORMAL Ensure a log metric filter and alarm exist for IAM policy changes (Scored)"
echo -e "\n$TITLE34 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'DeleteGroupPolicy.*DeleteRolePolicy.*DeleteUserPolicy.*PutGroupPolicy.*PutRolePolicy.*PutUserPolicy.*CreatePolicy.*DeletePolicy.*CreatePolicyVersion.*DeletePolicyVersion.*AttachRolePolicy.*DetachRolePolicy.*AttachUserPolicy.*DetachUserPolicy.*AttachGroupPolicy.*DetachGroupPolicy')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for IAM policy changes enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check35(){
TITLE35="$BLUE 3.5$NORMAL Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)"
echo -e "\n$TITLE35 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateTrail.*UpdateTrail.*DeleteTrail.*StartLogging.*StopLogging')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for CloudTrail configuration changes enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check36(){
TITLE36="$BLUE 3.6$NORMAL Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)"
echo -e "\n$TITLE36 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'ConsoleLogin.*Failed')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters for usage of root account enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check37(){
TITLE37="$BLUE 3.7$NORMAL Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)"
echo -e "\n$TITLE37 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'kms.amazonaws.com.*DisableKey.*ScheduleKeyDeletion')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check38(){
TITLE38="$BLUE 3.8$NORMAL Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)"
echo -e "\n$TITLE38 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 's3.amazonaws.com.*PutBucketAcl.*PutBucketPolicy.*PutBucketCors.*PutBucketLifecycle.*PutBucketReplication.*DeleteBucketPolicy.*DeleteBucketCors.*DeleteBucketLifecycle.*DeleteBucketReplication')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check39(){
TITLE39="$BLUE 3.9$NORMAL Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)"
echo -e "\n$TITLE39 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'config.amazonaws.com.*StopConfigurationRecorder.*DeleteDeliveryChannel.*PutDeliveryChannel.*PutConfigurationRecorder')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check310(){
TITLE310="$BLUE 3.10$NORMAL Ensure a log metric filter and alarm exist for security group changes (Scored)"
echo -e "\n$TITLE310 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'AuthorizeSecurityGroupIngress.*AuthorizeSecurityGroupEgress.*RevokeSecurityGroupIngress.*RevokeSecurityGroupEgress.*CreateSecurityGroup.*DeleteSecurityGroup')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check311(){
TITLE311="$BLUE 3.11$NORMAL Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)"
echo -e "\n$TITLE311 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateNetworkAcl.*CreateNetworkAclEntry.*DeleteNetworkAcl.*DeleteNetworkAclEntry.*ReplaceNetworkAclEntry.*ReplaceNetworkAclAssociation')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check312(){
TITLE312="$BLUE 3.12$NORMAL Ensure a log metric filter and alarm exist for changes to network gateways (Scored)"
echo -e "\n$TITLE312 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateCustomerGateway.*DeleteCustomerGateway.*AttachInternetGateway.*CreateInternetGateway.*DeleteInternetGateway.*DetachInternetGateway')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check313(){
TITLE313="$BLUE 3.13$NORMAL Ensure a log metric filter and alarm exist for route table changes (Scored)"
echo -e "\n$TITLE313 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateRoute.*CreateRouteTable.*ReplaceRoute.*ReplaceRouteTableAssociation.*DeleteRouteTable.*DeleteRoute.*DisassociateRouteTable')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check314(){
TITLE314="$BLUE 3.14$NORMAL Ensure a log metric filter and alarm exist for VPC changes (Scored)"
echo -e "\n$TITLE314 "
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateVpc.*DeleteVpc.*ModifyVpcAttribute.*AcceptVpcPeeringConnection.*CreateVpcPeeringConnection.*DeleteVpcPeeringConnection.*RejectVpcPeeringConnection.*AttachClassicLinkVpc.*DetachClassicLinkVpc.*DisableVpcClassicLink.*EnableVpcClassicLink')
if [[ $METRICFILTER_SET ]];then
echo -e " $OK OK! CloudWatch group found, and metric filters enabled$NORMAL"
else
echo -e " $RED WARNING! CloudWatch group found, but no metric filters or alarms associated$NORMAL"
fi
else
echo -e " $RED WARNING! No CloudWatch group found, no metric filters or alarms associated$NORMAL"
fi
}
check315(){
TITLE315="$BLUE 3.15$NORMAL Ensure appropriate subscribers to each SNS topic (Not Scored)"
echo -e "\n$TITLE315 "
for regx in $REGIONS; do
TOPICS_LIST=$($AWSCLI sns list-topics --profile $PROFILE --region $regx --output text --query 'Topics[*].TopicArn')
if [[ $TOPICS_LIST ]];then
for topic in $TOPICS_LIST; do
CHECK_TOPIC_LIST=$($AWSCLI sns list-subscriptions-by-topic --topic-arn $topic --profile $PROFILE --region $regx --query 'Subscriptions[*].{Endpoint:Endpoint,Protocol:Protocol}' --output text --max-items $MAXITEMS | grep -v "None")
if [[ $CHECK_TOPIC_LIST ]]; then
TOPIC_SHORT=$(echo $topic | awk -F: '{ print $7 }')
echo -e " $NOTICE Region $regx with Topic $TOPIC_SHORT: $NORMAL "
echo -e " $NOTICE - Suscription: $CHECK_TOPIC_LIST $NORMAL"
else
echo -e " $RED WARNING! No suscription found in: Region $regx and Topic $topic $NORMAL"
echo -e " $RED - Region $regx and Topic $topic $NORMAL"
fi
done
else
echo -e " $NOTICE Region $regx doesn't have topics $NORMAL"
fi
done
}
check41(){
TITLE41="$BLUE 4.1$NORMAL Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)"
echo -e "\n$TITLE41 "
for regx in $REGIONS; do
SG_LIST=$($AWSCLI ec2 describe-security-groups --filters "Name=ip-permission.to-port,Values=22" --query 'SecurityGroups[?length(IpPermissions[?ToPort==`22` && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupName: GroupName}' --profile $PROFILE --region $regx --output text)
if [[ $SG_LIST ]];then
for SG in $SG_LIST;do
echo -e " $RED Found Security Group: $SG open to 0.0.0.0/0 in Region $regx $NORMAL "
done
else
echo -e " $OK OK! No Security Groups found in $regx with port 22 TCP open to 0.0.0.0/0 $NORMAL "
fi
done
}
check42(){
TITLE42="$BLUE 4.2$NORMAL Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)"
echo -e "\n$TITLE42 "
for regx in $REGIONS; do
SG_LIST=$($AWSCLI ec2 describe-security-groups --filters "Name=ip-permission.to-port,Values=3389" --query 'SecurityGroups[?length(IpPermissions[?ToPort==`3389` && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupName: GroupName}' --profile $PROFILE --region $regx --output text)
if [[ $SG_LIST ]];then
for SG in $SG_LIST;do
echo -e " $RED Found Security Group: $SG open to 0.0.0.0/0 in Region $regx $NORMAL "
done
else
echo -e " $OK OK! No Security Groups found in $regx with port 3389 TCP open to 0.0.0.0/0 $NORMAL "
fi
done
}
check43(){
TITLE43="$BLUE 4.3$NORMAL Ensure VPC Flow Logging is Enabled in all VPCs (Scored)"
echo -e "\n$TITLE43 "
for regx in $REGIONS; do
CHECK_FL=$($AWSCLI ec2 describe-flow-logs --profile $PROFILE --region $regx --query 'FlowLogs[?FlowLogStatus==`ACTIVE`].LogGroupName' --output text)
if [[ $CHECK_FL ]];then
for FL in $CHECK_FL;do
echo -e " $OK OK! VPCFlowLog is enabled for LogGroupName: $FL in Region $regx $NORMAL "
done
else
echo -e " $RED WARNING! No VPCFlowLog has been found in Region $regx $NORMAL "
fi
done
}
check44(){
TITLE44="$BLUE 4.4$NORMAL Ensure the default security group of every VPC restricts all traffic (Scored)"
echo -e "\n$TITLE44 "
for regx in $REGIONS; do
CHECK_SGDEFAULT=$($AWSCLI ec2 describe-security-groups --profile $PROFILE --region $regx --filters Name=group-name,Values='default' --query 'SecurityGroups[*].{IpPermissions:IpPermissions,IpPermissionsEgress:IpPermissionsEgress,GroupId:GroupId}' --output text |grep 0.0.0.0)
if [[ $CHECK_SGDEFAULT ]];then
echo -e " $RED WARNING! Default Security Groups found that allow 0.0.0.0 IN or OUT traffic in Region $regx $NORMAL "
else
echo -e " $OK OK! No Default Security Groups open to 0.0.0.0 found in Region $regx $NORMAL "
fi
done
}
check45(){
#set -xe
TITLE45="$BLUE 4.5$NORMAL Ensure routing tables for VPC peering are \"least access\" (Not Scored)"
echo -e "\n$TITLE45 "
echo -e " $NOTICE Looking for VPC peering in all regions... $NORMAL "
for regx in $REGIONS; do
LIST_OF_VPCS_PEERING_CONNECTIONS=$($AWSCLI ec2 describe-vpc-peering-connections --output text --profile $PROFILE --region $regx --query 'VpcPeeringConnections[*].VpcPeeringConnectionId')
if [[ $LIST_OF_VPCS_PEERING_CONNECTIONS ]];then
echo -e " $NOTICE $regx: $LIST_OF_VPCS_PEERING_CONNECTIONS, review its routing tables $NORMAL "
#LIST_OF_VPCS=$($AWSCLI ec2 describe-vpcs --profile $PROFILE --region $regx --query 'Vpcs[*].VpcId' --output text)
#aws ec2 describe-route-tables --filter "Name=vpc-id,Values=vpc-0213e864" --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" --profile $PROFILE --region $regx
# for vpc in $LIST_OF_VPCS; do
# VPCS_WITH_PEERING=$($AWSCLI ec2 describe-route-tables --filter "Name=vpc-id,Values=$vpc" --profile $PROFILE --region $regx --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" |grep GatewayId|grep pcx-)
# done
#echo $VPCS_WITH_PEERING
else
echo -e " $OK $regx: No VPC peering found $NORMAL "
fi
done
}
callCheck(){
if [[ $CHECKNUMBER ]];then
case "$CHECKNUMBER" in
check11) check11;cleanTemp;exit;;
check12) check12;cleanTemp;exit;;
check13) check13;cleanTemp;exit;;
check14) check14;cleanTemp;exit;;
check15) check15;cleanTemp;exit;;
check16) check16;cleanTemp;exit;;
check17) check17;cleanTemp;exit;;
check18) check18;cleanTemp;exit;;
check19) check19;cleanTemp;exit;;
check110) check110;cleanTemp;exit;;
check111) check111;cleanTemp;exit;;
check112) check112;cleanTemp;exit;;
check113) check113;cleanTemp;exit;;
check114) check114;cleanTemp;exit;;
check115) check115;cleanTemp;exit;;
check116) check116;cleanTemp;exit;;
check117) check117;cleanTemp;exit;;
check118) check118;cleanTemp;exit;;
check119) check119;cleanTemp;exit;;
check120) check120;cleanTemp;exit;;
check121) check121;cleanTemp;exit;;
check122) check122;cleanTemp;exit;;
check123) check123;cleanTemp;exit;;
check124) check124;cleanTemp;exit;;
check21) check21;cleanTemp;exit;;
check22) check22;cleanTemp;exit;;
check23) check23;cleanTemp;exit;;
check24) check24;cleanTemp;exit;;
check25) check25;cleanTemp;exit;;
check26) check26;cleanTemp;exit;;
check27) check27;cleanTemp;exit;;
check28) check28;cleanTemp;exit;;
check31) check31;cleanTemp;exit;;
check32) check32;cleanTemp;exit;;
check33) check33;cleanTemp;exit;;
check34) check34;cleanTemp;exit;;
check35) check35;cleanTemp;exit;;
check36) check36;cleanTemp;exit;;
check37) check37;cleanTemp;exit;;
check38) check38;cleanTemp;exit;;
check39) check39;cleanTemp;exit;;
check310) check310;cleanTemp;exit;;
check311) check311;cleanTemp;exit;;
check312) check312;cleanTemp;exit;;
check313) check313;cleanTemp;exit;;
check314) check314;cleanTemp;exit;;
check315) check315;cleanTemp;exit;;
check41) check41;cleanTemp;exit;;
check42) check42;cleanTemp;exit;;
check43) check43;cleanTemp;exit;;
check44) check44;cleanTemp;exit;;
check45) check45;cleanTemp;exit;;
check1) check11;check12;check13;check14;check15;check16;check17;check18;check19;check110;check111;check112;check113;check114;check115;check116;check117;check118;check119;check120;check121;check122;check123;check124;cleanTemp;exit;;
check2) check21;check22;check23;check24;check25;check26;check27;check28;cleanTemp;exit;;
check3) check31;check32;check33;check34;check35;check36;check37;check38;check39;check310;check311;check312;check313;check314;check315;cleanTemp;exit;;
check4) check41;check42;check43;check44;check45;cleanTemp;exit;;
* ) echo -e "\n$RED ERROR! Use a valid check name (i.e. check41) $NORMAL\n";exit;;
esac
fi
}
callCheck
TITLE1="$BLUE 1 Identity and Access Management *********************************$NORMAL"
echo -e "\n\n$TITLE1 "
check11
check12
check13
check14
check15
check16
check17
check18
check19
check110
check111
check112
check113
check114
check115
check119
check120
check121
check122
check123
check124
TITLE2="$BLUE 2 Logging ********************************************************$NORMAL"
echo -e "\n\n$TITLE2 "
check21
check22
check23
check24
check25
check26
check27
check28
TITLE3="$BLUE 3 Monitoring *****************************************************"
echo -e "\n\n$TITLE3 "
# 3 Monitoring check commands / Mostly covered by SecurityMonkey
check31
check32
check33
check34
check35
check36
check37
check38
check39
check310
check311
check312
check313
check314
check315
TITLE4="$BLUE 4 Networking **************************************************$NORMAL"
echo -e "\n\n$TITLE4 "
check41
check42
check43
check44
check45
infoReferenceLong
cleanTemp