From acb889a2671566a8115f523fae531e7524ef2ffb Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Mon, 26 Jun 2017 15:32:59 -0500 Subject: [PATCH] Initial CSV Support * rename text output functions to follow convention * send appropriate data to stderr for progress monitoring * send results to stdout in pipe-delimited format * skip banner, etc. when running in CSV mode --- prowler | 516 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 279 insertions(+), 237 deletions(-) diff --git a/prowler b/prowler index 616c7ac1..b6b98737 100755 --- a/prowler +++ b/prowler @@ -104,7 +104,7 @@ if [[ $MODE != "mono" && $MODE != "text" && $MODE != "csv" ]]; then exit 1 fi -if [[ $MODE == "mono" ]]; then +if [[ $MODE == "mono" || $MODE == "csv" ]]; then MONOCHROME=1 fi @@ -241,22 +241,56 @@ fi # AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY_ID} TITLE_ID="" +TITLE_TEXT="CALLER ERROR - UNSET TITLE" ## Output formatting functions -text_ok(){ - echo " $TITLE_ID $OK OK! $NORMAL $@" +textOK(){ + if [[ $MODE == "csv" ]]; then + echo "$PROFILE|$REGION|$TITLE_ID|PASS|$TITLE_TEXT|$1" + else + echo " $TITLE_ID $OK OK! $NORMAL $@" + fi } -text_notice(){ - echo " $TITLE_ID $NOTICE INFO! $@ $NORMAL" +textNotice(){ + if [[ $MODE == "csv" ]]; then + echo "$PROFILE|$REGION|$TITLE_ID|INFO|$TITLE_TEXT|$1" + else + echo " $TITLE_ID $NOTICE INFO! $@ $NORMAL" + fi } -text_warn(){ - echo " $TITLE_ID $BAD WARNING! $@ $NORMAL" +textWarn(){ + if [[ $MODE == "csv" ]]; then + echo "$PROFILE|$REGION|$TITLE_ID|WARNING|$TITLE_TEXT|$1" + else + echo " $TITLE_ID $BAD WARNING! $@ $NORMAL" + fi } -text_title(){ +textTitle(){ TITLE_ID=$1 - echo -e "\n$BLUE $TITLE_ID $NORMAL ${@:2}" + TITLE_TEXT=$2 + + if [[ $3 ]]; then + ITEM_SCORED=$3 + else + ITEM_SCORED="1" + fi + + if [[ $MODE == "csv" ]]; then + >&2 echo "$TITLE_ID $TITLE_TEXT" + else + if [[ $ITEM_SCORED == "1" ]]; then + echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT" + else + echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL" + fi + fi +} + +printCsvHeader() { + >&2 echo "Pipe Delimited report on stdout; Diagnostics on stderr." + echo "PROFILE|REGION|TITLE_ID|RESULT|TITLE_TEXT|NOTES" } prowlerBanner() { @@ -270,21 +304,26 @@ prowlerBanner() { # Get whoami in AWS, who is the user running this shell script getWhoami(){ - 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 - $AWSCLI sts get-caller-identity --output json --profile $PROFILE --region $REGION | grep ':' + if [[ $MODE == "csv" ]]; then + CALLER_ARN=$($AWSCLI sts get-caller-identity --output json --profile $PROFILE --region $REGION --query "Arn" | tr -d '"') + echo "$PROFILE|$REGION|0.0|INFO|Report generated by user|$CALLER_ARN" else - echo "Caller Identity:" - $AWSCLI sts get-caller-identity --output table --profile $PROFILE --region $REGION 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 + $AWSCLI sts get-caller-identity --output json --profile $PROFILE --region $REGION | grep ':' + else + echo "Caller Identity:" + $AWSCLI sts get-caller-identity --output table --profile $PROFILE --region $REGION + echo "" + fi fi } printCurrentDate(){ - text_notice "Date: $(date)" + textNotice "Date: $(date)" } printColorsCode(){ @@ -295,10 +334,9 @@ printColorsCode(){ # Generate Credential Report genCredReport() { - echo -en '\nGenerating AWS IAM Credential Report...' - until $AWSCLI iam generate-credential-report --output text --query 'State' --profile $PROFILE --region $REGION |grep -m 1 "COMPLETE"; do + textTitle "0.0" "Generating AWS IAM Credential Report..." + until $( $AWSCLI iam generate-credential-report --output text --query 'State' --profile $PROFILE --region $REGION |grep -q -m 1 "COMPLETE") ; do sleep 1 - echo -n "." done } @@ -322,24 +360,24 @@ REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \ infoReferenceLong(){ # Report review note: - text_notice "For more information:" - text_notice "https://benchmarks.cisecurity.org/tools2/amazon/CIS_Amazon_Web_Services_Foundations_Benchmark_v1.1.0.pdf" - text_notice "For bugs or feedback:" - text_notice "https://github.com/Alfresco/aws-cis-security-benchmark/issues" + textNotice "For more information:" + textNotice "https://benchmarks.cisecurity.org/tools2/amazon/CIS_Amazon_Web_Services_Foundations_Benchmark_v1.1.0.pdf" + textNotice "For bugs or feedback:" + textNotice "https://github.com/Alfresco/aws-cis-security-benchmark/issues" } infoReferenceShort(){ # Report review note: - text_notice "http://bit.ly/2g3PEf7" + textNotice "http://bit.ly/2g3PEf7" } check11(){ ID11="1.1" TITLE11="Avoid the use of the root account (Scored)." COMMAND11=$(cat $TEMP_REPORT_FILE| grep '' | cut -d, -f5,11,16 | sed 's/,/,\ /g') - text_title "$ID11" "$TITLE11" - text_notice "Root account last accessed (password, key_1, key_2): $COMMAND11" + textTitle "$ID11" "$TITLE11" + textNotice "Root account last accessed (password, key_1, key_2): $COMMAND11" } check12(){ @@ -351,20 +389,20 @@ check12(){ for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$8 }' |grep -w $i| grep false | awk '{ print $1 }' done) - text_title "$ID12" "$TITLE12" + textTitle "$ID12" "$TITLE12" if [[ $COMMAND12 ]]; then for u in $COMMAND12; do - text_warn "User $u has Password enabled but MFA disabled" + textWarn "User $u has Password enabled but MFA disabled" done else - text_ok "No users found with Password enabled and MFA disabled" + textOK "No users found with Password enabled and MFA disabled" fi } check13(){ ID13="1.3" TITLE13="Ensure credentials unused for 90 days or greater are disabled (Scored)" - text_title "$ID13" "$TITLE13" + textTitle "$ID13" "$TITLE13" COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }') if [[ $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED ]]; then COMMAND13=$( @@ -379,14 +417,14 @@ check13(){ DATEUSED=$($AWSCLI iam list-users --query "Users[?UserName=='$i'].PasswordLastUsed" --output text --profile $PROFILE --region $REGION | cut -d'T' -f1) HOWOLDER=$(how_older_from_today $DATEUSED) if [ $HOWOLDER -gt "90" ];then - text_warn "User \"$i\" has not logged in during the last 90 days " + textWarn "User \"$i\" has not logged in during the last 90 days " else - text_ok "User \"$i\" found with credentials used in the last 90 days" + textOK "User \"$i\" found with credentials used in the last 90 days" fi done fi else - text_ok "No users found with password enabled" + textOK "No users found with password enabled" fi } @@ -396,45 +434,45 @@ check14(){ TITLE14="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 }') - text_title "$ID14" "$TITLE14" + textTitle "$ID14" "$TITLE14" C14_NUM_USERS1=0 C14_NUM_USERS2=0 # $(expr $C116_NUM_USERS + 1) if [[ $LIST_OF_USERS_WITH_ACCESS_KEY1 ]]; then - # text_warn "Users with access key 1 older than 90 days:" + # 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 $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 - text_warn " $user has not rotated access key1 in over 90 days " + 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 - text_ok "No users with access key 1 older than 90 days." + textOK "No users with access key 1 older than 90 days." fi else - text_ok "No users with access key 1." + textOK "No users with access key 1." fi if [[ $LIST_OF_USERS_WITH_ACCESS_KEY2 ]]; then - # text_warn "Users with access key 2 older than 90 days:" + # 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 $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 - text_warn " $user has not rotated access key2. " + textWarn " $user has not rotated access key2. " C14_NUM_USERS2 = $(expr $C14_NUM_USERS2 + 1) fi done if [[ $C14_NUM_USERS2 -eq 0 ]]; then - text_ok "No users with access key 2 older than 90 days." + textOK "No users with access key 2 older than 90 days." fi else - text_ok "No users with access key 2." + textOK "No users with access key 2." fi } @@ -442,11 +480,11 @@ check15(){ ID15="1.5" TITLE15="Ensure IAM password policy requires at least one uppercase letter (Scored)" COMMAND15=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireUppercaseCharacters') # must be true - text_title "$ID15" "$TITLE15" + textTitle "$ID15" "$TITLE15" if [[ $COMMAND15 == "true" ]];then - text_ok "Password Policy requires upper case" + textOK "Password Policy requires upper case" else - text_warn "Password Policy missing upper-case requirement" + textWarn "Password Policy missing upper-case requirement" fi } @@ -454,11 +492,11 @@ check16(){ ID16="1.6" TITLE16="Ensure IAM password policy require at least one lowercase letter (Scored)" COMMAND16=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireLowercaseCharacters') # must be true - text_title "$ID16" "$TITLE16" + textTitle "$ID16" "$TITLE16" if [[ $COMMAND16 == "true" ]];then - text_ok "Password Policy requires lower case" + textOK "Password Policy requires lower case" else - text_warn "Password Policy missing lower-case requirement" + textWarn "Password Policy missing lower-case requirement" fi } @@ -466,11 +504,11 @@ check17(){ ID17="1.7" TITLE17="Ensure IAM password policy require at least one symbol (Scored)" COMMAND17=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireSymbols') # must be true - text_title "$ID17" "$TITLE17" + textTitle "$ID17" "$TITLE17" if [[ $COMMAND17 == "true" ]];then - text_ok "Password Policy requires symbol" + textOK "Password Policy requires symbol" else - text_warn "Password Policy missing symbol requirement" + textWarn "Password Policy missing symbol requirement" fi } @@ -478,11 +516,11 @@ check18(){ ID18="1.8" TITLE18="Ensure IAM password policy require at least one number (Scored)" COMMAND18=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.RequireNumbers') # must be true - text_title "$ID18" "$TITLE18" + textTitle "$ID18" "$TITLE18" if [[ $COMMAND18 == "true" ]];then - text_ok "Password Policy requires number" + textOK "Password Policy requires number" else - text_warn "Password Policy missing number requirement" + textWarn "Password Policy missing number requirement" fi } @@ -490,11 +528,11 @@ check19(){ ID19="1.9" TITLE19="Ensure IAM password policy requires minimum length of 14 or greater (Scored)" COMMAND19=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.MinimumPasswordLength') - text_title "$ID19" "$TITLE19" + textTitle "$ID19" "$TITLE19" if [[ $COMMAND19 -gt "13" ]];then - text_ok "Password Policy requires more than 13 characters" + textOK "Password Policy requires more than 13 characters" else - text_warn "Password Policy missing or weak length requirement" + textWarn "Password Policy missing or weak length requirement" fi } @@ -502,15 +540,15 @@ check110(){ ID110="1.10" TITLE110="Ensure IAM password policy prevents password reuse (Scored)" COMMAND110=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.PasswordReusePrevention' --output text) - text_title "$ID110" "$TITLE110" + textTitle "$ID110" "$TITLE110" if [[ $COMMAND110 ]];then if [[ $COMMAND110 -gt "23" ]];then - text_ok "Password Policy limits reuse" + textOK "Password Policy limits reuse" else - text_warn "Password Policy has weak reuse requirment (lower than 24)" + textWarn "Password Policy has weak reuse requirment (lower than 24)" fi else - text_warn "Password Policy missing reuse requirement" + textWarn "Password Policy missing reuse requirement" fi } @@ -518,13 +556,13 @@ check111(){ ID111="1.11" TITLE111="Ensure IAM password policy expires passwords within 90 days or less (Scored)" COMMAND111=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION | grep MaxPasswordAge | awk -F: '{ print $2 }'|sed 's/\ //g'|sed 's/,/ /g') - text_title "$ID111" "$TITLE111" + textTitle "$ID111" "$TITLE111" if [[ $COMMAND111 ]];then if [ $COMMAND111 == "90" ];then - text_ok "Password Policy includes expiration" + textOK "Password Policy includes expiration" fi else - text_warn "Passowrd expiration not set or set greater than 90 days " + textWarn "Passowrd expiration not set or set greater than 90 days " fi } @@ -534,16 +572,16 @@ check112(){ # 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 }') - text_title "$ID112" "$TITLE112" + textTitle "$ID112" "$TITLE112" if [ $ROOTKEY1 == "false" ];then - text_ok "No access key 1 found for root" + textOK "No access key 1 found for root" else - text_warn "Found access key 1 for root " + textWarn "Found access key 1 for root " fi if [ $ROOTKEY2 == "false" ];then - text_ok "No access key 2 found for root" + textOK "No access key 2 found for root" else - text_warn "Found access key 2 for root " + textWarn "Found access key 2 for root " fi } @@ -551,11 +589,11 @@ check113(){ ID113="1.13" TITLE113="Ensure MFA is enabled for the root account (Scored)" COMMAND113=$($AWSCLI iam get-account-summary --profile $PROFILE --region $REGION |grep AccountMFAEnabled | awk -F': ' '{ print $2 }'|sed 's/,//') - text_title "$ID113" "$TITLE113" + textTitle "$ID113" "$TITLE113" if [ $COMMAND113 == "1" ]; then - text_ok "Virtual MFA is enabled for root" + textOK "Virtual MFA is enabled for root" else - text_warn "MFA is not ENABLED for root account " + textWarn "MFA is not ENABLED for root account " fi } @@ -563,16 +601,16 @@ check114(){ ID114="1.14" TITLE114="Ensure hardware MFA is enabled for the root account (Scored)" COMMAND113=$($AWSCLI iam get-account-summary --profile $PROFILE --region $REGION |grep AccountMFAEnabled | awk -F': ' '{ print $2 }'|sed 's/,//') - text_title "$ID114" "$TITLE114" + textTitle "$ID114" "$TITLE114" if [ $COMMAND113 == "1" ]; then COMMAND114=$($AWSCLI iam list-virtual-mfa-devices --profile $PROFILE --region $REGION --query 'VirtualMFADevices' --output text|grep :root |wc -l) if [ $COMMAND114 == "1" ]; then - text_ok "Virtual MFA is enabled for root" + textOK "Virtual MFA is enabled for root" else - text_ok "Hardware MFA is enabled for root " + textOK "Hardware MFA is enabled for root " fi else - text_warn "MFA is not ENABLED for root account " + textWarn "MFA is not ENABLED for root account " fi } @@ -580,27 +618,27 @@ check115(){ ID115="1.15" TITLE115="Ensure security questions are registered in the AWS account (Not Scored)" # No command available - text_title "$ID115" "$TITLE115" - text_notice "No command available for check 1.15 " - text_notice "Login to the AWS Console as root, click on the Account " - text_notice "Name -> My Account -> Configure Security Challenge Questions " + textTitle "$ID115" "$TITLE115" "0" + 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(){ ID116="1.16" TITLE116="Ensure IAM policies are attached only to groups or roles (Scored)" - text_title "$ID116" "$TITLE116" + textTitle "$ID116" "$TITLE116" LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text --profile $PROFILE --region $REGION) C116_NUM_USERS=0 for user in $LIST_USERS;do USER_POLICY=$($AWSCLI iam list-attached-user-policies --output text --profile $PROFILE --region $REGION --user-name $user) if [[ $USER_POLICY ]]; then - text_warn "$user has policy directly attached " + textWarn "$user has policy directly attached " C116_NUM_USERS=$(expr $C116_NUM_USERS + 1) fi done if [[ $C116_NUM_USERS -eq 0 ]]; then - text_ok "No policies attached to users." + textOK "No policies attached to users." fi } @@ -608,26 +646,26 @@ check117(){ ID117="1.17" TITLE117="Enable detailed billing (Scored)" # No command available - text_title "$ID117" "$TITLE117" - text_notice "No command available for check 1.17 " - text_notice "See section 1.17 on the CIS Benchmark guide for details " + textTitle "$ID117" "$TITLE117" + textNotice "No command available for check 1.17 " + textNotice "See section 1.17 on the CIS Benchmark guide for details " infoReferenceShort } check118(){ ID118="1.18" TITLE118="Ensure IAM Master and IAM Manager roles are active (Scored)" - text_title "$ID118" "$TITLE118" + textTitle "$ID118" "$TITLE118" FINDMASTERANDMANAGER=$($AWSCLI iam list-roles --profile $PROFILE --region $REGION --query "Roles[*].{RoleName:RoleName}" --output text | grep -E 'Master|Manager'| tr '\n' ' ') if [[ $FINDMASTERANDMANAGER ]];then - text_notice "Found next roles as possible IAM Master and IAM Manager candidates: " - text_notice "$FINDMASTERANDMANAGER " - text_notice "run the commands below to check their policies with section 1.18 in the guide..." + 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 $PROFILE --region $REGION --query "PolicyNames[*]" --output text) for policy in $INLINEPOLICIES;do - text_notice "$AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION" + textNotice "$AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION" done # find attached policies in found roles ATTACHEDPOLICIES=$($AWSCLI iam list-attached-role-policies --role-name $role --profile $PROFILE --region $REGION --query "AttachedPolicies[*]" --output text) @@ -636,7 +674,7 @@ check118(){ done done else - text_warn "IAM Master and IAM Manager roles not found" + textWarn "IAM Master and IAM Manager roles not found" fi } @@ -644,9 +682,9 @@ check119(){ ID119="1.19" TITLE119="Maintain current contact details (Scored)" # No command available - text_title "$ID119" "$TITLE119" - text_notice "No command available for check 1.19 " - text_notice "See section 1.19 on the CIS Benchmark guide for details " + textTitle "$ID119" "$TITLE119" + textNotice "No command available for check 1.19 " + textNotice "See section 1.19 on the CIS Benchmark guide for details " infoReferenceShort } @@ -654,73 +692,73 @@ check120(){ ID120="1.20" TITLE120="Ensure security contact information is registered (Scored)" # No command available - text_title "$ID120" "$TITLE120" - text_notice "No command available for check 1.20 " - text_notice "See section 1.20 on the CIS Benchmark guide for details " + textTitle "$ID120" "$TITLE120" + textNotice "No command available for check 1.20 " + textNotice "See section 1.20 on the CIS Benchmark guide for details " infoReferenceShort } check121(){ ID121="1.21" TITLE121="Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)" - text_title "$ID121" "$TITLE121" - text_notice "No command available for check 1.21 " - text_notice "See section 1.21 on the CIS Benchmark guide for details " + textTitle "$ID121" "$TITLE121" "0" + textNotice "No command available for check 1.21 " + textNotice "See section 1.21 on the CIS Benchmark guide for details " infoReferenceShort } check122(){ ID122="1.22" TITLE122="Ensure a support role has been created to manage incidents with AWS Support (Scored)" - text_title "$ID122" "$TITLE122" + textTitle "$ID122" "$TITLE122" SUPPORTPOLICYARN=$($AWSCLI iam list-policies --query "Policies[?PolicyName == 'AWSSupportAccess'].Arn" --profile $PROFILE --region $REGION --output text) if [[ $SUPPORTPOLICYARN ]];then for policyarn in $SUPPORTPOLICYARN;do POLICYTOSHOW=$($AWSCLI iam list-entities-for-policy --policy-arn $SUPPORTPOLICYARN --profile $PROFILE --region $REGION --output text) if [[ $POLICYTOSHOW ]];then - text_ok "Support Policy attached to $POLICYTOSHOW" - text_notice "Make sure your team can create a Support case with AWS " + textOK "Support Policy attached to $POLICYTOSHOW" + textNotice "Make sure your team can create a Support case with AWS " else - text_warn "Support Policy not applied to any Group, User or Role " + textWarn "Support Policy not applied to any Group, User or Role " fi done else - text_warn "No Support Policy found" + textWarn "No Support Policy found" fi } check123(){ ID123="1.23" TITLE123="Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)" - text_title "$ID123" "$TITLE123" + textTitle "$ID123" "$TITLE123" "0" LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text --profile $PROFILE --region $REGION) # List of USERS with KEY1 last_used_date as N/A LIST_USERS_KEY1_NA=$(for user in $LIST_USERS; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$11 }'|grep N/A |awk '{ print $1 }'; done) LIST_USERS_KEY1_ACTIVE=$(for user in $LIST_USERS_KEY1_NA; do grep $user $TEMP_REPORT_FILE|awk -F, '{ print $1,$9 }'|grep "true$"|awk '{ print $1 }'|sed 's/[:blank:]+/,/g' ; done) if [[ $LIST_USERS_KEY1_ACTIVE ]]; then - # text_notice "List of users with Access Key 1 never used:" - text_notice "Users who never used Access Key 1: $LIST_USERS_KEY1_ACTIVE" + # textNotice "List of users with Access Key 1 never used:" + textNotice "Users who never used Access Key 1: $LIST_USERS_KEY1_ACTIVE" else - text_ok "No users found with Access Key 1 never used" + 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 - # text_notice "List of users with Access Key 2 never used:" - text_notice "Users who never used Access Key 2: $LIST_USERS_KEY2_ACTIVE" + # textNotice "List of users with Access Key 2 never used:" + textNotice "Users who never used Access Key 2: $LIST_USERS_KEY2_ACTIVE" else - text_ok "No users found with Access Key 2 never used" + textOK "No users found with Access Key 2 never used" fi } check124(){ ID124="1.24" TITLE124="Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)" - text_title "$ID124" "$TITLE124" + textTitle "$ID124" "$TITLE124" LIST_CUSTOM_POLICIES=$($AWSCLI iam list-policies --output text --profile $PROFILE --region $REGION|grep 'arn:aws:iam::[0-9]\{12\}:'|awk '{ print $2 }') if [[ $LIST_CUSTOM_POLICIES ]]; then - text_notice "Looking for custom policies: (skipping default policies, it may take few seconds...)" + 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 $PROFILE --region $REGION --query 'Policies[*].[Arn,DefaultVersionId]' --output text|grep -w $policy |awk '{ print $2}') POLICY_WITH_FULL=$($AWSCLI iam get-policy-version --output text --policy-arn $policy --version-id $POLICY_VERSION --query "PolicyVersion.Document.Statement[?Effect == 'Allow' && contains(Resource, '*') && contains (Action, '*')]" --profile $PROFILE --region $REGION) @@ -729,79 +767,79 @@ check124(){ fi done if [[ $POLICIES_ALLOW_LIST ]]; then - text_notice "List of custom policies: " + textNotice "List of custom policies: " for policy in $POLICIES_ALLOW_LIST; do - text_notice "Policy $policy allows \"*:*\"" + textNotice "Policy $policy allows \"*:*\"" done else - text_ok "No custom policy found that allow full \"*:*\" administrative privileges" + textOK "No custom policy found that allow full \"*:*\" administrative privileges" fi else - text_ok "No custom policies found" + textOK "No custom policies found" fi } check21(){ ID21="2.1" TITLE21="Ensure CloudTrail is enabled in all regions (Scored)" - text_title "$ID21" "$TITLE21" + textTitle "$ID21" "$TITLE21" LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].Name' --output text) if [[ $LIST_OF_TRAILS ]];then for trail in $LIST_OF_TRAILS;do MULTIREGION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].IsMultiRegionTrail' --output text --trail-name-list $trail) if [[ $MULTIREGION_TRAIL_STATUS == 'False' ]];then - text_warn "$trail trail in $REGION is not enabled in multi region mode" + textWarn "$trail trail in $REGION is not enabled in multi region mode" else - text_ok "$trail trail in $REGION is enabled for all regions" + textOK "$trail trail in $REGION is enabled for all regions" fi done else - text_warn "No CloudTrail trails found!" + textWarn "No CloudTrail trails found!" fi } check22(){ ID22="2.2" TITLE22="Ensure CloudTrail log file validation is enabled (Scored)" - text_title "$ID22" "$TITLE22" + textTitle "$ID22" "$TITLE22" LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].Name' --output text) if [[ $LIST_OF_TRAILS ]];then for trail in $LIST_OF_TRAILS;do LOGFILEVALIDATION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].LogFileValidationEnabled' --output text --trail-name-list $trail) if [[ $LOGFILEVALIDATION_TRAIL_STATUS == 'False' ]];then - text_warn "$trail trail in $REGION has not log file validation enabled" + textWarn "$trail trail in $REGION has not log file validation enabled" else - text_ok "$trail trail in $REGION has log file validation enabled" + textOK "$trail trail in $REGION has log file validation enabled" fi done else - text_warn "No CloudTrail trails found!" + textWarn "No CloudTrail trails found!" fi } check23(){ ID23="2.3" TITLE23="Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)" - text_title "$ID23" "$TITLE23" + textTitle "$ID23" "$TITLE23" CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text --profile $PROFILE --region $REGION) if [[ $CLOUDTRAILBUCKET ]];then for bucket in $CLOUDTRAILBUCKET;do CLOUDTRAILBUCKET_HASALLPERMISIONS=$($AWSCLI s3api get-bucket-acl --bucket $bucket --query 'Grants[?Grantee.URI==`http://acs.amazonaws.com/groups/global/AllUsers`]' --profile $PROFILE --region $REGION --output text) if [[ $CLOUDTRAILBUCKET_HASALLPERMISIONS ]];then - text_warn "check your $bucket CloudTrail bucket ACL and Policy!" + textWarn "check your $bucket CloudTrail bucket ACL and Policy!" else - text_ok "Bucket $bucket is set correctly" + textOK "Bucket $bucket is set correctly" fi done else - text_warn "No CloudTrail bucket found!" + textWarn "No CloudTrail bucket found!" fi } check24(){ ID24="2.4" TITLE24="Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)" - text_title "$ID24" "$TITLE24" + textTitle "$ID24" "$TITLE24" TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --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 @@ -809,32 +847,32 @@ check24(){ TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1) LATESTDELIVERY_TIMESTAMP=$($AWSCLI cloudtrail get-trail-status --name $trail --profile $PROFILE --region $TRAIL_REGION --query 'LatestCloudWatchLogsDeliveryTime' --output text|grep -v None) if [[ ! $LATESTDELIVERY_TIMESTAMP ]];then - text_warn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)" + 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 - text_warn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)" + textWarn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)" else - text_ok "$trail trail has been logging during the last 24h (it is in $TRAIL_REGION)" + textOK "$trail trail has been logging during the last 24h (it is in $TRAIL_REGION)" fi fi done else - text_warn "No CloudTrail trails found!" + textWarn "No CloudTrail trails found!" fi } check25(){ ID25="2.5" TITLE25="Ensure AWS Config is enabled in all regions (Scored)" - text_title "$ID25" "$TITLE25" + textTitle "$ID25" "$TITLE25" for regx in $REGIONS; do CHECK_AWSCONFIG_STATUS=$($AWSCLI configservice get-status --profile $PROFILE --region $regx | grep "recorder: ON") if [[ $CHECK_AWSCONFIG_STATUS ]];then - text_ok "Region $regx has AWS Config recorder: ON " + textOK "Region $regx has AWS Config recorder: ON " else - text_warn "Region $regx has AWS Config disabled or not configured" + textWarn "Region $regx has AWS Config disabled or not configured" fi done } @@ -842,45 +880,45 @@ check25(){ check26(){ ID26="2.6" TITLE26="Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)" - text_title "$ID26" "$TITLE26" + textTitle "$ID26" "$TITLE26" CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text --profile $PROFILE --region $REGION) if [[ $CLOUDTRAILBUCKET ]];then for bucket in $CLOUDTRAILBUCKET;do CLOUDTRAILBUCKET_LOGENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket --profile $PROFILE --region $REGION --query 'LoggingEnabled.TargetBucket' --output text|grep -v None) if [[ $CLOUDTRAILBUCKET_LOGENABLED ]];then - text_ok "Bucket access logging enabled in $bucket" + textOK "Bucket access logging enabled in $bucket" else - text_warn "access logging is not enabled in $bucket CloudTrail S3 bucket!" + textWarn "access logging is not enabled in $bucket CloudTrail S3 bucket!" fi done else - text_warn "CloudTrail bucket not found!" + textWarn "CloudTrail bucket not found!" fi } check27(){ ID27="2.7" TITLE27="Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)" - text_title "$ID27" "$TITLE27" + textTitle "$ID27" "$TITLE27" CLOUDTRAILNAME=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].Name' --output text --profile $PROFILE --region $REGION) if [[ $CLOUDTRAILNAME ]];then for trail in $CLOUDTRAILNAME;do CLOUDTRAILENC_ENABLED=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --trail $trail --query 'trailList[*].KmsKeyId' --output text) if [[ $CLOUDTRAILENC_ENABLED ]];then - text_ok "KMS key found for $trail" + textOK "KMS key found for $trail" else - text_warn "encryption is not enabled in your CloudTrail trail $trail, KMS key not found!" + textWarn "encryption is not enabled in your CloudTrail trail $trail, KMS key not found!" fi done else - text_warn "CloudTrail bucket doesn't exist!" + textWarn "CloudTrail bucket doesn't exist!" fi } check28(){ ID28="2.8" TITLE28="Ensure rotation for customer created CMKs is enabled (Scored)" - text_title "$ID28" "$TITLE28" + textTitle "$ID28" "$TITLE28" for regx in $REGIONS; do CHECK_KMS_KEYLIST=$($AWSCLI kms list-keys --profile $PROFILE --region $regx --output text --query 'Keys[*].KeyId') if [[ $CHECK_KMS_KEYLIST ]];then @@ -888,22 +926,22 @@ check28(){ for key in $CHECK_KMS_KEYLIST_NO_DEFAULT; do CHECK_KMS_KEY_TYPE=$($AWSCLI kms describe-key --key-id $key --profile $PROFILE --region $regx --query 'KeyMetadata.Origin' | sed 's/["]//g') if [[ $CHECK_KMS_KEY_TYPE == "EXTERNAL" ]];then - text_ok "Key $key in Region $regx Customer Uploaded Key Material." + textOK "Key $key in Region $regx Customer Uploaded Key Material." else CHECK_KMS_KEY_ROTATION=$($AWSCLI kms get-key-rotation-status --key-id $key --profile $PROFILE --region $regx --output text) #CHECK_KMS_DEFAULT_KEY=$($AWSCLI kms describe-key --key-id $key --profile $PROFILE --region $regx --query 'KeyMetadata.Description' | sed -n '/Default master key that protects my ACM private keys when no other key is defined /p'|| echo "False") if [[ $CHECK_KMS_KEY_ROTATION == "True" ]];then - text_ok "Key $key in Region $regx is set correctly" + textOK "Key $key in Region $regx is set correctly" elif [[ $CHECK_KMS_KEY_ROTATION == "False" && $CHECK_KMS_DEFAULT_KEY ]];then - text_notice "Region $regx key $key is an AWS default master key and cannot be deleted nor modified." + textNotice "Region $regx key $key is an AWS default master key and cannot be deleted nor modified." else - text_warn "Key $key in Region $regx is not set to rotate!!!" + textWarn "Key $key in Region $regx is not set to rotate!!!" fi fi done else - text_notice "Region $regx doesn't have encryption keys " + textNotice "Region $regx doesn't have encryption keys " fi done } @@ -911,245 +949,245 @@ check28(){ check31(){ ID31="3.1" TITLE31="Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)" - text_title "$ID31" "$TITLE31" + textTitle "$ID31" "$TITLE31" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep AccessDenied) if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters for Access Denied enabled" + textOK "CloudWatch group found, and metric filters for Access Denied enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check32(){ ID32="3.2" TITLE32="Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)" - text_title "$ID32" "$TITLE32" + textTitle "$ID32" "$TITLE32" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'userIdentity.sessionContext.attributes.mfaAuthenticated.*true') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters for sign-in Console without MFA enabled" + textOK "CloudWatch group found, and metric filters for sign-in Console without MFA enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check33(){ ID33="3.3" TITLE33="Ensure a log metric filter and alarm exist for usage of root account (Scored)" - text_title "$ID33" "$TITLE33" + textTitle "$ID33" "$TITLE33" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION |grep -E 'userIdentity.*Root.*AwsServiceEvent') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters for usage of root account enabled" + textOK "CloudWatch group found, and metric filters for usage of root account enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check34(){ ID34="3.4" TITLE34="Ensure a log metric filter and alarm exist for IAM policy changes (Scored)" - text_title "$ID34" "$TITLE34" + textTitle "$ID34" "$TITLE34" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'DeleteGroupPolicy.*DeleteRolePolicy.*DeleteUserPolicy.*PutGroupPolicy.*PutRolePolicy.*PutUserPolicy.*CreatePolicy.*DeletePolicy.*CreatePolicyVersion.*DeletePolicyVersion.*AttachRolePolicy.*DetachRolePolicy.*AttachUserPolicy.*DetachUserPolicy.*AttachGroupPolicy.*DetachGroupPolicy') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters for IAM policy changes enabled" + textOK "CloudWatch group found, and metric filters for IAM policy changes enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check35(){ ID35="3.5" TITLE35="Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)" - text_title "$ID35" "$TITLE35" + textTitle "$ID35" "$TITLE35" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateTrail.*UpdateTrail.*DeleteTrail.*StartLogging.*StopLogging') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters for CloudTrail configuration changes enabled" + textOK "CloudWatch group found, and metric filters for CloudTrail configuration changes enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check36(){ ID36="3.6" TITLE36="Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)" - text_title "$ID36" "$TITLE36" + textTitle "$ID36" "$TITLE36" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'ConsoleLogin.*Failed') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters for usage of root account enabled" + textOK "CloudWatch group found, and metric filters for usage of root account enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check37(){ ID37="3.7" TITLE37="Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)" - text_title "$ID37" "$TITLE37" + textTitle "$ID37" "$TITLE37" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'kms.amazonaws.com.*DisableKey.*ScheduleKeyDeletion') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters enabled" + textOK "CloudWatch group found, and metric filters enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check38(){ ID38="3.8" TITLE38="Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)" - text_title "$ID38" "$TITLE38" + textTitle "$ID38" "$TITLE38" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 's3.amazonaws.com.*PutBucketAcl.*PutBucketPolicy.*PutBucketCors.*PutBucketLifecycle.*PutBucketReplication.*DeleteBucketPolicy.*DeleteBucketCors.*DeleteBucketLifecycle.*DeleteBucketReplication') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters enabled" + textOK "CloudWatch group found, and metric filters enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check39(){ ID39="3.9" TITLE39="Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)" - text_title "$ID39" "$TITLE39" + textTitle "$ID39" "$TITLE39" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'config.amazonaws.com.*StopConfigurationRecorder.*DeleteDeliveryChannel.*PutDeliveryChannel.*PutConfigurationRecorder') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters enabled" + textOK "CloudWatch group found, and metric filters enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check310(){ ID310="3.10" TITLE310="Ensure a log metric filter and alarm exist for security group changes (Scored)" - text_title "$ID310" "$TITLE310" + textTitle "$ID310" "$TITLE310" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'AuthorizeSecurityGroupIngress.*AuthorizeSecurityGroupEgress.*RevokeSecurityGroupIngress.*RevokeSecurityGroupEgress.*CreateSecurityGroup.*DeleteSecurityGroup') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters enabled" + textOK "CloudWatch group found, and metric filters enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check311(){ ID311="3.11" TITLE311="Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)" - text_title "$ID311" "$TITLE311" + textTitle "$ID311" "$TITLE311" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateNetworkAcl.*CreateNetworkAclEntry.*DeleteNetworkAcl.*DeleteNetworkAclEntry.*ReplaceNetworkAclEntry.*ReplaceNetworkAclAssociation') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters enabled" + textOK "CloudWatch group found, and metric filters enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check312(){ ID312="3.12" TITLE312="Ensure a log metric filter and alarm exist for changes to network gateways (Scored)" - text_title "$ID312" "$TITLE312" + textTitle "$ID312" "$TITLE312" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateCustomerGateway.*DeleteCustomerGateway.*AttachInternetGateway.*CreateInternetGateway.*DeleteInternetGateway.*DetachInternetGateway') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters enabled" + textOK "CloudWatch group found, and metric filters enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check313(){ ID313="3.13" TITLE313="Ensure a log metric filter and alarm exist for route table changes (Scored)" - text_title "$ID313" "$TITLE313" + textTitle "$ID313" "$TITLE313" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateRoute.*CreateRouteTable.*ReplaceRoute.*ReplaceRouteTableAssociation.*DeleteRouteTable.*DeleteRoute.*DisassociateRouteTable') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters enabled" + textOK "CloudWatch group found, and metric filters enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check314(){ ID314="3.14" TITLE314="Ensure a log metric filter and alarm exist for VPC changes (Scored)" - text_title "$ID314" "$TITLE314" + textTitle "$ID314" "$TITLE314" CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails --profile $PROFILE --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $7 }') if [[ $CLOUDWATCH_GROUP ]];then METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $CLOUDWATCH_GROUP --profile $PROFILE --region $REGION --query 'metricFilters' | grep -E 'CreateVpc.*DeleteVpc.*ModifyVpcAttribute.*AcceptVpcPeeringConnection.*CreateVpcPeeringConnection.*DeleteVpcPeeringConnection.*RejectVpcPeeringConnection.*AttachClassicLinkVpc.*DetachClassicLinkVpc.*DisableVpcClassicLink.*EnableVpcClassicLink') if [[ $METRICFILTER_SET ]];then - text_ok "CloudWatch group found, and metric filters enabled" + textOK "CloudWatch group found, and metric filters enabled" else - text_warn "CloudWatch group found, but no metric filters or alarms associated" + textWarn "CloudWatch group found, but no metric filters or alarms associated" fi else - text_warn "No CloudWatch group found, no metric filters or alarms associated" + textWarn "No CloudWatch group found, no metric filters or alarms associated" fi } check315(){ ID315="3.15" TITLE315="Ensure appropriate subscribers to each SNS topic (Not Scored)" - text_title "$ID315" "$TITLE315" + textTitle "$ID315" "$TITLE315" "0" for regx in $REGIONS; do TOPICS_LIST=$($AWSCLI sns list-topics --profile $PROFILE --region $regx --output text --query 'Topics[*].TopicArn') if [[ $TOPICS_LIST ]];then @@ -1157,15 +1195,15 @@ check315(){ CHECK_TOPIC_LIST=$($AWSCLI sns list-subscriptions-by-topic --topic-arn $topic --profile $PROFILE --region $regx --query 'Subscriptions[*].{Endpoint:Endpoint,Protocol:Protocol}' --output text --max-items $MAXITEMS | grep -v "None") if [[ $CHECK_TOPIC_LIST ]]; then TOPIC_SHORT=$(echo $topic | awk -F: '{ print $7 }') - text_notice "Region $regx with Topic $TOPIC_SHORT: " - text_notice "- Suscription: $CHECK_TOPIC_LIST " + textNotice "Region $regx with Topic $TOPIC_SHORT: " + textNotice "- Suscription: $CHECK_TOPIC_LIST " else - text_warn "No suscription found in: Region $regx and Topic $topic " - text_warn " - Region $regx and Topic $topic " + textWarn "No suscription found in: Region $regx and Topic $topic " + textWarn " - Region $regx and Topic $topic " fi done else - text_notice "Region $regx doesn't have topics " + textNotice "Region $regx doesn't have topics " fi done } @@ -1173,15 +1211,15 @@ check315(){ check41(){ ID41="4.1" TITLE41="Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)" - text_title "$ID41" "$TITLE41" + textTitle "$ID41" "$TITLE41" for regx in $REGIONS; do SG_LIST=$($AWSCLI ec2 describe-security-groups --filters "Name=ip-permission.to-port,Values=22" --query 'SecurityGroups[?length(IpPermissions[?ToPort==`22` && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupName: GroupName}' --profile $PROFILE --region $regx --output text) if [[ $SG_LIST ]];then for SG in $SG_LIST;do - text_warn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx " + textWarn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx " done else - text_ok "No Security Groups found in $regx with port 22 TCP open to 0.0.0.0/0 " + textOK "No Security Groups found in $regx with port 22 TCP open to 0.0.0.0/0 " fi done } @@ -1189,15 +1227,15 @@ check41(){ check42(){ ID42="4.2" TITLE42="Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)" - text_title "$ID42" "$TITLE42" + textTitle "$ID42" "$TITLE42" for regx in $REGIONS; do SG_LIST=$($AWSCLI ec2 describe-security-groups --filters "Name=ip-permission.to-port,Values=3389" --query 'SecurityGroups[?length(IpPermissions[?ToPort==`3389` && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupName: GroupName}' --profile $PROFILE --region $regx --output text) if [[ $SG_LIST ]];then for SG in $SG_LIST;do - text_warn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx " + textWarn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx " done else - text_ok "No Security Groups found in $regx with port 3389 TCP open to 0.0.0.0/0 " + textOK "No Security Groups found in $regx with port 3389 TCP open to 0.0.0.0/0 " fi done } @@ -1205,15 +1243,15 @@ check42(){ check43(){ ID43="4.3" TITLE43="Ensure VPC Flow Logging is Enabled in all VPCs (Scored)" - text_title "$ID43" "$TITLE43" + textTitle "$ID43" "$TITLE43" for regx in $REGIONS; do CHECK_FL=$($AWSCLI ec2 describe-flow-logs --profile $PROFILE --region $regx --query 'FlowLogs[?FlowLogStatus==`ACTIVE`].LogGroupName' --output text) if [[ $CHECK_FL ]];then for FL in $CHECK_FL;do - text_ok "VPCFlowLog is enabled for LogGroupName: $FL in Region $regx " + textOK "VPCFlowLog is enabled for LogGroupName: $FL in Region $regx " done else - text_warn "No VPCFlowLog has been found in Region $regx " + textWarn "No VPCFlowLog has been found in Region $regx " fi done } @@ -1221,13 +1259,13 @@ check43(){ check44(){ ID44="4.4" TITLE44="Ensure the default security group of every VPC restricts all traffic (Scored)" - text_title "$ID44" "$TITLE44" + textTitle "$ID44" "$TITLE44" for regx in $REGIONS; do CHECK_SGDEFAULT=$($AWSCLI ec2 describe-security-groups --profile $PROFILE --region $regx --filters Name=group-name,Values='default' --query 'SecurityGroups[*].{IpPermissions:IpPermissions,IpPermissionsEgress:IpPermissionsEgress,GroupId:GroupId}' --output text |grep 0.0.0.0) if [[ $CHECK_SGDEFAULT ]];then - text_warn "Default Security Groups found that allow 0.0.0.0 IN or OUT traffic in Region $regx" + textWarn "Default Security Groups found that allow 0.0.0.0 IN or OUT traffic in Region $regx" else - text_ok "No Default Security Groups open to 0.0.0.0 found in Region $regx " + textOK "No Default Security Groups open to 0.0.0.0 found in Region $regx " fi done } @@ -1236,12 +1274,12 @@ check45(){ #set -xe ID45="4.5" TITLE45="Ensure routing tables for VPC peering are \"least access\" (Not Scored)" - text_title "$ID45" "$TITLE45" - text_notice "Looking for VPC peering in all regions... " + textTitle "$ID45" "$TITLE45" "0" + 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 $PROFILE --region $regx --query 'VpcPeeringConnections[*].VpcPeeringConnectionId') if [[ $LIST_OF_VPCS_PEERING_CONNECTIONS ]];then - text_notice "$regx: $LIST_OF_VPCS_PEERING_CONNECTIONS, review its routing tables " + textNotice "$regx: $LIST_OF_VPCS_PEERING_CONNECTIONS, review its routing tables " #LIST_OF_VPCS=$($AWSCLI ec2 describe-vpcs --profile $PROFILE --region $regx --query 'Vpcs[*].VpcId' --output text) #aws ec2 describe-route-tables --filter "Name=vpc-id,Values=vpc-0213e864" --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" --profile $PROFILE --region $regx # for vpc in $LIST_OF_VPCS; do @@ -1249,7 +1287,7 @@ check45(){ # done #echo $VPCS_WITH_PEERING else - text_ok "$regx: No VPC peering found " + textOK "$regx: No VPC peering found " fi done } @@ -1326,7 +1364,7 @@ callCheck(){ check41;check42;check43;check44;check45 ;; * ) - text_warn "ERROR! Use a valid check name (i.e. check41)\n"; + textWarn "ERROR! Use a valid check name (i.e. check41)\n"; esac cleanTemp exit @@ -1336,10 +1374,14 @@ callCheck(){ ### All functions defined above ... run the workflow -prowlerBanner -printCurrentDate +if [[ $MODE != "csv" ]]; then + prowlerBanner + printCurrentDate + printColorsCode +else + printCsvHeader +fi getWhoami -printColorsCode genCredReport saveReport