diff --git a/README.md b/README.md index 4b07afa6..18072315 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX ### 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): +1. If you want to save your report for later analysis thare are different ways, natively (supported text, mono, csv, json, json-asff and junit-xml see note below for more info): ```sh ./prowler -M csv @@ -203,10 +203,10 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX ./prowler | ansi2html -la > report.html ``` - To generate JUnit report files add `-J`. This can be combined with any format. Files are written inside a prowler root directory named `junit-reports`: + To generate JUnit report files, include the junit-xml format. This can be combined with any other format. Files are written inside a prowler root directory named `junit-reports`: ```sh - ./prowler -J + ./prowler -M text,junit-xml ``` >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`. @@ -249,7 +249,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX -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 (default), mono, json, json-asff, csv. They can be used combined comma separated. + -M output mode: text (default), mono, json, json-asff, junit-xml, csv. They can be used combined comma separated. (separator is ","; data is on stdout; progress on stderr). -k keep the credential report -n show check numbers to sort easier @@ -262,7 +262,6 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX -V show version number & exit -s show scoring report -S send check output to AWS Security Hub - only valid when the output mode is json-asff (i.e. "-M json-asff -S") - -J generate JUnit reports, readable by Jenkins or other CI tools. Files are written to ./junit-reports -x specify external directory with custom checks (i.e. /my/own/checks, files must start by check) -q suppress info messages and passing test output -A account id for the account where to assume a role, requires -R and -T diff --git a/include/colors b/include/colors index 2ae6f77f..7bb9f84e 100644 --- a/include/colors +++ b/include/colors @@ -14,16 +14,15 @@ IFS=',' read -ra MODES <<< "${MODE}" for MODE in "${MODES[@]}"; do - if [[ "$MODE" != "mono" && "$MODE" != "text" && "$MODE" != "csv" && "$MODE" != "json" && "$MODE" != "json-asff" ]]; then - echo -e "${OPTRED}ERROR!$OPTNORMAL Invalid output mode. Choose text, mono, csv, json or json-asff. ./prowler -h for help" + if [[ "$MODE" != "mono" && "$MODE" != "text" && "$MODE" != "csv" && "$MODE" != "json" && "$MODE" != "json-asff" && "$MODE" != "junit-xml" ]]; then + echo -e "${OPTRED}ERROR!$OPTNORMAL Invalid output mode. Choose text, mono, csv, json, json-asff or junit-xml. ./prowler -h for help" EXITCODE=1 exit $EXITCODE fi -done - -if [[ "$MODE" == "mono" || "$MODE" == "csv" || "$MODE" == "json" || "$MODE" == "json-asff" ]]; then - MONOCHROME=1 -fi + if [[ "$MODE" == "mono" || "$MODE" == "csv" || "$MODE" == "json" || "$MODE" == "json-asff" ]]; then + MONOCHROME=1 + fi +done if [[ $MONOCHROME -eq 1 ]]; then # Colors diff --git a/include/junit_integration b/include/junit_integration index 479a7118..54bcd892 100644 --- a/include/junit_integration +++ b/include/junit_integration @@ -15,6 +15,14 @@ JUNIT_OUTPUT_DIRECTORY="junit-reports" +is_junit_output_enabled() { + if [[ ${MODES[@]} =~ "junit-xml" ]]; then + true + else + false + fi +} + xml_escape() { sed 's/&/\&/g; s//\>/g; s/\"/\"/g; s/'"'"'/\'/g' <<< "$1" } diff --git a/include/outputs b/include/outputs index a87f8342..0b56ef88 100644 --- a/include/outputs +++ b/include/outputs @@ -27,29 +27,28 @@ textPass(){ fi PASS_COUNTER=$((PASS_COUNTER+1)) - if [[ "${GENERATE_JUNIT}" -eq 1 ]]; then + if [[ $2 ]]; then + REPREGION=$2 + else + 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 + if [[ "${MODES[@]}" =~ "json" ]]; then + 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 + if [[ "${SEND_TO_SECURITY_HUB}" -eq 1 ]]; then + sendToSecurityHub "${JSON_ASFF_OUTPUT}" + fi + fi + if is_junit_output_enabled; then output_junit_success "$1" fi - if [[ "${MODES[@]}" =~ "csv" || "${MODES[@]}" =~ "json" || "${MODES[@]}" =~ "json-asff" ]]; then - if [[ $2 ]]; then - REPREGION=$2 - else - 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 - if [[ "${MODES[@]}" =~ "json" ]]; then - 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 - if [[ "${SEND_TO_SECURITY_HUB}" -eq 1 ]]; then - sendToSecurityHub "${JSON_ASFF_OUTPUT}" - fi - fi - else + if [[ "${MODES[@]}" =~ "text" ]]; then echo " $OK PASS!$NORMAL $1" fi } @@ -59,22 +58,21 @@ textInfo(){ return fi - if [[ "${GENERATE_JUNIT}" -eq 1 ]]; then + if [[ $2 ]]; then + REPREGION=$2 + else + REPREGION=$REGION + fi + if [[ "${MODES[@]}" =~ "csv" ]]; then + echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}INFO${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" "Info" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_JSON + fi + if is_junit_output_enabled; then output_junit_info "$1" fi - if [[ "${MODES[@]}" =~ "csv" || "${MODES[@]}" =~ "json" || "${MODES[@]}" =~ "json-asff" ]]; then - if [[ $2 ]]; then - REPREGION=$2 - else - REPREGION=$REGION - fi - if [[ "${MODES[@]}" =~ "csv" ]]; then - echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}INFO${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" "Info" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_JSON - fi - else + if [[ "${MODES[@]}" =~ "text" ]]; then echo " $NOTICE INFO! $1 $NORMAL" fi } @@ -82,29 +80,28 @@ textInfo(){ textFail(){ FAIL_COUNTER=$((FAIL_COUNTER+1)) EXITCODE=3 - if [[ "${GENERATE_JUNIT}" -eq 1 ]]; then + if [[ $2 ]]; then + REPREGION=$2 + else + REPREGION=$REGION + fi + if [[ "${MODES[@]}" =~ "csv" ]]; then + 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 + fi + if [[ "${MODES[@]}" =~ "json-asff" ]]; then + JSON_ASFF_OUTPUT=$(generateJsonAsffOutput "$1" "FAILED" "HIGH") + echo "${JSON_ASFF_OUTPUT}" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_ASFF + if [[ "${SEND_TO_SECURITY_HUB}" -eq 1 ]]; then + sendToSecurityHub "${JSON_ASFF_OUTPUT}" + fi + fi + if is_junit_output_enabled; then output_junit_failure "$1" fi - if [[ "${MODES[@]}" =~ "csv" || "${MODES[@]}" =~ "json" || "${MODES[@]}" =~ "json-asff" ]]; then - if [[ $2 ]]; then - REPREGION=$2 - else - REPREGION=$REGION - fi - if [[ "${MODES[@]}" =~ "csv" ]]; then - 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 - fi - if [[ "${MODES[@]}" =~ "json-asff" ]]; then - JSON_ASFF_OUTPUT=$(generateJsonAsffOutput "$1" "FAILED" "HIGH") - echo "${JSON_ASFF_OUTPUT}" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_ASFF - if [[ "${SEND_TO_SECURITY_HUB}" -eq 1 ]]; then - sendToSecurityHub "${JSON_ASFF_OUTPUT}" - fi - fi - else + if [[ "${MODES[@]}" =~ "text" ]]; then echo " $BAD FAIL! $1 $NORMAL" fi } diff --git a/prowler b/prowler index bca29ef0..f5e097f6 100755 --- a/prowler +++ b/prowler @@ -45,7 +45,6 @@ SEP=',' KEEPCREDREPORT=0 EXITCODE=0 SEND_TO_SECURITY_HUB=0 -GENERATE_JUNIT=0 SCRIPT_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" ) TITLE_ID="" TITLE_TEXT="CALLER ERROR - UNSET TITLE" @@ -66,7 +65,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 (default), mono, json, json-asff, csv. They can be used combined comma separated. + -M output mode: text (default), mono, json, json-asff, junit-xml, csv. They can be used combined comma separated. (separator is ","; data is on stdout; progress on stderr). -k keep the credential report -n show check numbers to sort easier @@ -79,7 +78,6 @@ USAGE: -V show version number & exit -s show scoring report -S send check output to AWS Security Hub - only valid when the output mode is json-asff (i.e. "-M json-asff -S") - -J generate JUnit reports, readable by Jenkins or other CI tools. Files are written to ./junit-reports -x specify external directory with custom checks (i.e. /my/own/checks, files must start by "check") -q suppress info messages and passing test output -A account id for the account where to assume a role, requires -R and -T @@ -94,7 +92,7 @@ USAGE: exit } -while getopts ":hlLkqp:r:c:g:f:m:M:E:enbVsSJxI:A:R:T:" OPTION; do +while getopts ":hlLkqp:r:c:g:f:m:M:E:enbVsSxI:A:R:T:" OPTION; do case $OPTION in h ) usage @@ -154,9 +152,6 @@ while getopts ":hlLkqp:r:c:g:f:m:M:E:enbVsSJxI:A:R:T:" OPTION; do S ) SEND_TO_SECURITY_HUB=1 ;; - J ) - GENERATE_JUNIT=1 - ;; x ) EXTERNAL_CHECKS_PATH=$OPTARG ;; @@ -280,12 +275,12 @@ execute_check() { fi fi show_check_title ${alternate_name} - if [[ "${GENERATE_JUNIT}" -eq 1 ]]; then + if is_junit_output_enabled; then prepare_junit_check_output "$1" fi # Execute the check ${alternate_name} - if [[ "${GENERATE_JUNIT}" -eq 1 ]]; then + if is_junit_output_enabled; then finalise_junit_check_output "$1" fi else @@ -300,12 +295,12 @@ execute_check() { fi fi show_check_title $1 - if [[ "${GENERATE_JUNIT}" -eq 1 ]]; then + if is_junit_output_enabled; then prepare_junit_check_output "$1" fi # Execute the check $1 - if [[ "${GENERATE_JUNIT}" -eq 1 ]]; then + if is_junit_output_enabled; then finalise_junit_check_output "$1" fi else @@ -443,7 +438,7 @@ if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]]; then checkSecurityHubCompatibility fi -if [[ "${GENERATE_JUNIT}" -eq 1 ]]; then +if is_junit_output_enabled; then prepare_junit_output fi