diff --git a/prowler b/prowler index 5a31ed83..77d03248 100755 --- a/prowler +++ b/prowler @@ -51,7 +51,7 @@ USAGE: -c specify a check number or group from the AWS CIS benchmark (i.e.: check11 for check 1.1, check3 for entire section 3 or level1 for CIS Level 1 Profile Definitions) -f specify an AWS region to run checks against (i.e.: us-west-1) -m specify the maximum number of items to return for long-running requests (default: 100) - -M output mode: text (defalut), mono, csv (separator is \"${SEP}\"; data is on stdout; progress on stderr) + -M output mode: text (default), mono, csv (separator is \"${SEP}\"; data is on stdout; progress on stderr) -k keep the credential report -h this help " @@ -180,7 +180,7 @@ if [[ "$OSTYPE" == "linux-gnu" ]]; then base64 -d } elif [[ "$OSTYPE" == "darwin"* ]]; then - # BSD/OSX coommands compatibility + # BSD/OSX commands compatibility how_older_from_today() { DATE_TO_COMPARE=$1 @@ -255,7 +255,7 @@ textOK(){ else REPREGION=$REGION fi - echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}PASS${SEP}$ITEM_SCORED${SEP}$TITLE_TEXT${SEP}$1" + 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 @@ -268,7 +268,7 @@ textNotice(){ else REPREGION=$REGION fi - echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}INFO${SEP}$ITEM_SCORED${SEP}$TITLE_TEXT${SEP}$1" + 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 @@ -281,7 +281,7 @@ textWarn(){ else REPREGION=$REGION fi - echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}WARNING${SEP}$ITEM_SCORED${SEP}$TITLE_TEXT${SEP}$1" + 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 @@ -291,16 +291,30 @@ textTitle(){ TITLE_ID=$1 TITLE_TEXT=$2 - if [[ $3 ]]; then - ITEM_SCORED=$3 - else - ITEM_SCORED="1" - fi + 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 == "1" ]]; then + if [[ $ITEM_SCORED == "Scored" ]]; then echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT" else echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL" @@ -311,7 +325,7 @@ textTitle(){ 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}TITLE_TEXT${SEP}NOTES" + echo "PROFILE${SEP}ACCOUNT_NUM${SEP}REGION${SEP}TITLE_ID${SEP}RESULT${SEP}SCORED${SEP}LEVEL${SEP}TITLE_TEXT${SEP}NOTES" } prowlerBanner() { @@ -335,7 +349,8 @@ getWhoami(){ exit 2 fi CALLER_ARN=$(echo $CALLER_ARN_RAW | tr -d '"') - textTitle "0.0" "Show report generation info" + printCsvHeader + textTitle "0.0" "Show report generation info" "NOT_SCORED" "SUPPORT" textNotice "ARN: $CALLER_ARN TIMESTAMP: $SCRIPT_START_TIME" else echo "" @@ -378,7 +393,7 @@ printColorsCode(){ # Generate Credential Report genCredReport() { - textTitle "0.1" "Generating AWS IAM Credential Report..." + textTitle "0.1" "Generating AWS IAM Credential Report..." "NOT_SCORED" "SUPPORT" until $( $AWSCLI iam generate-credential-report --output text --query 'State' --profile $PROFILE --region $REGION |grep -q -m 1 "COMPLETE") ; do sleep 1 done @@ -389,7 +404,7 @@ saveReport(){ TEMP_REPORT_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-XXXXX.cred_report ) $AWSCLI iam get-credential-report --query 'Content' --output text --profile $PROFILE --region $REGION | decode_report > $TEMP_REPORT_FILE if [[ $KEEPCREDREPORT -eq 1 ]]; then - textTitle "0.2" "Saving IAM Credential Report ..." + textTitle "0.2" "Saving IAM Credential Report ..." "NOT_SCORED" "SUPPORT" textNotice "IAM Credential Report saved in $TEMP_REPORT_FILE" fi } @@ -429,7 +444,7 @@ 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') - textTitle "$ID11" "$TITLE11" + textTitle "$ID11" "$TITLE11" "SCORED" "LEVEL1" textNotice "Root account last accessed (password key_1 key_2): $COMMAND11" } @@ -442,7 +457,7 @@ 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) - textTitle "$ID12" "$TITLE12" + textTitle "$ID12" "$TITLE12" "SCORED" "LEVEL1" if [[ $COMMAND12 ]]; then for u in $COMMAND12; do textWarn "User $u has Password enabled but MFA disabled" @@ -455,7 +470,7 @@ check12(){ check13(){ ID13="1.3" TITLE13="Ensure credentials unused for 90 days or greater are disabled (Scored)" - textTitle "$ID13" "$TITLE13" + 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=$( @@ -487,7 +502,7 @@ 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 }') - textTitle "$ID14" "$TITLE14" + textTitle "$ID14" "$TITLE14" "SCORED" "LEVEL1" C14_NUM_USERS1=0 C14_NUM_USERS2=0 if [[ $LIST_OF_USERS_WITH_ACCESS_KEY1 ]]; then @@ -532,7 +547,7 @@ 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 --output json --query 'PasswordPolicy.RequireUppercaseCharacters' 2> /dev/null) # must be true - textTitle "$ID15" "$TITLE15" + textTitle "$ID15" "$TITLE15" "SCORED" "LEVEL1" if [[ $COMMAND15 == "true" ]];then textOK "Password Policy requires upper case" else @@ -544,7 +559,7 @@ 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 --output json --query 'PasswordPolicy.RequireLowercaseCharacters' 2> /dev/null) # must be true - textTitle "$ID16" "$TITLE16" + textTitle "$ID16" "$TITLE16" "SCORED" "LEVEL1" if [[ $COMMAND16 == "true" ]];then textOK "Password Policy requires lower case" else @@ -556,7 +571,7 @@ 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 --output json --query 'PasswordPolicy.RequireSymbols' 2> /dev/null) # must be true - textTitle "$ID17" "$TITLE17" + textTitle "$ID17" "$TITLE17" "SCORED" "LEVEL1" if [[ $COMMAND17 == "true" ]];then textOK "Password Policy requires symbol" else @@ -568,7 +583,7 @@ 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 --output json --query 'PasswordPolicy.RequireNumbers' 2> /dev/null) # must be true - textTitle "$ID18" "$TITLE18" + textTitle "$ID18" "$TITLE18" "SCORED" "LEVEL1" if [[ $COMMAND18 == "true" ]];then textOK "Password Policy requires number" else @@ -580,7 +595,7 @@ 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 --output json --query 'PasswordPolicy.MinimumPasswordLength' 2> /dev/null) - textTitle "$ID19" "$TITLE19" + textTitle "$ID19" "$TITLE19" "SCORED" "LEVEL1" if [[ $COMMAND19 -gt "13" ]];then textOK "Password Policy requires more than 13 characters" else @@ -592,12 +607,12 @@ check110(){ ID110="1.10" TITLE110="Ensure IAM password policy prevents password reuse, 24 or greater (Scored)" COMMAND110=$($AWSCLI iam get-account-password-policy --profile $PROFILE --region $REGION --query 'PasswordPolicy.PasswordReusePrevention' --output text 2> /dev/null) - textTitle "$ID110" "$TITLE110" + 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 requirment (lower than 24)" + textWarn "Password Policy has weak reuse requirement (lower than 24)" fi else textWarn "Password Policy missing reuse requirement" @@ -608,13 +623,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 --output json | grep MaxPasswordAge | awk -F: '{ print $2 }'|sed 's/\ //g'|sed 's/,/ /g' 2> /dev/null) - textTitle "$ID111" "$TITLE111" + textTitle "$ID111" "$TITLE111" "SCORED" "LEVEL1" if [[ $COMMAND111 ]];then if [ $COMMAND111 == "90" ];then textOK "Password Policy includes expiration" fi else - textWarn "Passowrd expiration not set or set greater than 90 days " + textWarn "Password expiration not set or set greater than 90 days " fi } @@ -624,7 +639,7 @@ 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 }') - textTitle "$ID112" "$TITLE112" + textTitle "$ID112" "$TITLE112" "SCORED" "LEVEL1" if [ $ROOTKEY1 == "false" ];then textOK "No access key 1 found for root" else @@ -641,7 +656,7 @@ check113(){ ID113="1.13" TITLE113="Ensure MFA is enabled for the root account (Scored)" COMMAND113=$($AWSCLI iam get-account-summary --profile $PROFILE --region $REGION --output json|grep AccountMFAEnabled | awk -F': ' '{ print $2 }'|sed 's/,//') - textTitle "$ID113" "$TITLE113" + textTitle "$ID113" "$TITLE113" "SCORED" "LEVEL1" if [ $COMMAND113 == "1" ]; then textOK "Virtual MFA is enabled for root" else @@ -653,7 +668,7 @@ 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 --output json|grep AccountMFAEnabled | awk -F': ' '{ print $2 }'|sed 's/,//') - textTitle "$ID114" "$TITLE114" + textTitle "$ID114" "$TITLE114" "SCORED" "LEVEL1" 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 @@ -669,8 +684,7 @@ check114(){ check115(){ ID115="1.15" TITLE115="Ensure security questions are registered in the AWS account (Not Scored)" - # No command available - textTitle "$ID115" "$TITLE115" + 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 " @@ -679,7 +693,7 @@ check115(){ check116(){ ID116="1.16" TITLE116="Ensure IAM policies are attached only to groups or roles (Scored)" - textTitle "$ID116" "$TITLE116" + textTitle "$ID116" "$TITLE116" "SCORED" "LEVEL1" 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 @@ -698,7 +712,7 @@ check117(){ ID117="1.17" TITLE117="Enable detailed billing (Scored)" # No command available - textTitle "$ID117" "$TITLE117" + 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 " infoReferenceShort @@ -707,7 +721,7 @@ check117(){ check118(){ ID118="1.18" TITLE118="Ensure IAM Master and IAM Manager roles are active (Scored)" - textTitle "$ID118" "$TITLE118" + textTitle "$ID118" "$TITLE118" "SCORED" "LEVEL1" FINDMASTERANDMANAGER=$($AWSCLI iam list-roles --profile $PROFILE --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: " @@ -717,12 +731,12 @@ check118(){ # 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 - textNotice "$AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION --output json" + textNotice "INLINE: $AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION --output json" done # find attached policies in found roles ATTACHEDPOLICIES=$($AWSCLI iam list-attached-role-policies --role-name $role --profile $PROFILE --region $REGION --query "AttachedPolicies[*]" --output text) for policy in $ATTACHEDPOLICIES;do - textNotice "$AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION --output json" + textNotice "ATTACHED: $AWSCLI iam get-role-policy --role-name $role --policy-name $policy --profile $PROFILE --region $REGION --output json" done done else @@ -734,7 +748,7 @@ check119(){ ID119="1.19" TITLE119="Maintain current contact details (Scored)" # No command available - textTitle "$ID119" "$TITLE119" + 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 " infoReferenceShort @@ -744,7 +758,7 @@ check120(){ ID120="1.20" TITLE120="Ensure security contact information is registered (Scored)" # No command available - textTitle "$ID120" "$TITLE120" + 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 " infoReferenceShort @@ -753,7 +767,7 @@ check120(){ check121(){ ID121="1.21" TITLE121="Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)" - textTitle "$ID121" "$TITLE121" + 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 " infoReferenceShort @@ -762,7 +776,7 @@ check121(){ check122(){ ID122="1.22" TITLE122="Ensure a support role has been created to manage incidents with AWS Support (Scored)" - textTitle "$ID122" "$TITLE122" + textTitle "$ID122" "$TITLE122" "SCORED" "LEVEL1" SUPPORTPOLICYARN=$($AWSCLI iam list-policies --query "Policies[?PolicyName == 'AWSSupportAccess'].Arn" --profile $PROFILE --region $REGION --output text) if [[ $SUPPORTPOLICYARN ]];then for policyarn in $SUPPORTPOLICYARN;do @@ -785,11 +799,11 @@ check122(){ 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)" - textTitle "$ID123" "$TITLE123" + textTitle "$ID123" "$TITLE123" "NOT_SCORED" "LEVEL1" 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) + 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" @@ -812,7 +826,7 @@ check123(){ check124(){ ID124="1.24" TITLE124="Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)" - textTitle "$ID124" "$TITLE124" + textTitle "$ID124" "$TITLE124" "SCORED" "LEVEL1" 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 textNotice "Looking for custom policies: (skipping default policies - it may take few seconds...)" @@ -839,7 +853,7 @@ check124(){ check21(){ ID21="2.1" TITLE21="Ensure CloudTrail is enabled in all regions (Scored)" - textTitle "$ID21" "$TITLE21" + textTitle "$ID21" "$TITLE21" "SCORED" "LEVEL1" 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 @@ -858,7 +872,7 @@ check21(){ check22(){ ID22="2.2" TITLE22="Ensure CloudTrail log file validation is enabled (Scored)" - textTitle "$ID22" "$TITLE22" + textTitle "$ID22" "$TITLE22" "SCORED" "LEVEL2" 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 @@ -877,7 +891,7 @@ check22(){ check23(){ ID23="2.3" TITLE23="Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)" - textTitle "$ID23" "$TITLE23" + textTitle "$ID23" "$TITLE23" "SCORED" "LEVEL1" CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text --profile $PROFILE --region $REGION) if [[ $CLOUDTRAILBUCKET ]];then for bucket in $CLOUDTRAILBUCKET;do @@ -896,7 +910,7 @@ check23(){ check24(){ ID24="2.4" TITLE24="Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)" - textTitle "$ID24" "$TITLE24" + textTitle "$ID24" "$TITLE24" "SCORED" "LEVEL1" 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 @@ -923,7 +937,7 @@ check24(){ check25(){ ID25="2.5" TITLE25="Ensure AWS Config is enabled in all regions (Scored)" - textTitle "$ID25" "$TITLE25" + textTitle "$ID25" "$TITLE25" "SCORED" "LEVEL1" for regx in $REGIONS; do CHECK_AWSCONFIG_STATUS=$($AWSCLI configservice get-status --profile $PROFILE --region $regx --output json| grep "recorder: ON") if [[ $CHECK_AWSCONFIG_STATUS ]];then @@ -937,7 +951,7 @@ check25(){ check26(){ ID26="2.6" TITLE26="Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)" - textTitle "$ID26" "$TITLE26" + textTitle "$ID26" "$TITLE26" "SCORED" "LEVEL1" CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text --profile $PROFILE --region $REGION) if [[ $CLOUDTRAILBUCKET ]];then for bucket in $CLOUDTRAILBUCKET;do @@ -956,7 +970,7 @@ check26(){ check27(){ ID27="2.7" TITLE27="Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)" - textTitle "$ID27" "$TITLE27" + textTitle "$ID27" "$TITLE27" "SCORED" "LEVEL2" CLOUDTRAILNAME=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].Name' --output text --profile $PROFILE --region $REGION) if [[ $CLOUDTRAILNAME ]];then for trail in $CLOUDTRAILNAME;do @@ -975,7 +989,7 @@ check27(){ check28(){ ID28="2.8" TITLE28="Ensure rotation for customer created CMKs is enabled (Scored)" - textTitle "$ID28" "$TITLE28" + textTitle "$ID28" "$TITLE28" "SCORED" "LEVEL2" 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 @@ -1006,7 +1020,7 @@ check28(){ check31(){ ID31="3.1" TITLE31="Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)" - textTitle "$ID31" "$TITLE31" + textTitle "$ID31" "$TITLE31" "SCORED" "LEVEL1" 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) @@ -1023,7 +1037,7 @@ check31(){ check32(){ ID32="3.2" TITLE32="Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)" - textTitle "$ID32" "$TITLE32" + textTitle "$ID32" "$TITLE32" "SCORED" "LEVEL1" 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') @@ -1040,7 +1054,7 @@ check32(){ check33(){ ID33="3.3" TITLE33="Ensure a log metric filter and alarm exist for usage of root account (Scored)" - textTitle "$ID33" "$TITLE33" + textTitle "$ID33" "$TITLE33" "SCORED" "LEVEL1" 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') @@ -1057,7 +1071,7 @@ check33(){ check34(){ ID34="3.4" TITLE34="Ensure a log metric filter and alarm exist for IAM policy changes (Scored)" - textTitle "$ID34" "$TITLE34" + textTitle "$ID34" "$TITLE34" "SCORED" "LEVEL1" 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') @@ -1074,7 +1088,7 @@ check34(){ check35(){ ID35="3.5" TITLE35="Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)" - textTitle "$ID35" "$TITLE35" + textTitle "$ID35" "$TITLE35" "SCORED" "LEVEL1" 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') @@ -1091,7 +1105,7 @@ check35(){ check36(){ ID36="3.6" TITLE36="Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)" - textTitle "$ID36" "$TITLE36" + textTitle "$ID36" "$TITLE36" "SCORED" "LEVEL2" 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') @@ -1108,7 +1122,7 @@ check36(){ check37(){ ID37="3.7" TITLE37="Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)" - textTitle "$ID37" "$TITLE37" + textTitle "$ID37" "$TITLE37" "SCORED" "LEVEL2" 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') @@ -1125,7 +1139,7 @@ check37(){ check38(){ ID38="3.8" TITLE38="Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)" - textTitle "$ID38" "$TITLE38" + textTitle "$ID38" "$TITLE38" "SCORED" "LEVEL1" 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') @@ -1142,7 +1156,7 @@ check38(){ check39(){ ID39="3.9" TITLE39="Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)" - textTitle "$ID39" "$TITLE39" + textTitle "$ID39" "$TITLE39" "SCORED" "LEVEL2" 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') @@ -1159,7 +1173,7 @@ check39(){ check310(){ ID310="3.10" TITLE310="Ensure a log metric filter and alarm exist for security group changes (Scored)" - textTitle "$ID310" "$TITLE310" + textTitle "$ID310" "$TITLE310" "SCORED" "LEVEL2" 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') @@ -1176,7 +1190,7 @@ check310(){ check311(){ ID311="3.11" TITLE311="Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)" - textTitle "$ID311" "$TITLE311" + textTitle "$ID311" "$TITLE311" "SCORED" "LEVEL2" 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') @@ -1193,7 +1207,7 @@ check311(){ check312(){ ID312="3.12" TITLE312="Ensure a log metric filter and alarm exist for changes to network gateways (Scored)" - textTitle "$ID312" "$TITLE312" + textTitle "$ID312" "$TITLE312" "SCORED" "LEVEL1" 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') @@ -1210,7 +1224,7 @@ check312(){ check313(){ ID313="3.13" TITLE313="Ensure a log metric filter and alarm exist for route table changes (Scored)" - textTitle "$ID313" "$TITLE313" + textTitle "$ID313" "$TITLE313" "SCORED" "LEVEL1" 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') @@ -1227,7 +1241,7 @@ check313(){ check314(){ ID314="3.14" TITLE314="Ensure a log metric filter and alarm exist for VPC changes (Scored)" - textTitle "$ID314" "$TITLE314" + textTitle "$ID314" "$TITLE314" "SCORED" "LEVEL1" 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') @@ -1244,7 +1258,7 @@ check314(){ check315(){ ID315="3.15" TITLE315="Ensure appropriate subscribers to each SNS topic (Not Scored)" - textTitle "$ID315" "$TITLE315" + textTitle "$ID315" "$TITLE315" "NOT_SCORED" "LEVEL1" CAN_SNS_LIST_SUBS=1 for regx in $REGIONS; do TOPICS_LIST=$($AWSCLI sns list-topics --profile $PROFILE --region $regx --output text --query 'Topics[*].TopicArn') @@ -1263,10 +1277,10 @@ check315(){ 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 / Suscription $dest" "$regx" + textNotice "Region $regx / Topic $TOPIC_SHORT / Subscription $dest" "$regx" done else - textWarn "Region $regx / Topic $TOPIC_SHORT / Suscription NONE NONE" "$regx" + textWarn "Region $regx / Topic $TOPIC_SHORT / Subscription NONE NONE" "$regx" fi done elif [[ $CAN_SNS_LIST_SUBS -eq 0 ]]; then @@ -1281,7 +1295,7 @@ check315(){ check41(){ ID41="4.1" TITLE41="Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)" - textTitle "$ID41" "$TITLE41" + textTitle "$ID41" "$TITLE41" "SCORED" "LEVEL1" 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`].{GroupId:GroupId}' --profile $PROFILE --region $regx --output text) if [[ $SG_LIST ]];then @@ -1297,7 +1311,7 @@ check41(){ check42(){ ID42="4.2" TITLE42="Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)" - textTitle "$ID42" "$TITLE42" + textTitle "$ID42" "$TITLE42" "SCORED" "LEVEL1" 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 @@ -1313,7 +1327,7 @@ check42(){ check43(){ ID43="4.3" TITLE43="Ensure VPC Flow Logging is Enabled in all VPCs (Scored)" - textTitle "$ID43" "$TITLE43" + textTitle "$ID43" "$TITLE43" "SCORED" "LEVEL2" 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 @@ -1329,7 +1343,7 @@ check43(){ check44(){ ID44="4.4" TITLE44="Ensure the default security group of every VPC restricts all traffic (Scored)" - textTitle "$ID44" "$TITLE44" + textTitle "$ID44" "$TITLE44" "SCORED" "LEVEL2" 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 @@ -1344,7 +1358,7 @@ check45(){ #set -xe ID45="4.5" TITLE45="Ensure routing tables for VPC peering are \"least access\" (Not Scored)" - textTitle "$ID45" "$TITLE45" + 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 $PROFILE --region $regx --query 'VpcPeeringConnections[*].VpcPeeringConnectionId') @@ -1366,7 +1380,7 @@ extra71(){ # set -x ID71="7.1" TITLE71="Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID71" "$TITLE71" "0" + textTitle "$ID71" "$TITLE71" "NOT_SCORED" "EXTRA" ADMIN_GROUPS='' AWS_GROUPS=$($AWSCLI --profile $PROFILE iam list-groups --output text --query 'Groups[].GroupName') @@ -1400,7 +1414,7 @@ extra72(){ #set -x ID72="7.2" TITLE72="Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID72" "$TITLE72" "0" + 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 $PROFILE --region $regx --owner-ids $ACCOUNT_NUM --output text --query 'Snapshots[*].{ID:SnapshotId}') @@ -1420,7 +1434,7 @@ extra73(){ #set -x ID73="7.3" TITLE73="Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID73" "$TITLE73" "0" + 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 @@ -1569,11 +1583,8 @@ if [[ $MODE != "csv" ]]; then prowlerBanner printCurrentDate printColorsCode - getWhoami -else - getWhoami - printCsvHeader fi +getWhoami genCredReport saveReport @@ -1581,7 +1592,7 @@ saveReport callCheck TITLE1="Identity and Access Management ****************************************" -textTitle "1" "$TITLE1" +textTitle "1" "$TITLE1" "NOT_SCORED" "SUPPORT" check11 check12 check13 @@ -1608,7 +1619,7 @@ check123 check124 TITLE2="Logging ***************************************************************" -textTitle "2" "$TITLE2" +textTitle "2" "$TITLE2" "NOT_SCORED" "SUPPORT" check21 check22 check23 @@ -1619,7 +1630,7 @@ check27 check28 TITLE3="Monitoring ************************************************************" -textTitle "3" "$TITLE3" +textTitle "3" "$TITLE3" "NOT_SCORED" "SUPPORT" # 3 Monitoring check commands / Mostly covered by SecurityMonkey check31 check32 @@ -1638,7 +1649,7 @@ check314 check315 TITLE4="Networking ************************************************************" -textTitle "4" "$TITLE4" +textTitle "4" "$TITLE4" "NOT_SCORED" "SUPPORT" check41 check42 check43 @@ -1646,7 +1657,7 @@ check44 check45 TITLE7="Extras ************************************************************" -textTitle "7" "$TITLE7" +textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT" extra71 extra72 extra73