From 7b6f168d8ce9b2e24a5eef9a1e4f6e1ba6bd1bbb Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Thu, 6 Jul 2017 14:14:57 -0500 Subject: [PATCH 01/10] update output for check 1.22 to handle multiple users --- prowler | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/prowler b/prowler index d623447b..7fa5f15b 100755 --- a/prowler +++ b/prowler @@ -733,10 +733,13 @@ check122(){ 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 - textOK "Support Policy attached to $POLICYTOSHOW" - textNotice "Make sure your team can create a Support case with AWS " + POLICYUSERS=$($AWSCLI iam list-entities-for-policy --policy-arn $SUPPORTPOLICYARN --profile $PROFILE --region $REGION --output json) + if [[ $POLICYUSERS ]];then + textOK "Support Policy attached to $policyarn" + for user in $(echo "$POLICYUSERS" | grep UserName | cut -d'"' -f4) ; do + textNotice "User $user has support access via $policyarn" + done + # textNotice "Make sure your team can create a Support case with AWS " else textWarn "Support Policy not applied to any Group / User / Role " fi From df47f94cf9acf0269916e49af4cab0d7296667b7 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Fri, 7 Jul 2017 16:33:42 -0500 Subject: [PATCH 02/10] change name of temp file so that it's random Allows multiple instances of prowler to run in parallel (eg. via xargs -P ). Also, add trap handler to remove temp file if interrupted. --- prowler | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/prowler b/prowler index 7fa5f15b..b7edc4cd 100755 --- a/prowler +++ b/prowler @@ -362,7 +362,7 @@ genCredReport() { # Save report to a file, decode it, deletion at finish and after every single check, acb stands for AWS CIS Benchmark saveReport(){ - TEMP_REPORT_FILE=/tmp/.acb + TEMP_REPORT_FILE=$(mktemp -t prowler-XXXXX.cred_report ) $AWSCLI iam get-credential-report --query 'Content' --output text --profile $PROFILE --region $REGION | decode_report > $TEMP_REPORT_FILE } @@ -371,6 +371,9 @@ cleanTemp(){ rm -fr $TEMP_REPORT_FILE } +# Delete the temporary report file if we get interrupted/terminated +trap cleanTemp SIGHUP SIGINT SIGTERM + # Get a list of all available AWS Regions REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \ --output text \ From 5bdd5d8e54d0b1772a22882008b7e057ace5bcc2 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Fri, 7 Jul 2017 16:35:32 -0500 Subject: [PATCH 03/10] add account number to CSV output. --- prowler | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/prowler b/prowler index b7edc4cd..8b48d361 100755 --- a/prowler +++ b/prowler @@ -250,7 +250,7 @@ textOK(){ else REPREGION=$REGION fi - echo "$PROFILE${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}$TITLE_TEXT${SEP}$1" else echo " $OK OK! $NORMAL $1" fi @@ -263,7 +263,7 @@ textNotice(){ else REPREGION=$REGION fi - echo "$PROFILE${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}$TITLE_TEXT${SEP}$1" else echo " $NOTICE INFO! $1 $NORMAL" fi @@ -276,7 +276,7 @@ textWarn(){ else REPREGION=$REGION fi - echo "$PROFILE${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}$TITLE_TEXT${SEP}$1" else echo " $BAD WARNING! $1 $NORMAL" fi @@ -307,9 +307,9 @@ printCsvHeader() { >&2 echo "" >&2 echo "" >&2 echo "Generating \"${SEP}\" delimited report on stdout; Diagnostics on stderr." - >&2 echo " Using Profile $PROFILE" + >&2 echo " Using Profile $PROFILE, Account $ACCOUNT_NUM" >&2 echo "" - echo "PROFILE${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}TITLE_TEXT${SEP}NOTES" } prowlerBanner() { @@ -323,6 +323,7 @@ prowlerBanner() { # Get whoami in AWS, who is the user running this shell script getWhoami(){ + ACCOUNT_NUM=$($AWSCLI sts get-caller-identity --output json --profile $PROFILE --region $REGION --query "Account" | tr -d '"') if [[ $MODE == "csv" ]]; then CALLER_ARN=$($AWSCLI sts get-caller-identity --output json --profile $PROFILE --region $REGION --query "Arn" | tr -d '"') textTitle "0.0" "Show report generation info" From 043fa32aef9db05fc36b0bcfc8c74ad6b32b16f3 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 11 Jul 2017 14:59:20 -0500 Subject: [PATCH 04/10] exit script if there are problems with the credentials. --- prowler | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/prowler b/prowler index 8b48d361..77ea6a94 100755 --- a/prowler +++ b/prowler @@ -325,7 +325,14 @@ prowlerBanner() { getWhoami(){ ACCOUNT_NUM=$($AWSCLI sts get-caller-identity --output json --profile $PROFILE --region $REGION --query "Account" | tr -d '"') if [[ $MODE == "csv" ]]; then - CALLER_ARN=$($AWSCLI sts get-caller-identity --output json --profile $PROFILE --region $REGION --query "Arn" | tr -d '"') + CALLER_ARN_RAW=$($AWSCLI sts get-caller-identity --output json --profile $PROFILE --region $REGION --query "Arn") + if [[ 255 -eq $? ]]; then + # Failed to get own identity ... exit + echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" + >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" + exit 2 + fi + CALLER_ARN=$(echo $CALLER_ARN_RAW | tr -d '"') textTitle "0.0" "Show report generation info" textNotice "ARN: $CALLER_ARN TIMESTAMP: $SCRIPT_START_TIME" else @@ -334,10 +341,24 @@ getWhoami(){ 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 ':' + echo "Caller Identity:" + $AWSCLI sts get-caller-identity --output text --profile $PROFILE --region $REGION --query "Arn" + if [[ 255 -eq $? ]]; then + # Failed to get own identity ... exit + echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" + >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" + exit 2 + fi + echo "" else echo "Caller Identity:" $AWSCLI sts get-caller-identity --output table --profile $PROFILE --region $REGION + if [[ 255 -eq $? ]]; then + # Failed to get own identity ... exit + echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" + >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" + exit 2 + fi echo "" fi fi From 9ef23aecca004ba4ee719974240b9e8990c427b3 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 11 Jul 2017 15:36:35 -0500 Subject: [PATCH 05/10] handle permission failure on list-subscriptions-by-topic gracefully --- prowler | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/prowler b/prowler index 77ea6a94..e629628e 100755 --- a/prowler +++ b/prowler @@ -1237,12 +1237,19 @@ check315(){ ID315="3.15" TITLE315="Ensure appropriate subscribers to each SNS topic (Not Scored)" textTitle "$ID315" "$TITLE315" "0" + 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') - if [[ $TOPICS_LIST ]];then + if [[ $TOPICS_LIST && $CAN_SNS_LIST_SUBS -eq 1 ]];then for topic in $TOPICS_LIST; do - CHECK_TOPIC_LIST=$($AWSCLI sns list-subscriptions-by-topic --topic-arn $topic --profile $PROFILE --region $regx --query 'Subscriptions[*].{Endpoint:Endpoint,Protocol:Protocol}' --output text --max-items $MAXITEMS | grep -v "None") - if [[ $CHECK_TOPIC_LIST ]]; then + 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) + if [[ $? -eq 255 ]]; then + # Permission error + export CAN_SNS_LIST_SUBS=0 + textNotice "No permission to list topics" + break; + fi + if [[ $(grep -v 'None' $CHECK_TOPIC_LIST) ]]; then TOPIC_SHORT=$(echo $topic | awk -F: '{ print $7 }') textNotice "Region $regx with Topic $TOPIC_SHORT:" "$regx" textNotice "- Suscription: $CHECK_TOPIC_LIST" "$regx" @@ -1251,6 +1258,8 @@ check315(){ textWarn " - Region $regx and Topic $topic" "$regx" fi done + elif [[ $CAN_SNS_LIST_SUBS -eq 0 ]]; then + break else textNotice "Region $regx doesn't have topics" "$regx" fi From fe99890683228fbf1e49e24992623d630f64875d Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 11 Jul 2017 15:47:06 -0500 Subject: [PATCH 06/10] swallow error message for list-subscriptions-by-topic --- prowler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler b/prowler index e629628e..6b8b4248 100755 --- a/prowler +++ b/prowler @@ -1242,7 +1242,7 @@ check315(){ TOPICS_LIST=$($AWSCLI sns list-topics --profile $PROFILE --region $regx --output text --query 'Topics[*].TopicArn') if [[ $TOPICS_LIST && $CAN_SNS_LIST_SUBS -eq 1 ]];then for topic in $TOPICS_LIST; do - CHECK_TOPIC_LIST=$($AWSCLI sns list-subscriptions-by-topic --topic-arn $topic --profile $PROFILE --region $regx --query 'Subscriptions[*].{Endpoint:Endpoint,Protocol:Protocol}' --output text --max-items $MAXITEMS) + 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 2> /dev/null) if [[ $? -eq 255 ]]; then # Permission error export CAN_SNS_LIST_SUBS=0 From 93b815ecf28cd750037578bf076fba04c117e940 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 11 Jul 2017 15:52:21 -0500 Subject: [PATCH 07/10] gather count of topics per region, even when unable to list subscribers. --- prowler | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/prowler b/prowler index 6b8b4248..19e6b6a7 100755 --- a/prowler +++ b/prowler @@ -1246,7 +1246,9 @@ check315(){ if [[ $? -eq 255 ]]; then # Permission error export CAN_SNS_LIST_SUBS=0 - textNotice "No permission to list topics" + textNotice "No permission to list subscribers in topics" + ntopics=$(echo $TOPICS_LIST | wc -w ) + textNotice "Region $regx has $ntopics topics" "$regx" break; fi if [[ $(grep -v 'None' $CHECK_TOPIC_LIST) ]]; then @@ -1259,7 +1261,9 @@ check315(){ fi done elif [[ $CAN_SNS_LIST_SUBS -eq 0 ]]; then - break + ntopics=$(echo $TOPICS_LIST | wc -w ) + textNotice "Region $regx has $ntopics topics" "$regx" + # break else textNotice "Region $regx doesn't have topics" "$regx" fi From f787c57636d9eb9704e3c440cbfe166c35b579b5 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 11 Jul 2017 16:04:27 -0500 Subject: [PATCH 08/10] improve trap handling --- prowler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler b/prowler index 19e6b6a7..82e47ac8 100755 --- a/prowler +++ b/prowler @@ -394,7 +394,7 @@ cleanTemp(){ } # Delete the temporary report file if we get interrupted/terminated -trap cleanTemp SIGHUP SIGINT SIGTERM +trap cleanTemp EXIT # Get a list of all available AWS Regions REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \ From 866fe116105b826f1c66287bf0d62d4de3d08505 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 11 Jul 2017 16:23:50 -0500 Subject: [PATCH 09/10] fix typo in function name --- prowler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler b/prowler index 82e47ac8..a2ace106 100755 --- a/prowler +++ b/prowler @@ -714,7 +714,7 @@ check118(){ # 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 - 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 done else From 68f8e08506164c9482ce21427e2f705826a13e65 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 11 Jul 2017 16:24:42 -0500 Subject: [PATCH 10/10] add option (-k) to keep the credential report after execution. --- README.md | 1 + prowler | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0fbda960..0e78d34e 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ USAGE: -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 "|"; data is on stdout; progress on stderr) + -k keep the credential report -h this help ``` diff --git a/prowler b/prowler index a2ace106..28f44e23 100755 --- a/prowler +++ b/prowler @@ -37,6 +37,7 @@ MAXITEMS=100 MONOCHROME=0 MODE="text" SEP=',' +KEEPCREDREPORT=0 # Command usage menu @@ -51,17 +52,21 @@ USAGE: -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) + -k keep the credential report -h this help " exit } -while getopts ":hp:r:c:f:m:M:" OPTION; do +while getopts ":hkp:r:c:f:m:M:" OPTION; do case $OPTION in h ) usage exit 1 ;; + k ) + KEEPCREDREPORT=1 + ;; p ) PROFILE=$OPTARG ;; @@ -384,13 +389,19 @@ genCredReport() { # Save report to a file, decode it, deletion at finish and after every single check, acb stands for AWS CIS Benchmark saveReport(){ - TEMP_REPORT_FILE=$(mktemp -t prowler-XXXXX.cred_report ) + 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 ..." + textNotice "IAM Credential Report saved in $TEMP_REPORT_FILE" + fi } # Delete temporary report file cleanTemp(){ - rm -fr $TEMP_REPORT_FILE + if [[ $KEEPCREDREPORT -ne 1 ]]; then + rm -fr $TEMP_REPORT_FILE + fi } # Delete the temporary report file if we get interrupted/terminated