Files
prowler/include/outputs
Marc Jay 47a05c203a 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 <group_id>` 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
2020-04-20 01:12:53 +01:00

235 lines
7.2 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 [[ "${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
echo " $OK PASS!$NORMAL $1"
fi
}
textInfo(){
if [[ "$QUIET" == 1 ]]; then
return
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
echo " $NOTICE INFO! $1 $NORMAL"
fi
}
textFail(){
FAIL_COUNTER=$((FAIL_COUNTER+1))
EXITCODE=3
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
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 $(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 TYPE "$ASFF_TYPE" \
--arg RESOURCE_TYPE "$ASFF_RESOURCE_TYPE" \
--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": [
$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
}
}'
}