From c65fc3b9894d83243b7cb235eb2ff43db3e87e40 Mon Sep 17 00:00:00 2001 From: Joaquin Rinaudo Date: Thu, 27 Aug 2020 17:08:37 +0200 Subject: [PATCH 1/6] fix(security-hub): unique finding id, if status not changed, comment otherwise resolve older findings --- include/outputs | 7 ++++--- include/securityhub_integration | 35 +++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/include/outputs b/include/outputs index 97f8c29b..c83c2abd 100644 --- a/include/outputs +++ b/include/outputs @@ -256,6 +256,7 @@ generateJsonAsffOutput(){ local severity=$3 jq -M -c \ + --arg UUID $(uuidgen | awk '{print tolower($0)}') \ --arg ACCOUNT_NUM "$ACCOUNT_NUM" \ --arg TITLE_TEXT "$TITLE_TEXT" \ --arg MESSAGE "$(echo -e "${message}" | sed -e 's/^[[:space:]]*//')" \ @@ -269,15 +270,15 @@ generateJsonAsffOutput(){ --arg TIMESTAMP "$(get_iso8601_timestamp)" \ --arg PROWLER_VERSION "$PROWLER_VERSION" \ --arg AWS_PARTITION "$AWS_PARTITION" \ --n '{ + -n '{ "SchemaVersion": "2018-10-08", - "Id": "prowler-\($TITLE_ID)-\($ACCOUNT_NUM)-\($REPREGION)-\($UNIQUE_ID)", + "Id": "arn:\($AWS_PARTITION):securityhub:\($REPREGION):\($ACCOUNT_NUM):product/prowler/\($PROWLER_VERSION)/finding/\($UUID)", "ProductArn": "arn:\($AWS_PARTITION):securityhub:\($REPREGION):\($ACCOUNT_NUM):product/\($ACCOUNT_NUM)/default", "ProductFields": { "ProviderName": "Prowler", "ProviderVersion": $PROWLER_VERSION }, - "GeneratorId": "prowler-\($PROWLER_VERSION)", + "GeneratorId": "prowler-\($TITLE_ID)-\($ACCOUNT_NUM)-\($REPREGION)-\($UNIQUE_ID)", "AwsAccountId": $ACCOUNT_NUM, "Types": [ $TYPE diff --git a/include/securityhub_integration b/include/securityhub_integration index 9c36264b..d78da506 100644 --- a/include/securityhub_integration +++ b/include/securityhub_integration @@ -28,15 +28,42 @@ checkSecurityHubCompatibility(){ exit $EXITCODE fi done + # Get unresolved findings + SECURITY_HUB_PREVIOUS_FINDINGS=$($AWSCLI securityhub get-findings --filters '{"GeneratorId":[{"Value": "prowler-","Comparison":"PREFIX"}],"WorkflowStatus":[{"Value": "RESOLVED","Comparison":"NOT_EQUALS"}]}' | jq -r ".Findings[] | {Id, GeneratorId, Workflow, Compliance}"| jq -s) } sendToSecurityHub(){ local findings="$1" local region="$2" - BATCH_IMPORT_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-import-findings --findings "${findings}") - # A successful CLI response is: {"SuccessCount": 1,"FailedFindings": [],"FailedCount": 0} - # Therefore, check that SuccessCount is indeed 1 - if [[ -z "${BATCH_IMPORT_RESULT}" ]] || ! jq -e '.SuccessCount == 1' <<< "${BATCH_IMPORT_RESULT}" > /dev/null 2>&1; then + local generator_id=$(echo $findings | jq -r ".GeneratorId") + local status=$(echo $findings | jq -r ".Compliance.Status") + local product_arn=$(echo $findings | jq -r ".ProductArn") + PREVIOUS_FINDING=$(echo $SECURITY_HUB_PREVIOUS_FINDINGS | jq --arg finding "$generator_id" '.[] | select((.GeneratorId==$finding))' | jq -cs) + PREVIOUS_FINDING_IDS=$(echo $PREVIOUS_FINDING | jq -c --arg parn "$product_arn" 'map({"Id": .Id, ProductArn: $parn} )'); + if [[ $PREVIOUS_FINDING != "[]" ]]; then + + SAME_STATUS=$(echo $PREVIOUS_FINDING | jq --arg status "$status" '.[] | select(.Compliance.Status!=$status)') + SUPPRESSED=$(echo $PREVIOUS_FINDING | jq '.[] | select(.Workflow.Status=="SUPPRESSED")') + # If are old non-resolved findings with different status, resolve them and import new one + if [[ ! -z $SAME_STATUS && -z $SUPPRESSED ]]; then + BATCH_UPDATE_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-update-findings --finding-identifiers "${PREVIOUS_FINDING_IDS}" --workflow '{"Status": "RESOLVED"}') + BATCH_IMPORT_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-import-findings --findings "${findings}") + else + # Update to avoid being deleted + BATCH_UPDATE_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-update-findings --finding-identifiers "${PREVIOUS_FINDING_IDS}" --note '{"Text": "Finding re-detected by Prowler scan", "UpdatedBy": "prowler"}') + fi + else + #If new (or no unresolved ones) import it + BATCH_IMPORT_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-import-findings --findings "${findings}") + fi + + # Check for success if updated + if [[ ! -z "${BATCH_UPDATE_RESULT}" ]] && ! jq -e '.ProcessedFindings >= 1' <<< "${BATCH_UPDATE_RESULT}" > /dev/null 2>&1; then + echo -e "\n$RED ERROR!$NORMAL Failed to update AWS Security Hub finding\n" + fi + + # Check for success if imported + if [[ ! -z "${BATCH_IMPORT_RESULT}" ]] && ! jq -e '.SuccessCount == 1' <<< "${BATCH_IMPORT_RESULT}" > /dev/null 2>&1; then echo -e "\n$RED ERROR!$NORMAL Failed to send check output to AWS Security Hub\n" fi } From 580523fde43a4af701433cac03d3d44b64001a9a Mon Sep 17 00:00:00 2001 From: Joaquin Rinaudo Date: Tue, 1 Sep 2020 16:17:19 +0200 Subject: [PATCH 2/6] fix(all_checks): also run custom folder --- prowler | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/prowler b/prowler index 3c87ab8d..a1b18452 100755 --- a/prowler +++ b/prowler @@ -411,9 +411,13 @@ execute_all() { GROUP_RUN_BY_DEFAULT[7]='N' fi if [ "${GROUP_RUN_BY_DEFAULT[$i]}" == "Y" ]; then - execute_group $i + execute_group $i fi done + for checks in $(ls $EXTERNAL_CHECKS_PATH/check*); do + CHECK_ID=$(echo $checks | awk -F '_' '{ print $2 }') + execute_check $CHECK_ID + done } # Function to show the titles of either all checks or only those in the specified group @@ -554,6 +558,11 @@ execute_all if [[ "${MODES[@]}" =~ "html" ]]; then addHtmlFooter >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML fi + +if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]]; then + resolveSecurityHubPreviousFails +fi + scoring cleanTemp From 43f3365bb476cb19cd30d9275c8551fe921ba1cf Mon Sep 17 00:00:00 2001 From: Joaquin Rinaudo Date: Tue, 1 Sep 2020 16:22:32 +0200 Subject: [PATCH 3/6] revert: master --- include/outputs | 9 ++++---- include/securityhub_integration | 37 +++++---------------------------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/include/outputs b/include/outputs index 67f69219..f4baf470 100644 --- a/include/outputs +++ b/include/outputs @@ -256,7 +256,6 @@ generateJsonAsffOutput(){ local severity=$3 jq -M -c \ - --arg UUID $(uuidgen | awk '{print tolower($0)}') \ --arg ACCOUNT_NUM "$ACCOUNT_NUM" \ --arg TITLE_TEXT "$TITLE_TEXT" \ --arg MESSAGE "$(echo -e "${message}" | sed -e 's/^[[:space:]]*//')" \ @@ -270,15 +269,15 @@ generateJsonAsffOutput(){ --arg TIMESTAMP "$(get_iso8601_timestamp)" \ --arg PROWLER_VERSION "$PROWLER_VERSION" \ --arg AWS_PARTITION "$AWS_PARTITION" \ - -n '{ +-n '{ "SchemaVersion": "2018-10-08", - "Id": "arn:\($AWS_PARTITION):securityhub:\($REPREGION):\($ACCOUNT_NUM):product/prowler/\($PROWLER_VERSION)/finding/\($UUID)", + "Id": "prowler-\($TITLE_ID)-\($ACCOUNT_NUM)-\($REPREGION)-\($UNIQUE_ID)", "ProductArn": "arn:\($AWS_PARTITION):securityhub:\($REPREGION):\($ACCOUNT_NUM):product/\($ACCOUNT_NUM)/default", "ProductFields": { "ProviderName": "Prowler", "ProviderVersion": $PROWLER_VERSION }, - "GeneratorId": "prowler-\($TITLE_ID)-\($ACCOUNT_NUM)-\($REPREGION)-\($UNIQUE_ID)", + "GeneratorId": "prowler-\($PROWLER_VERSION)", "AwsAccountId": $ACCOUNT_NUM, "Types": [ $TYPE @@ -356,4 +355,4 @@ generateHtmlOutput(){ echo ''$message'' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML echo ''>> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML fi -} +} \ No newline at end of file diff --git a/include/securityhub_integration b/include/securityhub_integration index d78da506..c4f15183 100644 --- a/include/securityhub_integration +++ b/include/securityhub_integration @@ -28,42 +28,15 @@ checkSecurityHubCompatibility(){ exit $EXITCODE fi done - # Get unresolved findings - SECURITY_HUB_PREVIOUS_FINDINGS=$($AWSCLI securityhub get-findings --filters '{"GeneratorId":[{"Value": "prowler-","Comparison":"PREFIX"}],"WorkflowStatus":[{"Value": "RESOLVED","Comparison":"NOT_EQUALS"}]}' | jq -r ".Findings[] | {Id, GeneratorId, Workflow, Compliance}"| jq -s) } sendToSecurityHub(){ local findings="$1" local region="$2" - local generator_id=$(echo $findings | jq -r ".GeneratorId") - local status=$(echo $findings | jq -r ".Compliance.Status") - local product_arn=$(echo $findings | jq -r ".ProductArn") - PREVIOUS_FINDING=$(echo $SECURITY_HUB_PREVIOUS_FINDINGS | jq --arg finding "$generator_id" '.[] | select((.GeneratorId==$finding))' | jq -cs) - PREVIOUS_FINDING_IDS=$(echo $PREVIOUS_FINDING | jq -c --arg parn "$product_arn" 'map({"Id": .Id, ProductArn: $parn} )'); - if [[ $PREVIOUS_FINDING != "[]" ]]; then - - SAME_STATUS=$(echo $PREVIOUS_FINDING | jq --arg status "$status" '.[] | select(.Compliance.Status!=$status)') - SUPPRESSED=$(echo $PREVIOUS_FINDING | jq '.[] | select(.Workflow.Status=="SUPPRESSED")') - # If are old non-resolved findings with different status, resolve them and import new one - if [[ ! -z $SAME_STATUS && -z $SUPPRESSED ]]; then - BATCH_UPDATE_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-update-findings --finding-identifiers "${PREVIOUS_FINDING_IDS}" --workflow '{"Status": "RESOLVED"}') - BATCH_IMPORT_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-import-findings --findings "${findings}") - else - # Update to avoid being deleted - BATCH_UPDATE_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-update-findings --finding-identifiers "${PREVIOUS_FINDING_IDS}" --note '{"Text": "Finding re-detected by Prowler scan", "UpdatedBy": "prowler"}') - fi - else - #If new (or no unresolved ones) import it - BATCH_IMPORT_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-import-findings --findings "${findings}") - fi - - # Check for success if updated - if [[ ! -z "${BATCH_UPDATE_RESULT}" ]] && ! jq -e '.ProcessedFindings >= 1' <<< "${BATCH_UPDATE_RESULT}" > /dev/null 2>&1; then - echo -e "\n$RED ERROR!$NORMAL Failed to update AWS Security Hub finding\n" - fi - - # Check for success if imported - if [[ ! -z "${BATCH_IMPORT_RESULT}" ]] && ! jq -e '.SuccessCount == 1' <<< "${BATCH_IMPORT_RESULT}" > /dev/null 2>&1; then + BATCH_IMPORT_RESULT=$($AWSCLI securityhub --region "$region" $PROFILE_OPT batch-import-findings --findings "${findings}") + # A successful CLI response is: {"SuccessCount": 1,"FailedFindings": [],"FailedCount": 0} + # Therefore, check that SuccessCount is indeed 1 + if [[ -z "${BATCH_IMPORT_RESULT}" ]] || ! jq -e '.SuccessCount == 1' <<< "${BATCH_IMPORT_RESULT}" > /dev/null 2>&1; then echo -e "\n$RED ERROR!$NORMAL Failed to send check output to AWS Security Hub\n" fi -} +} \ No newline at end of file From 9baa6d6ae9286112650335cf1136fa31474511e1 Mon Sep 17 00:00:00 2001 From: Joaquin Rinaudo Date: Tue, 1 Sep 2020 16:26:16 +0200 Subject: [PATCH 4/6] revert: master --- include/outputs | 2 +- include/securityhub_integration | 2 +- prowler | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/include/outputs b/include/outputs index f4baf470..d4ddba7b 100644 --- a/include/outputs +++ b/include/outputs @@ -355,4 +355,4 @@ generateHtmlOutput(){ echo ''$message'' >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML echo ''>> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML fi -} \ No newline at end of file +} diff --git a/include/securityhub_integration b/include/securityhub_integration index c4f15183..9c36264b 100644 --- a/include/securityhub_integration +++ b/include/securityhub_integration @@ -39,4 +39,4 @@ sendToSecurityHub(){ if [[ -z "${BATCH_IMPORT_RESULT}" ]] || ! jq -e '.SuccessCount == 1' <<< "${BATCH_IMPORT_RESULT}" > /dev/null 2>&1; then echo -e "\n$RED ERROR!$NORMAL Failed to send check output to AWS Security Hub\n" fi -} \ No newline at end of file +} diff --git a/prowler b/prowler index a1b18452..ec812142 100755 --- a/prowler +++ b/prowler @@ -559,10 +559,6 @@ if [[ "${MODES[@]}" =~ "html" ]]; then addHtmlFooter >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML fi -if [[ "$SEND_TO_SECURITY_HUB" -eq 1 ]]; then - resolveSecurityHubPreviousFails -fi - scoring cleanTemp From 0eab753620783593829b8a351fe40ec11c048e31 Mon Sep 17 00:00:00 2001 From: Joaquin Rinaudo Date: Tue, 1 Sep 2020 16:34:19 +0200 Subject: [PATCH 5/6] feature: Execute custom checks in execute_all --- prowler | 1 - 1 file changed, 1 deletion(-) diff --git a/prowler b/prowler index 7e8eb3f3..c4e89d6e 100755 --- a/prowler +++ b/prowler @@ -560,7 +560,6 @@ execute_all if [[ "${MODES[@]}" =~ "html" ]]; then addHtmlFooter >> ${OUTPUT_FILE_NAME}.$EXTENSION_HTML fi - scoring cleanTemp From 6c0e1a13e346e838fbb78c9cd5eda9e9824d0aa1 Mon Sep 17 00:00:00 2001 From: Joaquin Rinaudo Date: Tue, 1 Sep 2020 16:36:07 +0200 Subject: [PATCH 6/6] feature: Only when custom checks are set --- prowler | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/prowler b/prowler index c4e89d6e..e4af677a 100755 --- a/prowler +++ b/prowler @@ -416,10 +416,12 @@ execute_all() { execute_group $i fi done - for checks in $(ls $EXTERNAL_CHECKS_PATH/check*); do - CHECK_ID=$(echo $checks | awk -F '_' '{ print $2 }') - execute_check $CHECK_ID - done + if [[ $EXTERNAL_CHECKS_PATH ]]; then + for checks in $(ls $EXTERNAL_CHECKS_PATH/check*); do + CHECK_ID=$(echo $checks | awk -F '_' '{ print $2 }') + execute_check $CHECK_ID + done + fi } # Function to show the titles of either all checks or only those in the specified group