From 47a05c203ae54b3929f83e46cbddd31b576a2ab2 Mon Sep 17 00:00:00 2001 From: Marc Jay Date: Mon, 20 Apr 2020 01:07:01 +0100 Subject: [PATCH] Improve listing of Checks and Groups Change `-l` flag to print a unique list of every single check (assuming none are orphaned outside of all groups) Allow `-g ` to be specified in combination with `-l`, to only print checks that are referenced by the specified group When listing all checks with `-l` only, print out all groups that reference each check Fixes: #545 --- README.md | 14 ++++---- include/outputs | 23 ++++++++----- prowler | 87 +++++++++++++++++++++++++++++++++---------------- 3 files changed, 80 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 8d71e75a..c2f498ca 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,9 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX ./prowler ``` - Use `-l` to list all available checks and group of checks (sections) + Use `-l` to list all available checks and the groups (sections) that reference them - If you want to avoid installing dependences run it using Docker: + If you want to avoid installing dependencies run it using Docker: ```sh docker run -ti --rm --name prowler --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --env AWS_SESSION_TOKEN toniblyx/prowler:latest @@ -159,7 +159,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX Valid check numbers are based on the AWS CIS Benchmark guide, so 1.1 is check11 and 3.10 is check310 -### Save your reports +### Save your reports 1. If you want to save your report for later analysis thare are different ways, natively (supported text, mono, csv, json and json-asff see note below for more info): @@ -190,7 +190,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX ./prowler | ansi2html -la > report.html ``` - >Note about output formats to use with `-M`: "text" is the default one with colors, "mono" is like default one but monochrome, "csv" is comma separated values, "json" plain basic json (without comma between lines) and "json-asff" is also json with Amazon Security Finding Format that you can ship to Security Hub using `-S`. + >Note about output formats to use with `-M`: "text" is the default one with colors, "mono" is like default one but monochrome, "csv" is comma separated values, "json" plain basic json (without comma between lines) and "json-asff" is also json with Amazon Security Finding Format that you can ship to Security Hub using `-S`. or save your report in a S3 bucket (this only works for text or mono, for csv, json or json-asff it has to be copied afterwards): @@ -235,7 +235,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX -k keep the credential report -n show check numbers to sort easier (i.e.: 1.01 instead of 1.1) - -l list all available checks only (does not perform any check) + -l list all available checks only (does not perform any check). Add -g to only list checks within the specified group -L list all groups (does not perform any check) -e exclude group extras -E execute all tests except a list of specified checks separated by comma (i.e. check21,check31) @@ -321,7 +321,7 @@ There are two requirements: 2. As mentioned in section "Custom IAM Policy", to allow Prowler to import its findings to AWS Security Hub you need to add the policy below to the role or user running Prowler: - [iam/prowler-security-hub.json](iam/prowler-security-hub.json) ->Note: to have updated findings in Security Hub you have to run Prowler periodically. Once a day or every certain amount of hours. +>Note: to have updated findings in Security Hub you have to run Prowler periodically. Once a day or every certain amount of hours. ## How to fix every FAIL @@ -498,7 +498,7 @@ AWS is made to be flexible for service links within and between different AWS ac This group of checks helps to analyse a particular AWS account (subject) on existing links to other AWS accounts across various AWS services, in order to identify untrusted links. -### Run +### Run To give it a quick shot just call: ```sh ./prowler -g trustboundaries diff --git a/include/outputs b/include/outputs index b1e23265..3bf75247 100644 --- a/include/outputs +++ b/include/outputs @@ -16,7 +16,7 @@ EXTENSION_CSV="csv" EXTENSION_JSON="json" EXTENSION_ASFF="asff-json" -EXTENSION_HTML="html" # not implemented yet, use ansi2html as in documentation +EXTENSION_HTML="html" # not implemented yet, use ansi2html as in documentation OUTPUT_DATE=$(date -u +"%Y%m%d%H%M%S") OUTPUT_FILE_NAME=prowler-output-$OUTPUT_DATE @@ -34,14 +34,14 @@ textPass(){ REPREGION=$REGION fi if [[ "${MODES[@]}" =~ "csv" ]]; then - echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}PASS${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_CSV - fi + echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}PASS${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_CSV + fi if [[ "${MODES[@]}" =~ "json" ]]; then - generateJsonOutput "$1" "Pass" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_JSON + generateJsonOutput "$1" "Pass" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_JSON fi if [[ "${MODES[@]}" =~ "json-asff" ]]; then JSON_ASFF_OUTPUT=$(generateJsonAsffOutput "$1" "PASSED" "INFORMATIONAL") - echo "${JSON_ASFF_OUTPUT}" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_ASFF + echo "${JSON_ASFF_OUTPUT}" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_ASFF if [[ "${SEND_TO_SECURITY_HUB}" -eq 1 ]]; then sendToSecurityHub "${JSON_ASFF_OUTPUT}" fi @@ -86,7 +86,7 @@ textFail(){ echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}FAIL${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_CSV fi if [[ "${MODES[@]}" =~ "json" ]]; then - generateJsonOutput "$1" "Fail" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_JSON + generateJsonOutput "$1" "Fail" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_JSON fi if [[ "${MODES[@]}" =~ "json-asff" ]]; then JSON_ASFF_OUTPUT=$(generateJsonAsffOutput "$1" "FAILED" "HIGH") @@ -130,15 +130,20 @@ textTitle(){ *) ITEM_LEVEL="Unspecified or Invalid";; esac + local group_ids + if [[ -n "$5" ]]; then + group_ids="$CYAN [$5] $NORMAL" + fi + if [[ "${MODES[@]}" =~ "csv" ]]; then - >&2 echo "$TITLE_ID $TITLE_TEXT" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_CSV + >&2 echo "$TITLE_ID $TITLE_TEXT" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_CSV elif [[ "${MODES[@]}" =~ "json" || "${MODES[@]}" =~ "json-asff" ]]; then : else if [[ "$ITEM_SCORED" == "Scored" ]]; then - echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT" + echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT $group_ids" else - echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL" + echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL $group_ids" fi fi } diff --git a/prowler b/prowler index 32b1dbb3..9347ae81 100755 --- a/prowler +++ b/prowler @@ -48,6 +48,7 @@ SEND_TO_SECURITY_HUB=0 SCRIPT_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" ) TITLE_ID="" TITLE_TEXT="CALLER ERROR - UNSET TITLE" +TOTAL_CHECKS=() # Command usage menu usage(){ @@ -70,7 +71,7 @@ USAGE: -k keep the credential report -n show check numbers to sort easier (i.e.: 1.01 instead of 1.1) - -l list all available checks only (does not perform any check) + -l list all available checks only (does not perform any check). Add -g to only list checks within the specified group -L list all groups (does not perform any check) -e exclude group extras -E execute all tests except a list of specified checks separated by comma (i.e. check21,check31) @@ -85,7 +86,7 @@ USAGE: -R role name to assume in the account, requires -A and -T (i.e.: ProwlerRole) -T session durantion given to that role credentials in seconds, default 1h (3600) recommended 12h, requires -R and -T - (i.e.: 43200) + (i.e.: 43200) -I External ID to be used when assuming roles (no mandatory), requires -A and -R. -h this help " @@ -232,14 +233,37 @@ if [[ $EXTERNAL_CHECKS_PATH ]]; then done fi -# Function to show the title of the check +# Get a list of total checks available by ID +for i in "${!GROUP_TITLE[@]}"; do + IFS=',' read -ra CHECKS <<< "${GROUP_CHECKS[$i]}" + for j in "${CHECKS[@]}"; do + TOTAL_CHECKS+=("$CHECK_ID_$j") + done +done +# Remove duplicates whilst preserving the order of checks, and store the result as an array +TOTAL_CHECKS=($(echo "${TOTAL_CHECKS[*]}" | tr ' ' '\n' | awk '!seen[$0]++')) + +# Function to show the title of the check, and optionally which group(s) it belongs to # using this way instead of arrays to keep bash3 (osx) and bash4(linux) compatibility show_check_title() { local check_id=CHECK_ID_$1 local check_title=CHECK_TITLE_$1 local check_scored=CHECK_SCORED_$1 local check_type=CHECK_TYPE_$1 - textTitle "${!check_id}" "${!check_title}" "${!check_scored}" "${!check_type}" + local group_ids + # If requested ($2 is any non-null value) iterate all GROUP_CHECKS and produce a comma-separated list of all + # the GROUP_IDs that include this particular check + if [[ -n "$2" ]]; then + for i in "${!GROUP_ID[@]}"; do + if [[ "${GROUP_CHECKS[$i]}" =~ "$1" ]]; then + if [[ -n "$group_ids" ]]; then + group_ids+=", " + fi + group_ids+="${GROUP_ID[$i]}" + fi + done + fi + textTitle "${!check_id}" "${!check_title}" "${!check_scored}" "${!check_type}" "$group_ids" } # Function to show the title of a group, by numeric id @@ -343,24 +367,41 @@ execute_all() { done } -# Function to show the titles of everything +# Function to show the titles of either all checks or only those in the specified group show_all_titles() { - MAIN_GROUPS=(1 2 3 4 7) - for i in "${MAIN_GROUPS[@]}"; do - show_group_title $i - # Display the title of the checks in groups 1,2,3,4 and 7 - # Any other group has checks in these groups - IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$i]} - for j in ${CHECKS[@]}; do - show_check_title $j - done - done + local checks + local check_id + local group_index + # If '-g ' has been specified, only show the titles of checks within the specified group + if [[ $GROUP_ID_READ ]];then + if [[ " ${GROUP_ID[@]} " =~ " ${GROUP_ID_READ} " ]]; then + for group_index in "${!GROUP_ID[@]}"; do + if [ "${GROUP_ID[$group_index]}" == "${GROUP_ID_READ}" ]; then + show_group_title "$group_index" + IFS=',' read -ra checks <<< "${GROUP_CHECKS[$i]}" + for check_id in ${checks[@]}; do + show_check_title "$check_id" + done + fi + done + else + textFail "Use a valid check group ID i.e.: group1, extras, forensics-ready, etc." + show_all_group_titles + exit $EXITCODE + fi + else + for check_id in "${TOTAL_CHECKS[@]}"; do + # Pass 1 so that the group IDs that this check belongs to are printed + show_check_title "$check_id" 1 + done + fi } show_all_group_titles() { - for i in "${!GROUP_TITLE[@]}"; do - show_group_title $i - done + local group_index + for group_index in "${!GROUP_TITLE[@]}"; do + show_group_title "$group_index" + done } # Function to execute all checks but exclude some of them @@ -373,16 +414,6 @@ get_all_checks_without_exclusion() { for E_CHECK in "${E_CHECKS[@]}"; do CHECKS_TO_EXCLUDE+=($E_CHECK) done - # Get a list of total checks available by ID - for i in "${!GROUP_TITLE[@]}"; do - # show_group_title $i - IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$i]} - for j in ${CHECKS[@]}; do - TOTAL_CHECKS+=($CHECK_ID_$j) - done - done - # Remove duplicates whilst preserving the order of checks, and store the result as an array - TOTAL_CHECKS=($(echo "${TOTAL_CHECKS[*]}" | tr ' ' '\n' | awk '!seen[$0]++')) # Create a list that contains all checks but excluded ones for i in "${TOTAL_CHECKS[@]}"; do local COINCIDENCE=false