Files
prowler/prowler
Jason Chen 775d28f9c4 prowler: fix IAM role detection and curl error
Related to https://github.com/toniblyx/prowler/pull/200

If the ec2 instance is not attached to an IAM role, the `curl -s -m 1
http://169.254.169.254/latest/meta-data/iam/security-credentials` will
return a 404 page instead of null, INSTANCE_PROFILE will always be true
and result to curl error when trying to use default cli profile:

    curl: option -: is unknown
    curl: try 'curl --help' or 'curl --manual' for more information
2018-04-19 14:56:36 +08:00

114 KiB
Executable File

#!/usr/bin/env 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 - https://blyx.com/contact
 
# 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...
 
OPTRED=""
OPTNORMAL=""
 
# Set the defaults for these getopts variables
REGION=""
FILTERREGION=""
MAXITEMS=100
MONOCHROME=0
MODE="text"
SEP=','
KEEPCREDREPORT=0
EXITCODE=0
 
 
# Command usage menu
usage(){
echo "
USAGE:
`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), all regions are checked anyway
-c <check_id> specify a check number or group from the AWS CIS benchmark
(i.e.: "check11" for check 1.1, "check3" for entire section 3, "level1" for CIS Level 1 Profile Definitions or "forensics-ready")
-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)
-M <mode> output mode: text (defalut), mono, csv (separator is ","; data is on stdout; progress on stderr)
-k keep the credential report
-n show check numbers to sort easier
(i.e.: 1.01 instead of 1.1)
-l list all available checks only (does not perform any check)
-e exclude extras
-h this help
"
exit
}
 
while getopts ":hlkp:r:c:f:m:M:en" OPTION; do
case $OPTION in
h )
usage
EXITCODE=1
exit $EXITCODE
;;
l )
PRINTCHECKSONLY=1
;;
k )
KEEPCREDREPORT=1
;;
p )
PROFILE=$OPTARG
;;
r )
REGION_OPT=$OPTARG
;;
c )
CHECKNUMBER=$OPTARG
;;
f )
FILTERREGION=$OPTARG
;;
m )
MAXITEMS=$OPTARG
;;
M )
MODE=$OPTARG
;;
n )
NUMERAL=1
;;
e )
EXTRAS=1
;;
: )
echo ""
echo "$OPTRED ERROR!$OPTNORMAL -$OPTARG requires an argument"
usage
EXITCODE=1
exit $EXITCODE
;;
? )
echo ""
echo "$OPTRED ERROR!$OPTNORMAL Invalid option"
usage
EXITCODE=1
exit $EXITCODE
;;
esac
done
 
if [[ $MODE != "mono" && $MODE != "text" && $MODE != "csv" ]]; then
echo ""
echo "$OPTRED ERROR!$OPTNORMAL Invalid output mode. Choose text, mono, or csv."
usage
EXITCODE=1
exit $EXITCODE
fi
 
if [[ "$MODE" == "mono" || "$MODE" == "csv" ]]; then
MONOCHROME=1
fi
 
if [[ $MONOCHROME -eq 1 ]]; then
# 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=''
else
# Colors
# NOTE: Your editor may NOT show the 0x1b / escape character left of the '['
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=""
fi
 
SCRIPT_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" )
 
# Functions to manage dates depending on OS
if [ "$OSTYPE" == "linux-gnu" ] || [ "$OSTYPE" == "linux-musl" ]; then
TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX)
# 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 commands compatibility
TEMP_REPORT_FILE=$(mktemp -t prowler.cred_report-XXXXXX)
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
TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX)
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! Valid \$OSTYPE: linux-gnu, linux-musl, darwin* or cygwin"
echo "Found: $OSTYPE"
EXITCODE=1
exit $EXITCODE
fi
 
# It checks -p optoin first and use it as profile, if not -p provided then
# check environment variables and if not, it checks and loads credentials from
# instance profile (metadata server) if runs in an EC2 instance
 
INSTANCE_PROFILE=$(curl -s -m 1 http://169.254.169.254/latest/meta-data/iam/security-credentials/)
if echo "$INSTANCE_PROFILE" | grep -q '404 - Not Found'; then
INSTANCE_PROFILE=
fi
 
if [[ $PROFILE ]]; then
PROFILE_OPT="--profile $PROFILE"
elif [[ $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY || $AWS_SESSION_TOKEN ]];then
PROFILE="ENV"
PROFILE_OPT=""
elif [[ $INSTANCE_PROFILE ]];then
PROFILE="INSTANCE-PROFILE"
AWS_ACCESS_KEY_ID=$(curl -s 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 -s 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_SESSION_TOKEN=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} grep Token| cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g')
else
PROFILE="default"
PROFILE_OPT="--profile $PROFILE"
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"
EXITCODE=1
exit $EXITCODE
fi
 
# Set default region by aws config, fall back to us-east-1
REGION_CONFIG=$(aws configure get region)
if [[ $REGION_OPT ]]; then
REGION="$REGION_OPT"
elif [[ $REGION_CONFIG ]]; then
REGION="$REGION_CONFIG"
else
REGION="us-east-1"
fi
 
TITLE_ID=""
TITLE_TEXT="CALLER ERROR - UNSET TITLE"
## Output formatting functions
textOK(){
if [[ "$MODE" == "csv" ]]; then
if [[ $2 ]]; then
REPREGION=$2
else
REPREGION=$REGION
fi
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}PASS${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
else
echo " $OK OK! $NORMAL $1"
fi
}
 
textNotice(){
if [[ "$MODE" == "csv" ]]; then
if [[ $2 ]]; then
REPREGION=$2
else
REPREGION=$REGION
fi
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}INFO${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
else
echo " $NOTICE INFO! $1 $NORMAL"
fi
}
 
textWarn(){
EXITCODE=3
if [[ "$MODE" == "csv" ]]; then
if [[ $2 ]]; then
REPREGION=$2
else
REPREGION=$REGION
fi
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}WARNING${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
else
echo " $BAD WARNING! $1 $NORMAL"
fi
}
 
textTitle(){
TITLE_ID=$1
if [[ $NUMERAL ]]; then
TITLE_ID=$(echo $TITLE_ID | cut -d, -f2)
else
TITLE_ID=$(echo $TITLE_ID | cut -d, -f1)
fi
 
TITLE_TEXT=$2
 
case "$3" in
0|No|NOT_SCORED)
ITEM_SCORED="Not Scored"
;;
1|Yes|SCORED)
ITEM_SCORED="Scored"
;;
*)
ITEM_SCORED="Unspecified"
;;
esac
 
case "$4" in
LEVEL1) ITEM_LEVEL="Level 1";;
LEVEL2) ITEM_LEVEL="Level 2";;
EXTRA) ITEM_LEVEL="Extra";;
SUPPORT) ITEM_LEVEL="Support";;
*) ITEM_LEVEL="Unspecified or Invalid";;
esac
 
if [[ "$MODE" == "csv" ]]; then
>&2 echo "$TITLE_ID $TITLE_TEXT"
else
if [[ "$ITEM_SCORED" == "Scored" ]]; then
echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT"
else
echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL"
fi
fi
}
 
# List of checks IDs and Titles
TITLE1="Identity and Access Management ****************************************"
ID11="1.1,1.01"
TITLE11="Avoid the use of the root account (Scored)."
ID12="1.2,1.02"
TITLE12="Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)"
ID13="1.3,1.03"
TITLE13="Ensure credentials unused for 90 days or greater are disabled (Scored)"
ID14="1.4,1.04"
TITLE14="Ensure access keys are rotated every 90 days or less (Scored)" # also checked by Security Monkey
ID15="1.5,1.05"
TITLE15="Ensure IAM password policy requires at least one uppercase letter (Scored)"
ID16="1.6,1.06"
TITLE16="Ensure IAM password policy require at least one lowercase letter (Scored)"
ID17="1.7,1.07"
TITLE17="Ensure IAM password policy require at least one symbol (Scored)"
ID18="1.8,1.08"
TITLE18="Ensure IAM password policy require at least one number (Scored)"
ID19="1.9,1.09"
TITLE19="Ensure IAM password policy requires minimum length of 14 or greater (Scored)"
ID110="1.10"
TITLE110="Ensure IAM password policy prevents password reuse: 24 or greater (Scored)"
ID111="1.11"
TITLE111="Ensure IAM password policy expires passwords within 90 days or less (Scored)"
ID112="1.12"
TITLE112="Ensure no root account access key exists (Scored)"
ID113="1.13"
TITLE113="Ensure MFA is enabled for the root account (Scored)"
ID114="1.14"
TITLE114="Ensure hardware MFA is enabled for the root account (Scored)"
ID115="1.15"
TITLE115="Ensure security questions are registered in the AWS account (Not Scored)"
ID116="1.16"
TITLE116="Ensure IAM policies are attached only to groups or roles (Scored)"
ID117="1.17"
TITLE117="Enable detailed billing (Scored)"
ID118="1.18"
TITLE118="Ensure IAM Master and IAM Manager roles are active (Scored)"
ID119="1.19"
TITLE119="Maintain current contact details (Scored)"
ID120="1.20"
TITLE120="Ensure security contact information is registered (Scored)"
ID121="1.21"
TITLE121="Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)"
ID122="1.22"
TITLE122="Ensure a support role has been created to manage incidents with AWS Support (Scored)"
ID123="1.23"
TITLE123="Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)"
ID124="1.24"
TITLE124="Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)"
TITLE2="Logging ***************************************************************"
ID21="2.1,2.01"
TITLE21="Ensure CloudTrail is enabled in all regions (Scored)"
ID22="2.2,2.02"
TITLE22="Ensure CloudTrail log file validation is enabled (Scored)"
ID23="2.3,2.03"
TITLE23="Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)"
ID24="2.4,2.04"
TITLE24="Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)"
ID25="2.5,2.05"
TITLE25="Ensure AWS Config is enabled in all regions (Scored)"
ID26="2.6,2.06"
TITLE26="Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)"
ID27="2.7,2.07"
TITLE27="Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)"
ID28="2.8,2.08"
TITLE28="Ensure rotation for customer created CMKs is enabled (Scored)"
TITLE3="Monitoring ************************************************************"
ID31="3.1,3.01"
TITLE31="Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)"
ID32="3.2,3.02"
TITLE32="Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)"
ID33="3.3,3.03"
TITLE33="Ensure a log metric filter and alarm exist for usage of root account (Scored)"
ID34="3.4,3.04"
TITLE34="Ensure a log metric filter and alarm exist for IAM policy changes (Scored)"
ID35="3.5,3.05"
TITLE35="Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)"
ID36="3.6,3.06"
TITLE36="Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)"
ID37="3.7,3.07"
TITLE37="Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)"
ID38="3.8,3.08"
TITLE38="Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)"
ID39="3.9,3.09"
TITLE39="Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)"
ID310="3.10"
TITLE310="Ensure a log metric filter and alarm exist for security group changes (Scored)"
ID311="3.11"
TITLE311="Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)"
ID312="3.12"
TITLE312="Ensure a log metric filter and alarm exist for changes to network gateways (Scored)"
ID313="3.13"
TITLE313="Ensure a log metric filter and alarm exist for route table changes (Scored)"
ID314="3.14"
TITLE314="Ensure a log metric filter and alarm exist for VPC changes (Scored)"
ID315="3.15"
TITLE315="Ensure appropriate subscribers to each SNS topic (Not Scored)"
TITLE4="Networking ************************************************************"
ID41="4.1,4.01"
TITLE41="Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)"
ID42="4.2,4.02"
TITLE42="Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)"
ID43="4.3,4.03"
TITLE43="Ensure VPC Flow Logging is Enabled in all VPCs (Scored)"
ID44="4.4,4.04"
TITLE44="Ensure the default security group of every VPC restricts all traffic (Scored)"
ID45="4.5,4.05"
TITLE45="Ensure routing tables for VPC peering are \"least access\" (Not Scored)"
TITLE7="Extras ****************************************************************"
ID71="7.1,7.01"
TITLE71="Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark)"
ID72="7.2,7.02"
TITLE72="Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark)"
ID73="7.3,7.03"
TITLE73="Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark)"
ID74="7.4,7.04"
TITLE74="Ensure there are no Security Groups without ingress filtering being used (Not Scored) (Not part of CIS benchmark)"
ID75="7.5,7.05"
TITLE75="Ensure there are no Security Groups not being used (Not Scored) (Not part of CIS benchmark)"
ID76="7.6,7.06"
TITLE76="Ensure there are no EC2 AMIs set as Public (Not Scored) (Not part of CIS benchmark)"
ID77="7.7,7.07"
TITLE77="Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark)"
ID78="7.8,7.08"
TITLE78="Ensure there are no Public Accessible RDS instances (Not Scored) (Not part of CIS benchmark)"
ID79="7.9,7.09"
TITLE79="Check for internet facing Elastic Load Balancers (Not Scored) (Not part of CIS benchmark)"
ID710="7.10,7.10"
TITLE710="Check for internet facing EC2 Instances (Not Scored) (Not part of CIS benchmark)"
ID711="7.11,7.11"
TITLE711="Check for Publicly Accessible Redshift Clusters (Not Scored) (Not part of CIS benchmark)"
ID712="7.12,7.12"
TITLE712="Check if Amazon Macie is enabled (Not Scored) (Not part of CIS benchmark)"
ID713="7.13,7.13"
TITLE713="Check if GuardDuty is enabled (Not Scored) (Not part of CIS benchmark)"
ID714="7.14,7.14"
TITLE714="Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark)"
ID715="7.15,7.15"
TITLE715="Check if Elasticsearch Service domains have logging enabled (Not Scored) (Not part of CIS benchmark)"
ID716="7.16,7.16"
TITLE716="Check if Elasticsearch Service domains allow open access (Not Scored) (Not part of CIS benchmark)"
ID717="7.17,7.17"
TITLE717="Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark)"
ID718="7.18,7.18"
TITLE718="Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark)"
ID719="7.19,7.19"
TITLE719="Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark)"
ID720="7.20,7.20"
TITLE720="Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark)"
ID721="7.21,7.21"
TITLE721="Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark)"
ID722="7.22,7.22"
TITLE722="Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark)"
ID723="7.23,7.23"
TITLE723="Check if RDS Snapshots are public (Not Scored) (Not part of CIS benchmark)"
 
printCsvHeader() {
>&2 echo ""
>&2 echo "Generating \"${SEP}\" delimited report on stdout for profile $PROFILE, account $ACCOUNT_NUM"
echo "PROFILE${SEP}ACCOUNT_NUM${SEP}REGION${SEP}TITLE_ID${SEP}RESULT${SEP}SCORED${SEP}LEVEL${SEP}TITLE_TEXT${SEP}NOTES"
}
 
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"
echo -e "$YELLOW Date: $(date)"
}
 
# Get whoami in AWS, who is the user running this shell script
getWhoami(){
ACCOUNT_NUM=$($AWSCLI sts get-caller-identity --output json $PROFILE_OPT --region $REGION --query "Account" | tr -d '"')
if [[ "$MODE" == "csv" ]]; then
CALLER_ARN_RAW=$($AWSCLI sts get-caller-identity --output json $PROFILE_OPT --region $REGION --query "Arn")
if [[ 255 -eq $? ]]; then
# Failed to get own identity ... exit
echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
>&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
EXITCODE=2
exit $EXITCODE
fi
CALLER_ARN=$(echo $CALLER_ARN_RAW | tr -d '"')
printCsvHeader
textTitle "0.0" "Show report generation info" "NOT_SCORED" "SUPPORT"
textNotice "ARN: $CALLER_ARN TIMESTAMP: $SCRIPT_START_TIME"
else
echo ""
echo "This report is being generated using credentials below:"
echo ""
echo -e "AWS-CLI Profile: $NOTICE[$PROFILE]$NORMAL AWS API Region: $NOTICE[$REGION]$NORMAL AWS Filter Region: $NOTICE[${FILTERREGION:-all}]$NORMAL\n"
if [[ $MONOCHROME -eq 1 ]]; then
echo "Caller Identity:"
$AWSCLI sts get-caller-identity --output text $PROFILE_OPT --region $REGION --query "Arn"
if [[ 255 -eq $? ]]; then
# Failed to get own identity ... exit
echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
>&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
exit 2
fi
echo ""
else
echo "Caller Identity:"
$AWSCLI sts get-caller-identity --output table $PROFILE_OPT --region $REGION
if [[ 255 -eq $? ]]; then
# Failed to get own identity ... exit
echo variable $PROFILE_OPT
echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
>&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
EXITCODE=2
exit $EXITCODE
fi
echo ""
fi
fi
}
 
printColorsCode(){
if [[ $MONOCHROME -eq 0 ]]; then
echo -e "\nColors Code for results: $NOTICE INFORMATIVE$NORMAL,$OK OK (RECOMMENDED VALUE)$NORMAL, $BAD WARNING (FIX REQUIRED)$NORMAL \n"
fi
}
 
# Generate Credential Report
genCredReport() {
textTitle "0.1" "Generating AWS IAM Credential Report..." "NOT_SCORED" "SUPPORT"
until $( $AWSCLI iam generate-credential-report --output text --query 'State' $PROFILE_OPT --region $REGION |grep -q -m 1 "COMPLETE") ; do
sleep 1
done
}
 
# Save report to a file, decode it, deletion at finish and after every single check
saveReport(){
$AWSCLI iam get-credential-report --query 'Content' --output text $PROFILE_OPT --region $REGION | decode_report > $TEMP_REPORT_FILE
if [[ $KEEPCREDREPORT -eq 1 ]]; then
textTitle "0.2" "Saving IAM Credential Report ..." "NOT_SCORED" "SUPPORT"
textNotice "IAM Credential Report saved in $TEMP_REPORT_FILE"
fi
}
 
# Delete temporary report file
cleanTemp(){
if [[ $KEEPCREDREPORT -ne 1 ]]; then
rm -fr $TEMP_REPORT_FILE
fi
}
 
# Delete the temporary report file if we get interrupted/terminated
trap cleanTemp EXIT
 
# Get a list of all available AWS Regions
REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \
--output text \
$PROFILE_OPT \
--region $REGION \
--region-names $FILTERREGION)
 
infoReferenceLong(){
# Report review note:
echo -e ""
echo -e "For more information on the Prowler, feedback and issue reporting:"
echo -e "https://github.com/Alfresco/prowler"
echo -e ""
echo -e "For more information on the CIS benchmark:"
echo -e "https://benchmarks.cisecurity.org/tools2/amazon/CIS_Amazon_Web_Services_Foundations_Benchmark_v1.1.0.pdf"
 
}
 
check11(){
# "Avoid the use of the root account (Scored)."
COMMAND11=$(cat $TEMP_REPORT_FILE| grep '<root_account>' | cut -d, -f5,11,16 | sed 's/,/\ /g')
textTitle "$ID11" "$TITLE11" "SCORED" "LEVEL1"
textNotice "Root account last accessed (password key_1 key_2): $COMMAND11"
}
 
check12(){
# "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 "$i " |grep false | awk '{ print $1 }'
done)
textTitle "$ID12" "$TITLE12" "SCORED" "LEVEL1"
if [[ $COMMAND12 ]]; then
for u in $COMMAND12; do
textWarn "User $u has Password enabled but MFA disabled"
done
else
textOK "No users found with Password enabled and MFA disabled"
fi
}
 
check13(){
# "Ensure credentials unused for 90 days or greater are disabled (Scored)"
textTitle "$ID13" "$TITLE13" "SCORED" "LEVEL1"
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_OPT --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_OPT --region $REGION | cut -d'T' -f1)
HOWOLDER=$(how_older_from_today $DATEUSED)
if [ $HOWOLDER -gt "90" ];then
textWarn "User \"$i\" has not logged in during the last 90 days "
else
textOK "User \"$i\" found with credentials used in the last 90 days"
fi
done
fi
else
textOK "No users found with password enabled"
fi
 
}
 
check14(){
# "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 }')
textTitle "$ID14" "$TITLE14" "SCORED" "LEVEL1"
C14_NUM_USERS1=0
C14_NUM_USERS2=0
if [[ $LIST_OF_USERS_WITH_ACCESS_KEY1 ]]; then
# textWarn "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 -v user_creation_time | 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
textWarn " $user has not rotated access key1 in over 90 days "
C14_NUM_USERS1=$(expr $C14_NUM_USERS1 + 1)
fi
done
if [[ $C14_NUM_USERS1 -eq 0 ]]; then
textOK "No users with access key 1 older than 90 days."
fi
else
textOK "No users with access key 1."
fi
 
if [[ $LIST_OF_USERS_WITH_ACCESS_KEY2 ]]; then
# textWarn "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 -v user_creation_time | 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
textWarn " $user has not rotated access key2. "
C14_NUM_USERS2=$(expr $C14_NUM_USERS2 + 1)
fi
done
if [[ $C14_NUM_USERS2 -eq 0 ]]; then
textOK "No users with access key 2 older than 90 days."
fi
else
textOK "No users with access key 2."
fi
}
 
check15(){
# "Ensure IAM password policy requires at least one uppercase letter (Scored)"
COMMAND15=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireUppercaseCharacters' 2> /dev/null) # must be true
textTitle "$ID15" "$TITLE15" "SCORED" "LEVEL1"
if [[ "$COMMAND15" == "true" ]];then
textOK "Password Policy requires upper case"
else
textWarn "Password Policy missing upper-case requirement"
fi
}
 
check16(){
# "Ensure IAM password policy require at least one lowercase letter (Scored)"
COMMAND16=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireLowercaseCharacters' 2> /dev/null) # must be true
textTitle "$ID16" "$TITLE16" "SCORED" "LEVEL1"
if [[ "$COMMAND16" == "true" ]];then
textOK "Password Policy requires lower case"
else
textWarn "Password Policy missing lower-case requirement"
fi
}
 
check17(){
# "Ensure IAM password policy require at least one symbol (Scored)"
COMMAND17=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireSymbols' 2> /dev/null) # must be true
textTitle "$ID17" "$TITLE17" "SCORED" "LEVEL1"
if [[ "$COMMAND17" == "true" ]];then
textOK "Password Policy requires symbol"
else
textWarn "Password Policy missing symbol requirement"
fi
}
 
check18(){
# "Ensure IAM password policy require at least one number (Scored)"
COMMAND18=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireNumbers' 2> /dev/null) # must be true
textTitle "$ID18" "$TITLE18" "SCORED" "LEVEL1"
if [[ "$COMMAND18" == "true" ]];then
textOK "Password Policy requires number"
else
textWarn "Password Policy missing number requirement"
fi
}
 
check19(){
# "Ensure IAM password policy requires minimum length of 14 or greater (Scored)"
COMMAND19=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.MinimumPasswordLength' 2> /dev/null)
textTitle "$ID19" "$TITLE19" "SCORED" "LEVEL1"
if [[ $COMMAND19 -gt "13" ]];then
textOK "Password Policy requires more than 13 characters"
else
textWarn "Password Policy missing or weak length requirement"
fi
}
 
check110(){
# "Ensure IAM password policy prevents password reuse: 24 or greater (Scored)"
COMMAND110=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --query 'PasswordPolicy.PasswordReusePrevention' --output text 2> /dev/null)
textTitle "$ID110" "$TITLE110" "SCORED" "LEVEL1"
if [[ $COMMAND110 ]];then
if [[ $COMMAND110 -gt "23" ]];then
textOK "Password Policy limits reuse"
else
textWarn "Password Policy has weak reuse requirement (lower than 24)"
fi
else
textWarn "Password Policy missing reuse requirement"
fi
}
 
check111(){
# "Ensure IAM password policy expires passwords within 90 days or less (Scored)"
COMMAND111=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json | grep MaxPasswordAge | awk -F: '{ print $2 }'|sed 's/\ //g'|sed 's/,/ /g' 2> /dev/null)
textTitle "$ID111" "$TITLE111" "SCORED" "LEVEL1"
if [[ $COMMAND111 ]];then
if [ "$COMMAND111" == "90" ];then
textOK "Password Policy includes expiration"
fi
else
textWarn "Password expiration not set or set greater than 90 days "
fi
}
 
check112(){
# "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 }')
textTitle "$ID112" "$TITLE112" "SCORED" "LEVEL1"
if [ "$ROOTKEY1" == "false" ];then
textOK "No access key 1 found for root"
else
textWarn "Found access key 1 for root "
fi
if [ "$ROOTKEY2" == "false" ];then
textOK "No access key 2 found for root"
else
textWarn "Found access key 2 for root "
fi
}
 
check113(){
# "Ensure MFA is enabled for the root account (Scored)"
COMMAND113=$($AWSCLI iam get-account-summary $PROFILE_OPT --region $REGION --output json --query 'SummaryMap.AccountMFAEnabled')
textTitle "$ID113" "$TITLE113" "SCORED" "LEVEL1"
if [ "$COMMAND113" == "1" ]; then
textOK "Virtual MFA is enabled for root"
else
textWarn "MFA is not ENABLED for root account "
fi
}
 
check114(){
# "Ensure hardware MFA is enabled for the root account (Scored)"
COMMAND113=$($AWSCLI iam get-account-summary $PROFILE_OPT --region $REGION --output json --query 'SummaryMap.AccountMFAEnabled')
textTitle "$ID114" "$TITLE114" "SCORED" "LEVEL1"
if [ "$COMMAND113" == "1" ]; then
COMMAND114=$($AWSCLI iam list-virtual-mfa-devices $PROFILE_OPT --region $REGION --output text --assignment-status Assigned --query 'VirtualMFADevices[*].[SerialNumber]' | grep '^arn:aws:iam::[0-9]\{12\}:mfa/root-account-mfa-device$')
if [[ "$COMMAND114" ]]; then
textWarn "Only Virtual MFA is enabled for root"
else
textOK "Hardware MFA is enabled for root "
fi
else
textWarn "MFA is not ENABLED for root account "
fi
}
 
check115(){
# "Ensure security questions are registered in the AWS account (Not Scored)"
textTitle "$ID115" "$TITLE115" "NOT_SCORED" "LEVEL2"
textNotice "No command available for check 1.15 "
textNotice "Login to the AWS Console as root & click on the Account "
textNotice "Name -> My Account -> Configure Security Challenge Questions "
}
 
check116(){
# "Ensure IAM policies are attached only to groups or roles (Scored)"
textTitle "$ID116" "$TITLE116" "SCORED" "LEVEL1"
LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text $PROFILE_OPT --region $REGION)
C116_NUM_USERS=0
for user in $LIST_USERS;do
USER_POLICY=$($AWSCLI iam list-attached-user-policies --output text $PROFILE_OPT --region $REGION --user-name $user)
if [[ $USER_POLICY ]]; then
textWarn "$user has policy directly attached "
C116_NUM_USERS=$(expr $C116_NUM_USERS + 1)
fi
done
if [[ $C116_NUM_USERS -eq 0 ]]; then
textOK "No policies attached to users."
fi
}
 
check117(){
# "Enable detailed billing (Scored)"
# No command available
textTitle "$ID117" "$TITLE117" "SCORED" "LEVEL1"
textNotice "No command available for check 1.17 "
textNotice "See section 1.17 on the CIS Benchmark guide for details "
}
 
check118(){
# "Ensure IAM Master and IAM Manager roles are active (Scored)"
textTitle "$ID118" "$TITLE118" "SCORED" "LEVEL1"
FINDMASTERANDMANAGER=$($AWSCLI iam list-roles $PROFILE_OPT --region $REGION --query "Roles[*].{RoleName:RoleName}" --output text | grep -E 'Master|Manager'| tr '\n' ' ')
if [[ $FINDMASTERANDMANAGER ]];then
textNotice "Found next roles as possible IAM Master and IAM Manager candidates: "
textNotice "$FINDMASTERANDMANAGER "
textNotice "run the commands below to check their policies with section 1.18 in the guide..."
for role in $FINDMASTERANDMANAGER;do
# find inline policies in found roles
INLINEPOLICIES=$($AWSCLI iam list-role-policies --role-name $role $PROFILE_OPT --region $REGION --query "PolicyNames[*]" --output text)
for policy in $INLINEPOLICIES;do
textNotice "INLINE: $AWSCLI iam get-role-policy --role-name $role --policy-name $policy $PROFILE_OPT --region $REGION --output json"
done
# find attached policies in found roles
ATTACHEDPOLICIES=$($AWSCLI iam list-attached-role-policies --role-name $role $PROFILE_OPT --region $REGION --query "AttachedPolicies[*]" --output text)
for policy in $ATTACHEDPOLICIES;do
textNotice "ATTACHED: $AWSCLI iam get-role-policy --role-name $role --policy-name $policy $PROFILE_OPT --region $REGION --output json"
done
done
else
textWarn "IAM Master and IAM Manager roles not found"
fi
}
 
check119(){
# "Maintain current contact details (Scored)"
# No command available
textTitle "$ID119" "$TITLE119" "SCORED" "LEVEL1"
textNotice "No command available for check 1.19 "
textNotice "See section 1.19 on the CIS Benchmark guide for details "
}
 
check120(){
# "Ensure security contact information is registered (Scored)"
# No command available
textTitle "$ID120" "$TITLE120" "SCORED" "LEVEL1"
textNotice "No command available for check 1.20 "
textNotice "See section 1.20 on the CIS Benchmark guide for details "
}
 
check121(){
# "Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)"
textTitle "$ID121" "$TITLE121" "NOT_SCORED" "LEVEL2"
textNotice "No command available for check 1.21 "
textNotice "See section 1.21 on the CIS Benchmark guide for details "
}
 
check122(){
# "Ensure a support role has been created to manage incidents with AWS Support (Scored)"
textTitle "$ID122" "$TITLE122" "SCORED" "LEVEL1"
SUPPORTPOLICYARN=$($AWSCLI iam list-policies --query "Policies[?PolicyName == 'AWSSupportAccess'].Arn" $PROFILE_OPT --region $REGION --output text)
if [[ $SUPPORTPOLICYARN ]];then
for policyarn in $SUPPORTPOLICYARN;do
POLICYUSERS=$($AWSCLI iam list-entities-for-policy --policy-arn $SUPPORTPOLICYARN $PROFILE_OPT --region $REGION --output json)
if [[ $POLICYUSERS ]];then
textOK "Support Policy attached to $policyarn"
for user in $(echo "$POLICYUSERS" | grep UserName | cut -d'"' -f4) ; do
textNotice "User $user has support access via $policyarn"
done
# textNotice "Make sure your team can create a Support case with AWS "
else
textWarn "Support Policy not applied to any Group / User / Role "
fi
done
else
textWarn "No Support Policy found"
fi
}
 
check123(){
# "Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)"
textTitle "$ID123" "$TITLE123" "NOT_SCORED" "LEVEL1"
LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text $PROFILE_OPT --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
for user in $LIST_USERS_KEY1_ACTIVE; do
textNotice "$user has never used Access Key 1"
done
else
textOK "No users found with Access Key 1 never used"
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
for user in $LIST_USERS_KEY2_ACTIVE; do
textNotice "$user has never used Access Key 2"
done
else
textOK "No users found with Access Key 2 never used"
fi
}
 
check124(){
# "Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)"
textTitle "$ID124" "$TITLE124" "SCORED" "LEVEL1"
LIST_CUSTOM_POLICIES=$($AWSCLI iam list-policies --output text $PROFILE_OPT --region $REGION|grep 'arn:aws:iam::[0-9]\{12\}:'|awk '{ print $2 }')
if [[ $LIST_CUSTOM_POLICIES ]]; then
textNotice "Looking for custom policies: (skipping default policies - it may take few seconds...)"
for policy in $LIST_CUSTOM_POLICIES; do
POLICY_VERSION=$($AWSCLI iam list-policies $PROFILE_OPT --region $REGION --query 'Policies[*].[Arn,DefaultVersionId]' --output text |awk "\$1 == \"$policy\" { 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_OPT --region $REGION)
if [[ $POLICY_WITH_FULL ]]; then
POLICIES_ALLOW_LIST="$POLICIES_ALLOW_LIST $policy"
fi
done
if [[ $POLICIES_ALLOW_LIST ]]; then
textNotice "List of custom policies: "
for policy in $POLICIES_ALLOW_LIST; do
textNotice "Policy $policy allows \"*:*\""
done
else
textOK "No custom policy found that allow full \"*:*\" administrative privileges"
fi
else
textOK "No custom policies found"
fi
}
 
check21(){
# "Ensure CloudTrail is enabled in all regions (Scored)"
textTitle "$ID21" "$TITLE21" "SCORED" "LEVEL1"
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --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_OPT --region $REGION --query 'trailList[*].IsMultiRegionTrail' --output text --trail-name-list $trail)
if [[ "$MULTIREGION_TRAIL_STATUS" == 'False' ]];then
textWarn "$trail trail in $REGION is not enabled in multi region mode"
else
textOK "$trail trail in $REGION is enabled for all regions"
fi
done
else
textWarn "No CloudTrail trails found!"
fi
}
 
check22(){
# "Ensure CloudTrail log file validation is enabled (Scored)"
textTitle "$ID22" "$TITLE22" "SCORED" "LEVEL2"
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --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_OPT --region $REGION --query 'trailList[*].LogFileValidationEnabled' --output text --trail-name-list $trail)
if [[ "$LOGFILEVALIDATION_TRAIL_STATUS" == 'False' ]];then
textWarn "$trail trail in $REGION has not log file validation enabled"
else
textOK "$trail trail in $REGION has log file validation enabled"
fi
done
else
textWarn "No CloudTrail trails found!"
fi
}
 
check23(){
# "Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)"
textTitle "$ID23" "$TITLE23" "SCORED" "LEVEL1"
CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text $PROFILE_OPT --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_OPT --region $REGION --output text)
if [[ $CLOUDTRAILBUCKET_HASALLPERMISIONS ]];then
textWarn "check your $bucket CloudTrail bucket ACL and Policy!"
else
textOK "Bucket $bucket is set correctly"
fi
done
else
textWarn "No CloudTrail bucket found!"
fi
}
 
check24(){
# "Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)"
textTitle "$ID24" "$TITLE24" "SCORED" "LEVEL1"
TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].{Name:Name, HomeRegion:HomeRegion}' --output text | tr "\t" ',')
if [[ $TRAILS_AND_REGIONS ]];then
for reg_trail in $TRAILS_AND_REGIONS;do
trail=$(echo $reg_trail | cut -d',' -f2)
TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1)
LATESTDELIVERY_TIMESTAMP=$($AWSCLI cloudtrail get-trail-status --name $trail $PROFILE_OPT --region $TRAIL_REGION --query 'LatestCloudWatchLogsDeliveryTime' --output text|grep -v None)
if [[ ! $LATESTDELIVERY_TIMESTAMP ]];then
textWarn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)"
else
LATESTDELIVERY_DATE=$(timestamp_to_date $LATESTDELIVERY_TIMESTAMP)
HOWOLDER=$(how_older_from_today $LATESTDELIVERY_DATE)
if [ $HOWOLDER -gt "1" ];then
textWarn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)"
else
textOK "$trail trail has been logging during the last 24h (it is in $TRAIL_REGION)"
fi
fi
done
else
textWarn "No CloudTrail trails found!"
fi
}
 
check25(){
# "Ensure AWS Config is enabled in all regions (Scored)"
textTitle "$ID25" "$TITLE25" "SCORED" "LEVEL1"
for regx in $REGIONS; do
CHECK_AWSCONFIG_STATUS=$($AWSCLI configservice get-status $PROFILE_OPT --region $regx --output json| grep "recorder: ON")
if [[ $CHECK_AWSCONFIG_STATUS ]];then
textOK "Region $regx has AWS Config recorder: ON" "$regx"
else
textWarn "Region $regx has AWS Config disabled or not configured" "$regx"
fi
done
}
 
check26(){
# "Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)"
textTitle "$ID26" "$TITLE26" "SCORED" "LEVEL1"
CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text $PROFILE_OPT --region $REGION)
if [[ $CLOUDTRAILBUCKET ]];then
for bucket in $CLOUDTRAILBUCKET;do
CLOUDTRAILBUCKET_LOGENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket $PROFILE_OPT --region $REGION --query 'LoggingEnabled.TargetBucket' --output text|grep -v None)
if [[ $CLOUDTRAILBUCKET_LOGENABLED ]];then
textOK "Bucket access logging enabled in $bucket"
else
textWarn "access logging is not enabled in $bucket CloudTrail S3 bucket!"
fi
done
else
textWarn "CloudTrail bucket not found!"
fi
}
 
check27(){
# "Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)"
textTitle "$ID27" "$TITLE27" "SCORED" "LEVEL2"
CLOUDTRAILNAME=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].Name' --output text $PROFILE_OPT --region $REGION)
if [[ $CLOUDTRAILNAME ]];then
for trail in $CLOUDTRAILNAME;do
CLOUDTRAILENC_ENABLED=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --trail $trail --query 'trailList[*].KmsKeyId' --output text)
if [[ $CLOUDTRAILENC_ENABLED ]];then
textOK "KMS key found for $trail"
else
textWarn "encryption is not enabled in your CloudTrail trail $trail (KMS key not found)!"
fi
done
else
textWarn "CloudTrail bucket doesn't exist!"
fi
}
 
check28(){
# "Ensure rotation for customer created CMKs is enabled (Scored)"
textTitle "$ID28" "$TITLE28" "SCORED" "LEVEL2"
for regx in $REGIONS; do
CHECK_KMS_KEYLIST=$($AWSCLI kms list-keys $PROFILE_OPT --region $regx --output text --query 'Keys[*].KeyId')
if [[ $CHECK_KMS_KEYLIST ]];then
CHECK_KMS_KEYLIST_NO_DEFAULT=$(for key in $CHECK_KMS_KEYLIST ; do $AWSCLI kms describe-key --key-id $key $PROFILE_OPT --region $regx --output text --query 'KeyMetadata.[KeyId, KeyManager]'|grep -v 'AWS'|awk '{ print $1 }'; done)
for key in $CHECK_KMS_KEYLIST_NO_DEFAULT; do
CHECK_KMS_KEY_TYPE=$($AWSCLI kms describe-key --key-id $key $PROFILE_OPT --region $regx --query 'KeyMetadata.Origin' | sed 's/["]//g')
if [[ "$CHECK_KMS_KEY_TYPE" == "EXTERNAL" ]];then
textOK "Key $key in Region $regx Customer Uploaded Key Material." "$regx"
else
CHECK_KMS_KEY_ROTATION=$($AWSCLI kms get-key-rotation-status --key-id $key $PROFILE_OPT --region $regx --output text)
if [[ "$CHECK_KMS_KEY_ROTATION" == "True" ]];then
textOK "Key $key in Region $regx is set correctly"
else
textWarn "Key $key in Region $regx is not set to rotate!!!" "$regx"
fi
fi
done
 
else
textNotice "Region $regx doesn't have encryption keys" "$regx"
fi
done
}
 
check31(){
# "Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)"
textTitle "$ID31" "$TITLE31" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text| tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | grep $group | awk -F: '{ print $4 }')
#METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | awk '/UnauthorizedOperation/ || /AccessDenied/ {print $3}')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --output text | grep METRICFILTERS | awk 'BEGIN {IGNORECASE=1}; /UnauthorizedOperation/ || /AccessDenied/ {print $3};')
if [[ $METRICFILTER_SET ]];then
for metric in $METRICFILTER_SET; do
metric_name=$($AWSCLI logs describe-metric-filters $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --log-group-name $group --filter-name-prefix $metric --output text --query 'metricFilters[0].metricTransformations[0].metricName')
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[?MetricName==`'$metric_name'`]' --output text)
if [[ $HAS_ALARM_ASSOCIATED ]];then
CHECK31OK="$CHECK31OK $group:$metric"
else
CHECK31WARN="$CHECK31WARN $group:$metric"
fi
done
else
CHECK31WARN="$CHECK31WARN $group"
fi
done
 
if [[ $CHECK31OK ]]; then
for group in $CHECK31OK; do
metric=${group#*:}
group=${group%:*}
textOK "CloudWatch group $group found with metric filter $metric and alarms set for Unauthorized Operation and Access Denied"
done
fi
if [[ $CHECK31WARN ]]; then
for group in $CHECK31WARN; do
case $group in
*:*) metric=${group#*:}
group=${group%:*}
textWarn "CloudWatch group $group found with metric filter $metric but no alarms associated"
;;
*) textWarn "CloudWatch group $group found but no metric filters or alarms associated"
esac
done
fi
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check32(){
# "Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)"
textTitle "$ID32" "$TITLE32" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' |grep filterPattern|grep MFAUsed| awk '/ConsoleLogin/ && (/additionalEventData.MFAUsed.*\!=.*\"Yes/) {print $1}')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /ConsoleLogin/ || /MFAUsed/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms set for sign-in Console without MFA enabled"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check33(){
# "Ensure a log metric filter and alarm exist for usage of root account (Scored)"
textTitle "$ID33" "$TITLE33" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION |grep -E 'userIdentity.*Root.*AwsServiceEvent')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | tr '[:upper:]' '[:lower:]'| grep -Ei 'userIdentity|Root|AwsServiceEvent')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms set for usage of root account"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check34(){
# "Ensure a log metric filter and alarm exist for IAM policy changes (Scored)"
textTitle "$ID34" "$TITLE34" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_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
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /DeletePolicy/ || /DeletePolicies/ || /Policies/ || /Policy/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for IAM policy changes"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check35(){
# "Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)"
textTitle "$ID35" "$TITLE35" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateTrail.*UpdateTrail.*DeleteTrail.*StartLogging.*StopLogging')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /TrailChange/ || /Trails/ || /CreateTrail/ || /UpdateTrail/ || /DeleteTrail/ || /StartLogging/ || /StopLogging/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for CloudTrail configuration changes"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check36(){
# "Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)"
textTitle "$ID36" "$TITLE36" "SCORED" "LEVEL2"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'ConsoleLogin.*Failed')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /FailedLogin/ || /ConsoleLogin/ || /Failed/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for AWS Management Console authentication failures"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check37(){
# "Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)"
textTitle "$ID37" "$TITLE37" "SCORED" "LEVEL2"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'kms.amazonaws.com.*DisableKey.*ScheduleKeyDeletion')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /DisableKey/ || /ScheduleKeyDeletion/ || /kms/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for changes of customer created CMKs"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check38(){
# "Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)"
textTitle "$ID38" "$TITLE38" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 's3.amazonaws.com.*PutBucketAcl.*PutBucketPolicy.*PutBucketCors.*PutBucketLifecycle.*PutBucketReplication.*DeleteBucketPolicy.*DeleteBucketCors.*DeleteBucketLifecycle.*DeleteBucketReplication')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /S3/ || /BucketPolicy/ || /BucketPolicies/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for S3 bucket policy changes"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check39(){
# "Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)"
textTitle "$ID39" "$TITLE39" "SCORED" "LEVEL2"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'config.amazonaws.com.*StopConfigurationRecorder.*DeleteDeliveryChannel.*PutDeliveryChannel.*PutConfigurationRecorder')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /config/ || /ConfigurationRecorder/ || /DeliveryChannel/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for AWS Config configuration changes"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check310(){
# "Ensure a log metric filter and alarm exist for security group changes (Scored)"
textTitle "$ID310" "$TITLE310" "SCORED" "LEVEL2"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'AuthorizeSecurityGroupIngress.*AuthorizeSecurityGroupEgress.*RevokeSecurityGroupIngress.*RevokeSecurityGroupEgress.*CreateSecurityGroup.*DeleteSecurityGroup')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /SecurityGroup/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for security group changes"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check311(){
# "Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)"
textTitle "$ID311" "$TITLE311" "SCORED" "LEVEL2"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateNetworkAcl.*CreateNetworkAclEntry.*DeleteNetworkAcl.*DeleteNetworkAclEntry.*ReplaceNetworkAclEntry.*ReplaceNetworkAclAssociation')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /NetworkAcl/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for changes to NACLs"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check312(){
# "Ensure a log metric filter and alarm exist for changes to network gateways (Scored)"
textTitle "$ID312" "$TITLE312" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateCustomerGateway.*DeleteCustomerGateway.*AttachInternetGateway.*CreateInternetGateway.*DeleteInternetGateway.*DetachInternetGateway')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /InternetGateway/ || /CustomerGateway/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for changes to network gateways"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check313(){
# "Ensure a log metric filter and alarm exist for route table changes (Scored)"
textTitle "$ID313" "$TITLE313" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateRoute.*CreateRouteTable.*ReplaceRoute.*ReplaceRouteTableAssociation.*DeleteRouteTable.*DeleteRoute.*DisassociateRouteTable')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /Route/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for route table changes"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check314(){
# "Ensure a log metric filter and alarm exist for VPC changes (Scored)"
textTitle "$ID314" "$TITLE314" "SCORED" "LEVEL1"
CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
if [[ $CLOUDWATCH_GROUP ]];then
for group in $CLOUDWATCH_GROUP; do
CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateVpc.*DeleteVpc.*ModifyVpcAttribute.*AcceptVpcPeeringConnection.*CreateVpcPeeringConnection.*DeleteVpcPeeringConnection.*RejectVpcPeeringConnection.*AttachClassicLinkVpc.*DetachClassicLinkVpc.*DisableVpcClassicLink.*EnableVpcClassicLink')
if [[ $METRICFILTER_SET ]];then
HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /VPC/;')
if [[ $HAS_ALARM_ASSOCIATED ]];then
textOK "CloudWatch group $group found with metric filters and alarms for VPC changes"
else
textWarn "CloudWatch group $group found with metric filters but no alarms associated"
fi
else
textWarn "CloudWatch group $group found but no metric filters or alarms associated"
fi
done
else
textWarn "No CloudWatch group found for CloudTrail events"
fi
}
 
check315(){
# "Ensure appropriate subscribers to each SNS topic (Not Scored)"
textTitle "$ID315" "$TITLE315" "NOT_SCORED" "LEVEL1"
CAN_SNS_LIST_SUBS=1
for regx in $REGIONS; do
TOPICS_LIST=$($AWSCLI sns list-topics $PROFILE_OPT --region $regx --output text --query 'Topics[*].TopicArn')
ntopics=$(echo $TOPICS_LIST | wc -w )
if [[ $TOPICS_LIST && $CAN_SNS_LIST_SUBS -eq 1 ]];then
textNotice "Region $regx has $ntopics topics" "$regx"
for topic in $TOPICS_LIST; do
TOPIC_SHORT=$(echo $topic | awk -F: '{ print $6 }')
CHECK_TOPIC_LIST=$($AWSCLI sns list-subscriptions-by-topic --topic-arn $topic $PROFILE_OPT --region $regx --query 'Subscriptions[*].{Endpoint:Endpoint,Protocol:Protocol}' --output text --max-items $MAXITEMS 2> /dev/null)
if [[ $? -eq 255 ]]; then
# Permission error
export CAN_SNS_LIST_SUBS=0
ntopics=$(echo $TOPICS_LIST | wc -w )
textNotice "Region $regx / $ntopics Topics / Subscriptions NO_PERMISSION" "$regx"
break;
fi
if [[ "Z" != "Z${CHECK_TOPIC_LIST}" ]]; then
printf '%s\n' "$CHECK_TOPIC_LIST" | while IFS= read -r dest ; do
textNotice "Region $regx / Topic $TOPIC_SHORT / Subscription $dest" "$regx"
done
else
textWarn "Region $regx / Topic $TOPIC_SHORT / Subscription NONE" "$regx"
fi
done
elif [[ $CAN_SNS_LIST_SUBS -eq 0 ]]; then
textNotice "Region $regx has $ntopics topics - unable to list subscribers" "$regx"
# break
else
textOK "Region $regx has 0 topics" "$regx"
fi
done
}
 
check41(){
# "Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)"
textTitle "$ID41" "$TITLE41" "SCORED" "LEVEL1"
for regx in $REGIONS; do
SG_LIST=$($AWSCLI ec2 describe-security-groups --query 'SecurityGroups[?length(IpPermissions[?((FromPort==null && ToPort==null) || (FromPort<=`22` && ToPort>=`22`)) && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupId:GroupId}' $PROFILE_OPT --region $regx --output text)
if [[ $SG_LIST ]];then
for SG in $SG_LIST;do
textWarn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx" "$regx"
done
else
textOK "No Security Groups found in $regx with port 22 TCP open to 0.0.0.0/0" "$regx"
fi
done
}
 
check42(){
# "Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)"
textTitle "$ID42" "$TITLE42" "SCORED" "LEVEL1"
for regx in $REGIONS; do
SG_LIST=$($AWSCLI ec2 describe-security-groups --query 'SecurityGroups[?length(IpPermissions[?((FromPort==null && ToPort==null) || (FromPort<=`3389` && ToPort>=`3389`)) && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupId:GroupId}' $PROFILE_OPT --region $regx --output text)
if [[ $SG_LIST ]];then
for SG in $SG_LIST;do
textWarn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx" "$regx"
done
else
textOK "No Security Groups found in $regx with port 3389 TCP open to 0.0.0.0/0" "$regx"
fi
done
}
 
check43(){
# "Ensure VPC Flow Logging is Enabled in all VPCs (Scored)"
textTitle "$ID43" "$TITLE43" "SCORED" "LEVEL2"
for regx in $REGIONS; do
CHECK_FL=$($AWSCLI ec2 describe-flow-logs $PROFILE_OPT --region $regx --query 'FlowLogs[?FlowLogStatus==`ACTIVE`].LogGroupName' --output text)
if [[ $CHECK_FL ]];then
for FL in $CHECK_FL;do
textOK "VPCFlowLog is enabled for LogGroupName: $FL in Region $regx" "$regx"
done
else
textWarn "No VPCFlowLog has been found in Region $regx" "$regx"
fi
done
}
 
check44(){
# "Ensure the default security group of every VPC restricts all traffic (Scored)"
textTitle "$ID44" "$TITLE44" "SCORED" "LEVEL2"
for regx in $REGIONS; do
CHECK_SGDEFAULT=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --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
textWarn "Default Security Groups found that allow 0.0.0.0 IN or OUT traffic in Region $regx" "$regx"
else
textOK "No Default Security Groups open to 0.0.0.0 found in Region $regx" "$regx"
fi
done
}
 
check45(){
# "Ensure routing tables for VPC peering are \"least access\" (Not Scored)"
textTitle "$ID45" "$TITLE45" "NOT_SCORED" "LEVEL2"
textNotice "Looking for VPC peering in all regions... "
for regx in $REGIONS; do
LIST_OF_VPCS_PEERING_CONNECTIONS=$($AWSCLI ec2 describe-vpc-peering-connections --output text $PROFILE_OPT --region $regx --query 'VpcPeeringConnections[*].VpcPeeringConnectionId')
if [[ $LIST_OF_VPCS_PEERING_CONNECTIONS ]];then
textNotice "$regx: $LIST_OF_VPCS_PEERING_CONNECTIONS - review routing tables" "$regx"
#LIST_OF_VPCS=$($AWSCLI ec2 describe-vpcs $PROFILE_OPT --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_OPT --region $regx
# for vpc in $LIST_OF_VPCS; do
# VPCS_WITH_PEERING=$($AWSCLI ec2 describe-route-tables --filter "Name=vpc-id,Values=$vpc" $PROFILE_OPT --region $regx --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" |grep GatewayId|grep pcx-)
# done
#echo $VPCS_WITH_PEERING
else
textOK "$regx: No VPC peering found" "$regx"
fi
done
}
 
extra71(){
# "Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID71" "$TITLE71" "NOT_SCORED" "EXTRA"
 
ADMIN_GROUPS=''
AWS_GROUPS=$($AWSCLI $PROFILE_OPT iam list-groups --output text --query 'Groups[].GroupName')
for grp in $AWS_GROUPS; do
# aws --profile onlinetraining iam list-attached-group-policies --group-name Administrators --query 'AttachedPolicies[].PolicyArn' | grep 'arn:aws:iam::aws:policy/AdministratorAccess'
# list-attached-group-policies
CHECK_ADMIN_GROUP=$($AWSCLI $PROFILE_OPT iam list-attached-group-policies --group-name $grp --output json --query 'AttachedPolicies[].PolicyArn' | grep 'arn:aws:iam::aws:policy/AdministratorAccess')
if [[ $CHECK_ADMIN_GROUP ]]; then
ADMIN_GROUPS="$ADMIN_GROUPS $grp"
textNotice "$grp group provides administrative access"
ADMIN_USERS=$($AWSCLI $PROFILE_OPT iam get-group --group-name $grp --output json --query 'Users[].UserName' | grep '"' | cut -d'"' -f2 )
for auser in $ADMIN_USERS; do
# users in group are Administrators
# users
# check for user MFA device in credential report
USER_MFA_ENABLED=$( cat $TEMP_REPORT_FILE | grep "^$auser," | cut -d',' -f8)
if [[ "true" == $USER_MFA_ENABLED ]]; then
textOK "$auser / MFA Enabled / admin via group $grp"
else
textWarn "$auser / MFA DISABLED / admin via group $grp"
fi
done
else
textNotice "$grp group provides non-administrative access"
fi
done
# set +x
}
 
extra72(){
# "Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID72" "$TITLE72" "NOT_SCORED" "EXTRA"
textNotice "Looking for EBS Snapshots in all regions... "
for regx in $REGIONS; do
LIST_OF_EBS_SNAPSHOTS=$($AWSCLI ec2 describe-snapshots $PROFILE_OPT --region $regx --owner-ids $ACCOUNT_NUM --output text --query 'Snapshots[*].{ID:SnapshotId}' --max-items $MAXITEMS | grep -v None 2> /dev/null)
for snapshot in $LIST_OF_EBS_SNAPSHOTS; do
SNAPSHOT_IS_PUBLIC=$($AWSCLI ec2 describe-snapshot-attribute $PROFILE_OPT --region $regx --output text --snapshot-id $snapshot --attribute createVolumePermission --query "CreateVolumePermissions[?Group=='all']")
if [[ $SNAPSHOT_IS_PUBLIC ]];then
textWarn "$regx: $snapshot is currently Public!" "$regx"
else
textOK "$regx: $snapshot is not Public" "$regx"
fi
done
done
 
}
 
extra73(){
textTitle "$ID73" "$TITLE73" "NOT_SCORED" "EXTRA"
textNotice "Looking for open S3 Buckets (ACLs and Policies) in all regions... "
ALL_BUCKETS_LIST=$($AWSCLI s3api list-buckets --query 'Buckets[*].{Name:Name}' --profile $PROFILE --region $REGION --output text)
for bucket in $ALL_BUCKETS_LIST; do
extra73Thread $bucket &
done
wait
}
 
extra73Thread(){
bucket=$1
BUCKET_LOCATION=$($AWSCLI s3api get-bucket-location --bucket $bucket --profile $PROFILE --region $REGION --output text)
if [[ "None" == $BUCKET_LOCATION ]]; then
BUCKET_LOCATION="us-east-1"
fi
if [[ "EU" == $BUCKET_LOCATION ]]; then
BUCKET_LOCATION="eu-west-1"
fi
# check if AllUsers is in the ACL as Grantee
CHECK_BUCKET_ALLUSERS_ACL=$($AWSCLI s3api get-bucket-acl --profile $PROFILE --region $BUCKET_LOCATION --bucket $bucket --query "Grants[?Grantee.URI == 'http://acs.amazonaws.com/groups/global/AllUsers']" --output text |grep -v GRANTEE)
CHECK_BUCKET_ALLUSERS_ACL_SINGLE_LINE=$(echo -ne $CHECK_BUCKET_ALLUSERS_ACL)
# check if AuthenticatedUsers is in the ACL as Grantee, they will have access with sigened URL only
CHECK_BUCKET_AUTHUSERS_ACL=$($AWSCLI s3api get-bucket-acl --profile $PROFILE --region $BUCKET_LOCATION --bucket $bucket --query "Grants[?Grantee.URI == 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers']" --output text |grep -v GRANTEE)
CHECK_BUCKET_AUTHUSERS_ACL_SINGLE_LINE=$(echo -ne $CHECK_BUCKET_AUTHUSERS_ACL)
# to prevent error NoSuchBucketPolicy first clean the output controlling stderr
TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-${bucket}.policy.XXXXXXXXXX)
$AWSCLI s3api get-bucket-policy --profile $PROFILE --region $BUCKET_LOCATION --bucket $bucket --output text --query Policy > $TEMP_POLICY_FILE 2> /dev/null
# check if the S3 policy has Principal as *
CHECK_BUCKET_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | sed -e 's/[{}]/''/g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}'|awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep ^\"Principal|grep \*)
if [[ $CHECK_BUCKET_ALLUSERS_ACL || $CHECK_BUCKET_AUTHUSERS_ACL || $CHECK_BUCKET_ALLUSERS_POLICY ]];then
if [[ $CHECK_BUCKET_ALLUSERS_ACL ]];then
textWarn "$BUCKET_LOCATION: $bucket bucket is open to the Internet (Everyone) with permissions: $CHECK_BUCKET_ALLUSERS_ACL_SINGLE_LINE" "$regx"
fi
if [[ $CHECK_BUCKET_AUTHUSERS_ACL ]];then
textWarn "$BUCKET_LOCATION: $bucket bucket is open to Authenticated users (Any AWS user) with permissions: $CHECK_BUCKET_AUTHUSERS_ACL_SINGLE_LINE" "$regx"
fi
if [[ $CHECK_BUCKET_ALLUSERS_POLICY ]];then
textWarn "$BUCKET_LOCATION: $bucket bucket policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx"
fi
else
textOK "$BUCKET_LOCATION: $bucket bucket is not open" "$regx"
fi
rm -fr $TEMP_POLICY_FILE
}
 
extra74(){
# "Ensure there are no Security Groups without ingress filtering being used (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID74" "$TITLE74" "NOT_SCORED" "EXTRA"
textNotice "Looking for Security Groups in all regions... "
for regx in $REGIONS; do
LIST_OF_SECURITYGROUPS=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --filters "Name=ip-permission.cidr,Values=0.0.0.0/0" --query "SecurityGroups[].[GroupId]" --output text --max-items $MAXITEMS)
for SG_ID in $LIST_OF_SECURITYGROUPS; do
SG_NO_INGRESS_FILTER=$($AWSCLI ec2 describe-network-interfaces $PROFILE_OPT --region $regx --filters "Name=group-id,Values=$SG_ID" --query "length(NetworkInterfaces)" --output text)
if [[ $SG_NO_INGRESS_FILTER -ne 0 ]];then
textWarn "$regx: $SG_ID has not ingress filtering and it is being used!" "$regx"
else
textNotice "$regx: $SG_ID has not ingress filtering but it is no being used" "$regx"
fi
done
done
 
}
 
extra75(){
# "Ensure there are no Security Groups not being used (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID75" "$TITLE75" "NOT_SCORED" "EXTRA"
textNotice "Looking for Security Groups in all regions... "
for regx in $REGIONS; do
LIST_OF_SECURITYGROUPS=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --query "SecurityGroups[].[GroupId]" --output text --max-items $MAXITEMS)
for SG_ID in $LIST_OF_SECURITYGROUPS; do
SG_NOT_USED=$($AWSCLI ec2 describe-network-interfaces $PROFILE_OPT --region $regx --filters "Name=group-id,Values=$SG_ID" --query "length(NetworkInterfaces)" --output text)
if [[ $SG_NOT_USED -eq 0 ]];then
textWarn "$regx: $SG_ID is not being used!" "$regx"
else
textOK "$regx: $SG_ID is being used" "$regx"
fi
done
done
}
 
extra76(){
# "Ensure there are no EC2 AMIs set as Public (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID76" "$TITLE76" "NOT_SCORED" "EXTRA"
textNotice "Looking for AMIs in all regions... "
for regx in $REGIONS; do
LIST_OF_PUBLIC_AMIS=$($AWSCLI ec2 describe-images --owners self $PROFILE_OPT --region $regx --filters "Name=is-public,Values=true" --query 'Images[*].{ID:ImageId}' --output text)
if [[ $LIST_OF_PUBLIC_AMIS ]];then
for ami in $LIST_OF_PUBLIC_AMIS; do
textWarn "$regx: $ami is currently Public!" "$regx"
done
else
textOK "$regx: No Public AMIs found" "$regx"
fi
done
}
 
extra77(){
# "Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID77" "$TITLE77" "NOT_SCORED" "EXTRA"
textNotice "Looking for ECR repos in all regions... "
for regx in $REGIONS; do
LIST_OF_ECR_REPOS=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $regx --query 'repositories[*].{Name:repositoryName}' --output text)
for ecr_repo in $LIST_OF_ECR_REPOS; do
TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-ecr-repo.policy.XXXXXXXXXX)
$AWSCLI ecr get-repository-policy --repository-name $ecr_repo $PROFILE_OPT --region $regx --output text > $TEMP_POLICY_FILE 2> /dev/null
# check if the policy has Principal as *
CHECK_ECR_REPO_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep \"Principal|grep \*)
if [[ $CHECK_ECR_REPO_ALLUSERS_POLICY ]];then
textWarn "$regx: $ecr_repo policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx"
else
textOK "$regx: $ecr_repo is not open" "$regx"
fi
done
rm -fr $TEMP_POLICY_FILE
done
}
 
extra78(){
# "Ensure there are no Public Accessible RDS instances (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID78" "$TITLE78" "NOT_SCORED" "EXTRA"
textNotice "Looking for RDS instances in all regions... "
for regx in $REGIONS; do
LIST_OF_RDS_PUBLIC_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[?PubliclyAccessible==`true`].[DBInstanceIdentifier,Endpoint.Address]' --output text)
if [[ $LIST_OF_RDS_PUBLIC_INSTANCES ]];then
while read -r rds_instance;do
RDS_NAME=$(echo $rds_instance | awk '{ print $1; }')
RDS_DNSNAME=$(echo $rds_instance | awk '{ print $2; }')
textWarn "$regx: RDS instance: $RDS_NAME at $RDS_DNSNAME is set as Publicly Accessible!" "$regx"
done <<< "$LIST_OF_RDS_PUBLIC_INSTANCES"
else
textOK "$regx: no Publicly Accessible RDS instances found" "$regx"
fi
done
}
 
extra79(){
# "Check for internet facing Elastic Load Balancers (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID79" "$TITLE79" "NOT_SCORED" "EXTRA"
textNotice "Looking for Elastic Load Balancers in all regions... "
for regx in $REGIONS; do
LIST_OF_PUBLIC_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[?Scheme == `internet-facing`].[LoadBalancerName,DNSName]' --output text)
LIST_OF_PUBLIC_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[?Scheme == `internet-facing`].[LoadBalancerName,DNSName]' --output text)
LIST_OF_ALL_ELBS=$( echo $LIST_OF_PUBLIC_ELBS; echo $LIST_OF_PUBLIC_ELBSV2)
LIST_OF_ALL_ELBS_PER_LINE=$( echo $LIST_OF_ALL_ELBS| xargs -n2 )
if [[ $LIST_OF_ALL_ELBS ]];then
while read -r elb;do
ELB_NAME=$(echo $elb | awk '{ print $1; }')
ELB_DNSNAME=$(echo $elb | awk '{ print $2; }')
textWarn "$regx: ELB: $ELB_NAME at DNS: $ELB_DNSNAME is internet-facing!" "$regx"
done <<< "$LIST_OF_ALL_ELBS_PER_LINE"
else
textOK "$regx: no Internet Facing ELBs found" "$regx"
fi
done
}
 
extra710(){
# "Check for internet facing EC2 Instances (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID710" "$TITLE710" "NOT_SCORED" "EXTRA"
textNotice "Looking for instances in all regions... "
for regx in $REGIONS; do
LIST_OF_PUBLIC_INSTANCES=$($AWSCLI ec2 describe-instances $PROFILE_OPT --region $regx --query 'Reservations[*].Instances[?PublicIpAddress].[InstanceId,PublicIpAddress]' --output text)
if [[ $LIST_OF_PUBLIC_INSTANCES ]];then
while read -r instance;do
INSTANCE_ID=$(echo $instance | awk '{ print $1; }')
PUBLIC_IP=$(echo $instance | awk '{ print $2; }')
textWarn "$regx: Instance: $INSTANCE_ID at IP: $PUBLIC_IP is internet-facing!" "$regx"
done <<< "$LIST_OF_PUBLIC_INSTANCES"
else
textOK "$regx: no Internet Facing EC2 Instances found" "$regx"
fi
done
}
 
extra711(){
# "Check for Publicly Accessible Redshift Clusters (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID711" "$TITLE711" "NOT_SCORED" "EXTRA"
textNotice "Looking for Reshift clusters in all regions... "
for regx in $REGIONS; do
LIST_OF_PUBLIC_REDSHIFT_CLUSTERS=$($AWSCLI redshift describe-clusters $PROFILE_OPT --region $regx --query 'Clusters[?PubliclyAccessible == `true`].[ClusterIdentifier,Endpoint.Address]' --output text)
if [[ $LIST_OF_PUBLIC_REDSHIFT_CLUSTERS ]];then
while read -r cluster;do
CLUSTER_ID=$(echo $cluster | awk '{ print $1; }')
CLUSTER_ENDPOINT=$(echo $cluster | awk '{ print $2; }')
textWarn "$regx: Cluster: $CLUSTER_ID at Endpoint: $CLUSTER_ENDPOINT is publicly accessible!" "$regx"
done <<< "$LIST_OF_PUBLIC_REDSHIFT_CLUSTERS"
else
textOK "$regx: no Publicly Accessible Redshift Clusters found" "$regx"
fi
done
}
 
extra712(){
# "Check if Amazon Macie is enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID712" "$TITLE712" "NOT_SCORED" "EXTRA"
textNotice "No API commands available to check if Macie is enabled,"
textNotice "just looking if IAM Macie related permissions exist. "
MACIE_IAM_ROLES_CREATED=$($AWSCLI iam list-roles $PROFILE_OPT --query 'Roles[*].Arn'|grep AWSMacieServiceCustomer|wc -l)
if [[ $MACIE_IAM_ROLES_CREATED -eq 2 ]];then
textOK "Macie related IAM roles exist, so it might be enabled. Check it out manually."
else
textWarn "No Macie related IAM roles found. It is most likely not to be enabled"
fi
}
 
extra713(){
# "Check if GuardDuty is enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID713" "$TITLE713" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
LIST_OF_GUARDDUTY_DETECTORS=$($AWSCLI guardduty list-detectors $PROFILE_OPT --region $regx --output text |cut -f2)
if [[ $LIST_OF_GUARDDUTY_DETECTORS ]];then
while read -r detector;do
DETECTOR_ENABLED=$($AWSCLI guardduty get-detector --detector-id $detector $PROFILE_OPT --region $regx --output text| cut -f3|grep ENABLED)
if [[ $DETECTOR_ENABLED ]]; then
textOK "$regx: GuardDuty detector $detector enabled" "$regx"
else
textWarn "$regx: GuardDuty detector $detector configured but suspended" "$regx"
fi
done <<< "$LIST_OF_GUARDDUTY_DETECTORS"
else
textWarn "$regx: GuardDuty detector not configured!" "$regx"
fi
done
}
 
extra714(){
# "Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID714" "$TITLE714" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
LIST_OF_DISTRIBUTIONS=$($AWSCLI cloudfront list-distributions $PROFILE_OPT --region $regx --query 'DistributionList.Items[].Id' --output text |grep -v "^None")
if [[ $LIST_OF_DISTRIBUTIONS ]]; then
for cdn in $LIST_OF_DISTRIBUTIONS;do
CDN_LOG_ENABLED=$($AWSCLI cloudfront get-distribution $PROFILE_OPT --region $regx --id "$cdn" --query 'Distribution.DistributionConfig.Logging.Enabled' | grep true)
if [[ $CDN_LOG_ENABLED ]];then
textOK "$regx: CDN $cdn logging enabled" "$regx"
else
textWarn "$regx: CDN $cdn logging disabled!" "$regx"
fi
done
else
textNotice "$regx: No CDN configured" "$regx"
fi
done
}
 
extra715(){
# "Check if Elasticsearch Service domains have logging enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID715" "$TITLE715" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query DomainNames --output text)
if [[ $LIST_OF_DOMAINS ]]; then
for domain in $LIST_OF_DOMAINS;do
SEARCH_SLOWLOG_ENABLED=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.LogPublishingOptions.Options.SEARCH_SLOW_LOGS.Enabled --output text |grep -v ^None|grep -v ^False)
if [[ $SEARCH_SLOWLOG_ENABLED ]];then
textOK "$regx: ElasticSearch Service domain $domain SEARCH_SLOW_LOGS enabled" "$regx"
else
textWarn "$regx: ElasticSearch Service domain $domain SEARCH_SLOW_LOGS disabled!" "$regx"
fi
INDEX_SLOWLOG_ENABLED=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.LogPublishingOptions.Options.INDEX_SLOW_LOGS.Enabled --output text |grep -v ^None|grep -v ^False)
if [[ $INDEX_SLOWLOG_ENABLED ]];then
textOK "$regx: ElasticSearch Service domain $domain INDEX_SLOW_LOGS enabled" "$regx"
else
textWarn "$regx: ElasticSearch Service domain $domain INDEX_SLOW_LOGS disabled!" "$regx"
fi
done
else
textNotice "$regx: No Elasticsearch Service domain found" "$regx"
fi
done
}
 
extra716(){
# "Check if Elasticsearch Service domains allow open access (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID716" "$TITLE716" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query DomainNames --output text)
if [[ $LIST_OF_DOMAINS ]]; then
for domain in $LIST_OF_DOMAINS;do
CHECK_IF_MEMBER_OF_VPC=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.VPCOptions.Options.VPCId --output text|grep -v ^None)
if [[ ! $CHECK_IF_MEMBER_OF_VPC ]];then
TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-es-domain.policy.XXXXXXXXXX)
$AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.AccessPolicies.Options --output text > $TEMP_POLICY_FILE 2> /dev/null
# check if the policy has Principal as *
CHECK_ES_DOMAIN_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep \"Principal|grep \*)
if [[ $CHECK_ES_DOMAIN_ALLUSERS_POLICY ]];then
textWarn "$regx: $domain policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx"
else
textOK "$regx: $domain is not open" "$regx"
fi
else
textOK "$regx: $domain is in a VPC" "$regx"
fi
done
fi
textNotice "$regx: No Elasticsearch Service domain found" "$regx"
rm -fr $TEMP_POLICY_FILE
done
}
 
extra717(){
# "Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID717" "$TITLE717" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
LIST_OF_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[*].LoadBalancerName' --output text|xargs -n1)
LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[*].LoadBalancerArn' --output text|xargs -n1)
if [[ $LIST_OF_ELBS || $LIST_OF_ELBSV2 ]]; then
if [[ $LIST_OF_ELBS ]]; then
for elb in $LIST_OF_ELBS; do
CHECK_ELBS_LOG_ENABLED=$($AWSCLI elb describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-name $elb --query 'LoadBalancerAttributes.AccessLog.Enabled'|grep "^true")
if [[ $CHECK_ELBS_LOG_ENABLED ]]; then
textOK "$regx: $elb has access logs to S3 configured" "$regx"
else
textWarn "$regx: $elb has not configured access logs" "$regx"
fi
done
fi
if [[ $LIST_OF_ELBSV2 ]]; then
for elbarn in $LIST_OF_ELBSV2; do
CHECK_ELBSV2_LOG_ENABLED=$($AWSCLI elbv2 describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-arn $elbarn --query Attributes[*] --output text|grep "^access_logs.s3.enabled"|cut -f2|grep true)
ELBV2_NAME=$(echo $elbarn|cut -d\/ -f3)
if [[ $CHECK_ELBSV2_LOG_ENABLED ]]; then
textOK "$regx: $ELBV2_NAME has access logs to S3 configured" "$regx"
else
textWarn "$regx: $ELBV2_NAME has not configured access logs" "$regx"
fi
done
fi
else
textNotice "$regx: No ELBs found" "$regx"
fi
done
}
 
extra718(){
# "Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID718" "$TITLE718" "NOT_SCORED" "EXTRA"
LIST_OF_BUCKETS=$($AWSCLI s3api list-buckets $PROFILE_OPT --query Buckets[*].Name --output text|xargs -n1)
if [[ $LIST_OF_BUCKETS ]]; then
for bucket in $LIST_OF_BUCKETS;do
BUCKET_SERVER_LOG_ENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket $PROFILE_OPT --query [LoggingEnabled] --output text|grep -v "^None$")
if [[ $BUCKET_SERVER_LOG_ENABLED ]];then
textOK "Bucket $bucket has server access logging enabled"
else
textWarn "Bucket $bucket has server access logging disabled!"
fi
done
else
textNotice "No S3 Buckets found"
fi
}
 
extra719(){
# "Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID719" "$TITLE719" "NOT_SCORED" "EXTRA"
LIST_OF_HOSTED_ZONES=$($AWSCLI route53 list-hosted-zones $PROFILE_OPT --query HostedZones[*].Id --output text|xargs -n1)
if [[ $LIST_OF_HOSTED_ZONES ]]; then
for hostedzoneid in $LIST_OF_HOSTED_ZONES;do
HOSTED_ZONE_QUERY_LOG_ENABLED=$($AWSCLI route53 list-query-logging-configs --hosted-zone-id $hostedzoneid $PROFILE_OPT --query QueryLoggingConfigs[*].CloudWatchLogsLogGroupArn --output text|cut -d: -f7)
if [[ $HOSTED_ZONE_QUERY_LOG_ENABLED ]];then
textOK "Route53 hosted zone Id $hostedzoneid has query logging enabled in Log Group $HOSTED_ZONE_QUERY_LOG_ENABLED"
else
textWarn "Route53 hosted zone Id $hostedzoneid has query logging disabled!"
fi
done
else
textNotice "No Route53 hosted zones found"
fi
}
 
extra720(){
# "Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID720" "$TITLE720" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
LIST_OF_FUNCTIONS=$($AWSCLI lambda list-functions $PROFILE_OPT --region $regx --query Functions[*].FunctionName --output text)
if [[ $LIST_OF_FUNCTIONS ]]; then
for lambdafunction in $LIST_OF_FUNCTIONS;do
LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query trailList[?HomeRegion==\`$regx\`].Name --output text)
if [[ $LIST_OF_TRAILS ]]; then
for trail in $LIST_OF_TRAILS; do
FUNCTION_ENABLED_IN_TRAIL=$($AWSCLI cloudtrail get-event-selectors $PROFILE_OPT --trail-name $trail --region $regx --query "EventSelectors[*].DataResources[?Type == \`AWS::Lambda::Function\`].Values" --output text |xargs -n1| grep -E "^arn:aws:lambda.*function:$lambdafunction$")
if [[ $FUNCTION_ENABLED_IN_TRAIL ]]; then
textOK "$regx: Lambda function $lambdafunction enabled in trail $trail" "$regx"
else
textWarn "$regx: Lambda function $lambdafunction NOT enabled in trail $trail" "$regx"
fi
done
# LIST_OF_MULTIREGION_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query "trailList[?IsMultiRegionTrail == \`true\`].Name" --output text)
# if [[ $LIST_OF_MULTIREGION_TRAILS ]]; then
# for trail in $LIST_OF_MULTIREGION_TRAILS; do
# REGION_OF_TRAIL=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query "trailList[?IsMultiRegionTrail == \`true\` && Name == \`$trail\` ].HomeRegion" --output text)
# FUNCTION_ENABLED_IN_THIS_REGION=$($AWSCLI cloudtrail get-event-selectors $PROFILE_OPT --trail-name $trail --region $REGION_OF_TRAIL --query "EventSelectors[*].DataResources[?Type == \`AWS::Lambda::Function\`].Values" --output text |xargs -n1| grep -E "^arn:aws:lambda.*function:$lambdafunction$")
# if [[ $FUNCTION_ENABLED_IN_THIS_REGION ]]; then
# textOK "$regx: Lambda function $lambdafunction enabled in trail $trail" "$regx"
# else
# textWarn "$regx: Lambda function $lambdafunction NOT enabled in trail $trail" "$regx"
# fi
# done
# else
# textWarn "$regx: Lambda function $lambdafunction is not being recorded!" "$regx"
# fi
else
textWarn "$regx: Lambda function $lambdafunction is not being recorded no CloudTrail found!" "$regx"
fi
done
else
textNotice "$regx: No Lambda functions found" "$regx"
fi
done
}
 
extra721(){
# "Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID721" "$TITLE721" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
LIST_OF_REDSHIFT_CLUSTERS=$($AWSCLI redshift describe-clusters $PROFILE_OPT --region $regx --query 'Clusters[*].ClusterIdentifier' --output text)
if [[ $LIST_OF_REDSHIFT_CLUSTERS ]]; then
for redshiftcluster in $LIST_OF_REDSHIFT_CLUSTERS;do
REDSHIFT_LOG_ENABLED=$($AWSCLI redshift describe-logging-status $PROFILE_OPT --region $regx --cluster-identifier $redshiftcluster --query LoggingEnabled --output text | grep True)
if [[ $REDSHIFT_LOG_ENABLED ]];then
REDSHIFT_LOG_ENABLED_BUCKET=$($AWSCLI redshift describe-logging-status $PROFILE_OPT --region $regx --cluster-identifier $redshiftcluster --query BucketName --output text)
textOK "$regx: Redshift cluster $redshiftcluster has audit logging enabled to bucket $REDSHIFT_LOG_ENABLED_BUCKET" "$regx"
else
textWarn "$regx: Redshift cluster $redshiftcluster logging disabled!" "$regx"
fi
done
else
textNotice "$regx: No Redshift cluster configured" "$regx"
fi
done
}
 
extra722(){
# "Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID722" "$TITLE722" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
LIST_OF_API_GW=$($AWSCLI apigateway get-rest-apis $PROFILE_OPT --region $regx --query items[*].id --output text)
if [[ $LIST_OF_API_GW ]];then
for apigwid in $LIST_OF_API_GW;do
API_GW_NAME=$($AWSCLI apigateway get-rest-apis $PROFILE_OPT --region $regx --query "items[?id==\`$apigwid\`].name" --output text)
CHECK_STAGES_NAME=$($AWSCLI apigateway get-stages $PROFILE_OPT --region $regx --rest-api-id $apigwid --query "item[*].stageName" --output text)
if [[ $CHECK_STAGES_NAME ]];then
for stagname in $CHECK_STAGES_NAME;do
CHECK_STAGE_METHOD_LOGGING=$($AWSCLI apigateway get-stages $PROFILE_OPT --region $regx --rest-api-id $apigwid --query "item[?stageName == \`$stagname\` ].methodSettings" --output text|awk '{ print $1" log level "$6}')
if [[ $CHECK_STAGE_METHOD_LOGGING ]];then
textOK "$regx: API Gateway $API_GW_NAME has stage logging enabled for $CHECK_STAGE_METHOD_LOGGING" "$regx"
else
textWarn "$regx: API Gateway $API_GW_NAME logging disabled for stage $stagname!" "$regx"
fi
done
else
textWarn "$regx: No Stage name found for $API_GW_NAME" "$regx"
fi
done
else
textNotice "$regx: No API Gateway found" "$regx"
fi
done
}
 
extra723(){
# "Check if RDS Snapshots are public (Not Scored) (Not part of CIS benchmark)"
textTitle "$ID723" "$TITLE723" "NOT_SCORED" "EXTRA"
for regx in $REGIONS; do
# RDS snapshots
LIST_OF_RDS_SNAPSHOTS=$($AWSCLI rds describe-db-snapshots $PROFILE_OPT --region $regx --query DBSnapshots[*].DBSnapshotIdentifier --output text)
if [[ $LIST_OF_RDS_SNAPSHOTS ]]; then
for rdssnapshot in $LIST_OF_RDS_SNAPSHOTS;do
SNAPSHOT_IS_PUBLIC=$($AWSCLI rds describe-db-snapshot-attributes $PROFILE_OPT --region $regx --db-snapshot-identifier $rdssnapshot --query DBSnapshotAttributesResult.DBSnapshotAttributes[*] --output text|grep ^ATTRIBUTEVALUES|cut -f2|grep all)
if [[ $SNAPSHOT_IS_PUBLIC ]];then
textWarn "$regx: RDS Snapshot $rdssnapshot is public!" "$regx"
else
textOK "$regx: RDS Snapshot $rdssnapshot is not shared" "$regx"
fi
done
else
textNotice "$regx: No RDS Snapshots found" "$regx"
fi
# RDS cluster snapshots
LIST_OF_RDS_CLUSTER_SNAPSHOTS=$($AWSCLI rds describe-db-cluster-snapshots $PROFILE_OPT --region $regx --query DBClusterSnapshots[*].DBClusterSnapshotIdentifier --output text)
if [[ $LIST_OF_RDS_CLUSTER_SNAPSHOTS ]]; then
for rdsclustersnapshot in $LIST_OF_RDS_CLUSTER_SNAPSHOTS;do
CLUSTER_SNAPSHOT_IS_PUBLIC=$($AWSCLI rds describe-db-cluster-snapshot-attributes $PROFILE_OPT --region $regx --db-cluster-snapshot-identifier $rdsclustersnapshot --query DBClusterSnapshotAttributesResult.DBClusterSnapshotAttributes[*] --output text|grep ^ATTRIBUTEVALUES|cut -f2|grep all)
if [[ $CLUSTER_SNAPSHOT_IS_PUBLIC ]];then
textWarn "$regx: RDS Cluster Snapshot $rdsclustersnapshot is public!" "$regx"
else
textOK "$regx: RDS Cluster Snapshot $rdsclustersnapshot is not shared" "$regx"
fi
done
else
textNotice "$regx: No RDS Cluster Snapshots found" "$regx"
fi
done
}
 
callCheck(){
if [[ $CHECKNUMBER ]];then
case "$CHECKNUMBER" in
check11|check101 ) check11;;
check12|check102 ) check12;;
check13|check103 ) check13;;
check14|check104 ) check14;;
check15|check105 ) check15;;
check16|check106 ) check16;;
check17|check107 ) check17;;
check18|check108 ) check18;;
check19|check109 ) check19;;
check110 ) check110;;
check111 ) check111;;
check112 ) check112;;
check113 ) check113;;
check114 ) check114;;
check115 ) check115;;
check116 ) check116;;
check117 ) check117;;
check118 ) check118;;
check119 ) check119;;
check120 ) check120;;
check121 ) check121;;
check122 ) check122;;
check123 ) check123;;
check124 ) check124;;
check21|check201 ) check21;;
check22|check202 ) check22;;
check23|check203 ) check23;;
check24|check204 ) check24;;
check25|check205 ) check25;;
check26|check206 ) check26;;
check27|check207 ) check27;;
check28|check208 ) check28;;
check31|check301 ) check31;;
check32|check302 ) check32;;
check33|check303 ) check33;;
check34|check304 ) check34;;
check35|check305 ) check35;;
check36|check306 ) check36;;
check37|check307 ) check37;;
check38|check308 ) check38;;
check39|check309 ) check39;;
check310 ) check310;;
check311 ) check311;;
check312 ) check312;;
check313 ) check313;;
check314 ) check314;;
check315 ) check315;;
check41|check401 ) check41;;
check42|check402 ) check42;;
check43|check403 ) check43;;
check44|check404 ) check44;;
check45|check405 ) check45;;
extra71|extra701 ) extra71;;
extra72|extra702 ) extra72;;
extra73|extra703 ) extra73;;
extra74|extra704 ) extra74;;
extra75|extra705 ) extra75;;
extra76|extra706 ) extra76;;
extra77|extra707 ) extra77;;
extra78|extra708 ) extra78;;
extra79|extra709 ) extra79;;
extra710 ) extra710;;
extra711 ) extra711;;
extra712 ) extra712;;
extra713 ) extra713;;
extra714 ) extra714;;
extra715 ) extra715;;
extra716 ) extra716;;
extra717 ) extra717;;
extra718 ) extra718;;
extra719 ) extra719;;
extra720 ) extra720;;
extra721 ) extra721;;
extra722 ) extra722;;
extra723 ) extra723;;
 
## Groups of Checks
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;
;;
check2 )
check21;check22;check23;check24;check25;check26;check27;check28
;;
check3 )
check31;check32;check33;check34;check35;check36;check37;check38;
check39;check310;check311;check312;check313;check314;check315
;;
check4 )
check41;check42;check43;check44;check45
;;
level1 )
check11;check12;check13;check14;check15;check16;check17;check18;
check19;check110;check111;check112;check113;check115;check116;check117;
check118;check119;check120;check122;check123;check124;check21;check23;
check24;check25;check26;check31;check32;check33;check34;check35;
check38;check312;check313;check314;check315;check41;check42
;;
level2 )
check11;check12;check13;check14;check15;check16;check17;check18;
check19;check110;check111;check112;check113;check114;check115;check116;
check117;check118;check119;check120;check121;check122;check123;check124;
check21;check22;check23;check24;check25;check26;check27;check28;check31;
check32;check33;check34;check35;check36;check37;check38;check39;
check310;check311;check312;check313;check314;check315;check41;check42;
check43;check44;check45
;;
extras )
extra71;extra72;extra73;extra74;extra75;extra76;extra77;extra78;
extra79;extra710;extra711;extra712;extra713;extra714;extra715;extra716;
extra717;extra718;extra719;extra720;extra721;extra722;extra723
;;
forensics-ready )
check21;check22;check23;check24;check25;check26;check27;
check43;
extra712;extra713;extra714;extra715;extra717;extra718;extra719;
extra720;extra721;extra722
;;
* )
textWarn "ERROR! Use a valid check name (i.e. check41 or extra71)\n";
esac
cleanTemp
exit $EXITCODE
fi
}
 
# List only check tittles
 
if [[ $PRINTCHECKSONLY == "1" ]]; then
prowlerBanner
textTitle "1" "$TITLE1" "NOT_SCORED" "SUPPORT"
textTitle "$ID11" "$TITLE11" "SCORED" "LEVEL1"
textTitle "$ID12" "$TITLE12" "SCORED" "LEVEL1"
textTitle "$ID13" "$TITLE13" "SCORED" "LEVEL1"
textTitle "$ID14" "$TITLE14" "SCORED" "LEVEL1"
textTitle "$ID15" "$TITLE15" "SCORED" "LEVEL1"
textTitle "$ID16" "$TITLE16" "SCORED" "LEVEL1"
textTitle "$ID17" "$TITLE17" "SCORED" "LEVEL1"
textTitle "$ID18" "$TITLE18" "SCORED" "LEVEL1"
textTitle "$ID19" "$TITLE19" "SCORED" "LEVEL1"
textTitle "$ID110" "$TITLE110" "SCORED" "LEVEL1"
textTitle "$ID111" "$TITLE111" "SCORED" "LEVEL1"
textTitle "$ID112" "$TITLE112" "SCORED" "LEVEL1"
textTitle "$ID113" "$TITLE113" "SCORED" "LEVEL1"
textTitle "$ID114" "$TITLE114" "SCORED" "LEVEL1"
textTitle "$ID115" "$TITLE115" "NOT_SCORED" "LEVEL2"
textTitle "$ID116" "$TITLE116" "SCORED" "LEVEL1"
textTitle "$ID117" "$TITLE117" "SCORED" "LEVEL1"
textTitle "$ID118" "$TITLE118" "SCORED" "LEVEL1"
textTitle "$ID119" "$TITLE119" "SCORED" "LEVEL1"
textTitle "$ID120" "$TITLE120" "SCORED" "LEVEL1"
textTitle "$ID121" "$TITLE121" "NOT_SCORED" "LEVEL2"
textTitle "$ID122" "$TITLE122" "SCORED" "LEVEL1"
textTitle "$ID123" "$TITLE123" "NOT_SCORED" "LEVEL1"
textTitle "$ID124" "$TITLE124" "SCORED" "LEVEL1"
textTitle "2" "$TITLE2" "NOT_SCORED" "SUPPORT"
textTitle "$ID21" "$TITLE21" "SCORED" "LEVEL1"
textTitle "$ID22" "$TITLE22" "SCORED" "LEVEL2"
textTitle "$ID23" "$TITLE23" "SCORED" "LEVEL1"
textTitle "$ID24" "$TITLE24" "SCORED" "LEVEL1"
textTitle "$ID25" "$TITLE25" "SCORED" "LEVEL1"
textTitle "$ID26" "$TITLE26" "SCORED" "LEVEL1"
textTitle "$ID27" "$TITLE27" "SCORED" "LEVEL2"
textTitle "$ID28" "$TITLE28" "SCORED" "LEVEL2"
textTitle "3" "$TITLE3" "NOT_SCORED" "SUPPORT"
textTitle "$ID31" "$TITLE31" "SCORED" "LEVEL1"
textTitle "$ID32" "$TITLE32" "SCORED" "LEVEL1"
textTitle "$ID33" "$TITLE33" "SCORED" "LEVEL1"
textTitle "$ID34" "$TITLE34" "SCORED" "LEVEL1"
textTitle "$ID35" "$TITLE35" "SCORED" "LEVEL1"
textTitle "$ID36" "$TITLE36" "SCORED" "LEVEL2"
textTitle "$ID37" "$TITLE37" "SCORED" "LEVEL2"
textTitle "$ID38" "$TITLE38" "SCORED" "LEVEL1"
textTitle "$ID39" "$TITLE39" "SCORED" "LEVEL2"
textTitle "$ID310" "$TITLE310" "SCORED" "LEVEL2"
textTitle "$ID311" "$TITLE311" "SCORED" "LEVEL2"
textTitle "$ID312" "$TITLE312" "SCORED" "LEVEL1"
textTitle "$ID313" "$TITLE313" "SCORED" "LEVEL1"
textTitle "$ID314" "$TITLE314" "SCORED" "LEVEL1"
textTitle "$ID315" "$TITLE315" "NOT_SCORED" "LEVEL1"
textTitle "4" "$TITLE4" "NOT_SCORED" "SUPPORT"
textTitle "$ID41" "$TITLE41" "SCORED" "LEVEL1"
textTitle "$ID42" "$TITLE42" "SCORED" "LEVEL1"
textTitle "$ID43" "$TITLE43" "SCORED" "LEVEL2"
textTitle "$ID44" "$TITLE44" "SCORED" "LEVEL2"
textTitle "$ID45" "$TITLE45" "NOT_SCORED" "LEVEL2"
textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT"
textTitle "$ID71" "$TITLE71" "NOT_SCORED" "EXTRA"
textTitle "$ID72" "$TITLE72" "NOT_SCORED" "EXTRA"
textTitle "$ID73" "$TITLE73" "NOT_SCORED" "EXTRA"
textTitle "$ID74" "$TITLE74" "NOT_SCORED" "EXTRA"
textTitle "$ID75" "$TITLE75" "NOT_SCORED" "EXTRA"
textTitle "$ID76" "$TITLE76" "NOT_SCORED" "EXTRA"
textTitle "$ID77" "$TITLE77" "NOT_SCORED" "EXTRA"
textTitle "$ID78" "$TITLE78" "NOT_SCORED" "EXTRA"
textTitle "$ID79" "$TITLE79" "NOT_SCORED" "EXTRA"
textTitle "$ID710" "$TITLE710" "NOT_SCORED" "EXTRA"
textTitle "$ID711" "$TITLE711" "NOT_SCORED" "EXTRA"
textTitle "$ID712" "$TITLE712" "NOT_SCORED" "EXTRA"
textTitle "$ID713" "$TITLE713" "NOT_SCORED" "EXTRA"
textTitle "$ID714" "$TITLE714" "NOT_SCORED" "EXTRA"
textTitle "$ID715" "$TITLE715" "NOT_SCORED" "EXTRA"
textTitle "$ID716" "$TITLE716" "NOT_SCORED" "EXTRA"
textTitle "$ID717" "$TITLE717" "NOT_SCORED" "EXTRA"
textTitle "$ID718" "$TITLE718" "NOT_SCORED" "EXTRA"
textTitle "$ID719" "$TITLE719" "NOT_SCORED" "EXTRA"
textTitle "$ID720" "$TITLE720" "NOT_SCORED" "EXTRA"
textTitle "$ID721" "$TITLE721" "NOT_SCORED" "EXTRA"
textTitle "$ID722" "$TITLE722" "NOT_SCORED" "EXTRA"
textTitle "$ID723" "$TITLE723" "NOT_SCORED" "EXTRA"
exit $EXITCODE
fi
 
### All functions defined above ... run the workflow
 
if [[ $MODE != "csv" ]]; then
prowlerBanner
printColorsCode
fi
getWhoami
genCredReport
saveReport
callCheck
 
textTitle "1" "$TITLE1" "NOT_SCORED" "SUPPORT"
check11
check12
check13
check14
check15
check16
check17
check18
check19
check110
check111
check112
check113
check114
check115
check116
check117
check118
check119
check120
check121
check122
check123
check124
 
textTitle "2" "$TITLE2" "NOT_SCORED" "SUPPORT"
check21
check22
check23
check24
check25
check26
check27
check28
 
textTitle "3" "$TITLE3" "NOT_SCORED" "SUPPORT"
# 3 Monitoring check commands / Mostly covered by SecurityMonkey
check31
check32
check33
check34
check35
check36
check37
check38
check39
check310
check311
check312
check313
check314
check315
 
textTitle "4" "$TITLE4" "NOT_SCORED" "SUPPORT"
check41
check42
check43
check44
check45
 
if [[ ! $EXTRAS ]]; then
textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT"
extra71
extra72
extra73
extra74
extra75
extra76
extra77
extra78
extra79
extra710
extra711
extra712
extra713
extra714
extra715
extra716
extra717
extra718
extra719
extra720
extra721
extra722
extra723
fi
 
cleanTemp
exit $EXITCODE