mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-11 07:15:15 +00:00
238 lines
7.0 KiB
Bash
238 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
|
|
|
|
EXTENSION_CSV="csv"
|
|
EXTENSION_JSON="json"
|
|
EXTENSION_ASFF="asff-json"
|
|
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
|
|
|
|
|
|
textPass(){
|
|
if [[ "$QUIET" == 1 ]]; then
|
|
return
|
|
fi
|
|
|
|
PASS_COUNTER=$((PASS_COUNTER+1))
|
|
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[@]}" =~ "text" ]]; then
|
|
echo " $OK PASS!$NORMAL $1"
|
|
fi
|
|
}
|
|
|
|
textInfo(){
|
|
if [[ "$QUIET" == 1 ]]; then
|
|
return
|
|
fi
|
|
|
|
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[@]}" =~ "text" ]]; then
|
|
echo " $NOTICE INFO! $1 $NORMAL"
|
|
fi
|
|
}
|
|
|
|
textFail(){
|
|
FAIL_COUNTER=$((FAIL_COUNTER+1))
|
|
EXITCODE=3
|
|
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[@]}" =~ "text" ]]; then
|
|
echo " $BAD FAIL! $1 $NORMAL"
|
|
fi
|
|
}
|
|
|
|
textTitle(){
|
|
CHECKS_COUNTER=$((CHECKS_COUNTER+1))
|
|
TITLE_ID=$1
|
|
if [[ $NUMERAL ]]; then
|
|
# Left-pad the check ID with zeros to simplify sorting, e.g. 1.1 -> 1.01
|
|
TITLE_ID=$(awk -F'.' '{ printf "%d.%02d", $1, $2 }' <<< "$TITLE_ID")
|
|
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
|
|
|
|
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
|
|
elif [[ "${MODES[@]}" =~ "json" || "${MODES[@]}" =~ "json-asff" ]]; then
|
|
:
|
|
else
|
|
if [[ "$ITEM_SCORED" == "Scored" ]]; then
|
|
echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT $group_ids"
|
|
else
|
|
echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL $group_ids"
|
|
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 "$(get_iso8601_timestamp)" \
|
|
-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 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 TITLE_ID "$TITLE_ID" \
|
|
--arg TYPE "$ASFF_TYPE" \
|
|
--arg RESOURCE_TYPE "$ASFF_RESOURCE_TYPE" \
|
|
--arg REPREGION "$REPREGION" \
|
|
--arg TIMESTAMP "$(get_iso8601_timestamp)" \
|
|
--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": [
|
|
$TYPE
|
|
],
|
|
"FirstObservedAt": $TIMESTAMP,
|
|
"UpdatedAt": $TIMESTAMP,
|
|
"CreatedAt": $TIMESTAMP,
|
|
"Severity": {
|
|
"Label": $SEVERITY
|
|
},
|
|
"Title": $TITLE_TEXT,
|
|
"Description": $MESSAGE,
|
|
"Resources": [
|
|
{
|
|
"Type": $RESOURCE_TYPE,
|
|
"Id": "AWS::::Account:\($ACCOUNT_NUM)",
|
|
"Partition": "aws",
|
|
"Region": $REPREGION
|
|
}
|
|
],
|
|
"Compliance": {
|
|
"Status": $STATUS
|
|
}
|
|
}'
|
|
}
|