mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 14:55:00 +00:00
json-asff mode outputs JSON, similar to the standard 'json' mode with one check per line, but in AWS Security Finding Format - used by AWS Security Hub Currently uses a generic Type, Resources and ProductArn value, but sets the Id to a unique value that includes the details of the message, in order to separate out checks that run against multiple resources and output one result per resource per check. This ensures that findings can be updated, should the resource move in or out of compliance securityhub mode generates the ASFF JSON and then passes it to an 'aws securityhub batch-import-findings' call, once per resource per check. Output to the screen is similar to the standard mode, but prints whether or not the finding was submitted successfully Fixes #524
218 lines
7.0 KiB
Bash
218 lines
7.0 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
# use this file except in compliance with the License. You may obtain a copy
|
|
# of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software distributed
|
|
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations under the License.
|
|
|
|
# Output formatting functions
|
|
textPass(){
|
|
if [[ "$QUIET" == 1 ]]; then
|
|
return
|
|
fi
|
|
|
|
PASS_COUNTER=$((PASS_COUNTER+1))
|
|
if [[ "$MODE" == "csv" || "$MODE" == "json" || "$MODE" == "json-asff" || "$MODE" == "securityhub" ]]; then
|
|
if [[ $2 ]]; then
|
|
REPREGION=$2
|
|
else
|
|
REPREGION=$REGION
|
|
fi
|
|
if [[ "$MODE" == "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"
|
|
elif [[ "$MODE" == "json" ]]; then
|
|
generateJsonOutput "$1" "Pass"
|
|
elif [[ "$MODE" == "json-asff" ]]; then
|
|
generateJsonAsffOutput "$1" "PASSED" "INFORMATIONAL"
|
|
elif [[ "$MODE" == "securityhub" ]]; then
|
|
printf " $OK PASS!$NORMAL %s... " "$1"
|
|
aws securityhub batch-import-findings --findings "$(generateJsonAsffOutput "$1" "PASSED" "INFORMATIONAL")" | jq -M -r 'if .SuccessCount == 1 then "Successfully submitted finding" else "Failed to upload finding" end'
|
|
fi
|
|
else
|
|
echo " $OK PASS!$NORMAL $1"
|
|
fi
|
|
}
|
|
|
|
textInfo(){
|
|
if [[ "$QUIET" == 1 ]]; then
|
|
return
|
|
fi
|
|
|
|
if [[ "$MODE" == "csv" || "$MODE" == "json" || "$MODE" == "json-asff" ]]; then
|
|
if [[ $2 ]]; then
|
|
REPREGION=$2
|
|
else
|
|
REPREGION=$REGION
|
|
fi
|
|
if [[ "$MODE" == "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"
|
|
elif [[ "$MODE" == "json" ]]; then
|
|
generateJsonOutput "$1" "Info"
|
|
elif [[ "$MODE" == "json-asff" ]]; then
|
|
generateJsonAsffOutput "$1" "NOT_AVAILABLE" "LOW"
|
|
elif [[ "$MODE" == "securityhub" ]]; then
|
|
printf " $NOTICE INFO! %s... $NORMAL" "$1"
|
|
aws securityhub batch-import-findings --findings "$(generateJsonAsffOutput "$1" "NOT_AVAILABLE" "LOW")" | jq -M -r 'if .SuccessCount == 1 then "Successfully submitted finding" else "Failed to upload finding" end'
|
|
fi
|
|
else
|
|
echo " $NOTICE INFO! $1 $NORMAL"
|
|
fi
|
|
}
|
|
|
|
textFail(){
|
|
FAIL_COUNTER=$((FAIL_COUNTER+1))
|
|
EXITCODE=3
|
|
if [[ "$MODE" == "csv" || "$MODE" == "json" || "$MODE" == "json-asff" || "$MODE" == "securityhub" ]]; then
|
|
if [[ $2 ]]; then
|
|
REPREGION=$2
|
|
else
|
|
REPREGION=$REGION
|
|
fi
|
|
if [[ "$MODE" == "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"
|
|
elif [[ "$MODE" == "json" ]]; then
|
|
generateJsonOutput "$1" "Fail"
|
|
elif [[ "$MODE" == "json-asff" ]]; then
|
|
generateJsonAsffOutput "$1" "FAILED" "HIGH"
|
|
elif [[ "$MODE" == "securityhub" ]]; then
|
|
printf " $BAD FAIL! %s... $NORMAL" "$1"
|
|
aws securityhub batch-import-findings --findings "$(generateJsonAsffOutput "$1" "FAILED" "HIGH")" | jq -M -r 'if .SuccessCount == 1 then "Successfully submitted finding" else "Failed to upload finding" end'
|
|
fi
|
|
else
|
|
echo " $BAD FAIL! $1 $NORMAL"
|
|
fi
|
|
}
|
|
|
|
textTitle(){
|
|
CHECKS_COUNTER=$((CHECKS_COUNTER+1))
|
|
TITLE_ID=$1
|
|
if [[ $NUMERAL ]]; then
|
|
TITLE_ID=$(echo $TITLE_ID | cut -d, -f2)
|
|
else
|
|
TITLE_ID=$(echo $TITLE_ID | cut -d, -f1)
|
|
fi
|
|
|
|
TITLE_TEXT=$2
|
|
|
|
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"
|
|
elif [[ "$MODE" == "json" || "$MODE" == "json-asff" ]]; then
|
|
:
|
|
else
|
|
if [[ "$ITEM_SCORED" == "Scored" ]]; then
|
|
echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT"
|
|
else
|
|
echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
generateJsonOutput(){
|
|
local message=$1
|
|
local status=$2
|
|
jq -M -c \
|
|
--arg PROFILE "$PROFILE" \
|
|
--arg ACCOUNT_NUM "$ACCOUNT_NUM" \
|
|
--arg TITLE_TEXT "$TITLE_TEXT" \
|
|
--arg MESSAGE "$(echo -e "${message}" | sed -e 's/^[[:space:]]*//')" \
|
|
--arg STATUS "$status" \
|
|
--arg SCORED "$ITEM_SCORED" \
|
|
--arg ITEM_LEVEL "$ITEM_LEVEL" \
|
|
--arg TITLE_ID "$TITLE_ID" \
|
|
--arg REPREGION "$REPREGION" \
|
|
--arg TIMESTAMP $(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
|
-n '{
|
|
"Profile": $PROFILE,
|
|
"Account Number": $ACCOUNT_NUM,
|
|
"Control": $TITLE_TEXT,
|
|
"Message": $MESSAGE,
|
|
"Status": $STATUS,
|
|
"Scored": $SCORED,
|
|
"Level": $ITEM_LEVEL,
|
|
"Control ID": $TITLE_ID,
|
|
"Region": $REPREGION,
|
|
"Timestamp": $TIMESTAMP,
|
|
}'
|
|
}
|
|
|
|
generateJsonAsffOutput(){
|
|
# UNIQUE_ID must only contain characters from the unreserved characters set defined in section 2.3 of RFC-3986
|
|
# Replace any successive non-conforming characters with a single underscore
|
|
local message=$1
|
|
local status=$2
|
|
local severity=$3
|
|
jq -M -c \
|
|
--arg PROFILE "$PROFILE" \
|
|
--arg ACCOUNT_NUM "$ACCOUNT_NUM" \
|
|
--arg TITLE_TEXT "$TITLE_TEXT" \
|
|
--arg MESSAGE "$(echo -e "${message}" | sed -e 's/^[[:space:]]*//')" \
|
|
--arg UNIQUE_ID "$(LC_ALL=C echo -e "${message}" | tr -cs '[:alnum:]._~-\n' '_')" \
|
|
--arg STATUS "$status" \
|
|
--arg SEVERITY "$severity" \
|
|
--arg SCORED "$ITEM_SCORED" \
|
|
--arg ITEM_LEVEL "$ITEM_LEVEL" \
|
|
--arg TITLE_ID "$TITLE_ID" \
|
|
--arg REPREGION "$REPREGION" \
|
|
--arg TIMESTAMP $(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
|
--arg PROWLER_VERSION "$PROWLER_VERSION" \
|
|
-n '{
|
|
"SchemaVersion": "2018-10-08",
|
|
"Id": "prowler-\($TITLE_ID)-\($ACCOUNT_NUM)-\($REPREGION)-\($UNIQUE_ID)",
|
|
"ProductArn": "arn:aws:securityhub:\($REPREGION):\($ACCOUNT_NUM):product/\($ACCOUNT_NUM)/default",
|
|
"ProductFields": {
|
|
"ProviderName": "Prowler",
|
|
"ProviderVersion": $PROWLER_VERSION
|
|
},
|
|
"GeneratorId": "prowler-\($PROWLER_VERSION)",
|
|
"AwsAccountId": $ACCOUNT_NUM,
|
|
"Types": [
|
|
"Software and Configuration Checks"
|
|
],
|
|
"FirstObservedAt": $TIMESTAMP,
|
|
"UpdatedAt": $TIMESTAMP,
|
|
"CreatedAt": $TIMESTAMP,
|
|
"Severity": {
|
|
"Label": $SEVERITY
|
|
},
|
|
"Title": $TITLE_TEXT,
|
|
"Description": $MESSAGE,
|
|
"Resources": [
|
|
{
|
|
"Type": "AwsAccount",
|
|
"Id": "AWS: : : :Account:\($ACCOUNT_NUM)",
|
|
"Partition": "aws",
|
|
"Region": $REPREGION
|
|
}
|
|
],
|
|
"Compliance": {
|
|
"Status": $STATUS
|
|
}
|
|
}'
|
|
}
|