mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-11 07:15:15 +00:00
Added new option "-E" which will execute all tests except a list of specified checks separated by comma (i.e. check21,check31). Any invalid check name will be discarded. And if just one argument is passed and this is invalid, then Prowler will execute all checks. To save space, the option will return a list of total checks excluding the list provided. Then, the functionality will overwrite CHECK_ID with the final list and the program will continue as if the user entered "-c" option and the final list of checks.
383 lines
11 KiB
Bash
Executable File
383 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
# Copyright 2018 Toni de la Fuente
|
||
|
||
# Prowler is a tool that provides automate auditing and hardening guidance of an
|
||
# AWS account. It is based on AWS-CLI commands. It follows some guidelines
|
||
# present in the CIS Amazon Web Services Foundations Benchmark at:
|
||
# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf
|
||
|
||
# Contact the author at https://blyx.com/contact
|
||
# and open issues or ask questions at https://github.com/toniblyx/prowler
|
||
|
||
|
||
# All CIS based checks in checks folder are licensed under a Creative Commons
|
||
# Attribution-NonCommercial-ShareAlike 4.0 International Public License.
|
||
# The link to the license terms can be found at
|
||
# https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
|
||
#
|
||
# Any other piece of code is licensed as Apache License 2.0 as specified in
|
||
# each file. You may obtain a copy of the License at
|
||
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
||
# Prowler - Iron Maiden
|
||
#
|
||
# Walking through the city, looking oh so pretty
|
||
# I've just got to find my way
|
||
# See the ladies flashing
|
||
# All there legs and lashes
|
||
# I've just got to find my way...
|
||
|
||
OPTRED="[1;31m"
|
||
OPTNORMAL="[0;39m"
|
||
|
||
# Set the defaults variables
|
||
PROWLER_VERSION=2.0
|
||
PROWLER_DIR=$(dirname "$0")
|
||
|
||
REGION=""
|
||
FILTERREGION=""
|
||
MAXITEMS=100
|
||
MONOCHROME=0
|
||
MODE="text"
|
||
SEP=','
|
||
KEEPCREDREPORT=0
|
||
EXITCODE=0
|
||
SCRIPT_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" )
|
||
TITLE_ID=""
|
||
TITLE_TEXT="CALLER ERROR - UNSET TITLE"
|
||
|
||
# Command usage menu
|
||
usage(){
|
||
echo "
|
||
USAGE:
|
||
`basename $0` [ -p <profile> -r <region> -h ]
|
||
Options:
|
||
-p <profile> specify your AWS profile to use (i.e.: default)
|
||
-r <region> specify an AWS region to direct API requests to
|
||
(i.e.: us-east-1), all regions are checked anyway if the check requires it
|
||
-c <check_id> specify one or multiple check ids separated by commas, to see all available checks use "-l" option
|
||
(i.e.: "check11" for check 1.1 or "extra71,extra72" for extra check 71 and extra check 72)
|
||
-g <group_id> specify a group of checks by id, to see all available group of checks use "-L"
|
||
(i.e.: "check3" for entire section 3, "level1" for CIS Level 1 Profile Definitions or "forensics-ready")
|
||
-f <filterregion> specify an AWS region to run checks against
|
||
(i.e.: us-west-1)
|
||
-m <maxitems> specify the maximum number of items to return for long-running requests (default: 100)
|
||
-M <mode> output mode: text (default), mono, json, csv (separator is ","; data is on stdout; progress on stderr)
|
||
-k keep the credential report
|
||
-n show check numbers to sort easier
|
||
(i.e.: 1.01 instead of 1.1)
|
||
-l list all available checks only (does not perform any check)
|
||
-L list all groups (does not perform any check)
|
||
-e exclude group extras
|
||
-E execute all tests except a list of specified checks separated by comma (i.e. check21,check31)
|
||
-b do not print Prowler banner
|
||
-V show version number & exit
|
||
-h this help
|
||
"
|
||
exit
|
||
}
|
||
|
||
while getopts ":hlLkp:r:c:g:f:m:M:E:enbV" OPTION; do
|
||
case $OPTION in
|
||
h )
|
||
usage
|
||
EXITCODE=1
|
||
exit $EXITCODE
|
||
;;
|
||
l )
|
||
PRINTCHECKSONLY=1
|
||
;;
|
||
L )
|
||
PRINTGROUPSONLY=1
|
||
;;
|
||
k )
|
||
KEEPCREDREPORT=1
|
||
;;
|
||
p )
|
||
PROFILE=$OPTARG
|
||
;;
|
||
r )
|
||
REGION_OPT=$OPTARG
|
||
;;
|
||
c )
|
||
CHECK_ID=$OPTARG
|
||
;;
|
||
g )
|
||
GROUP_ID_READ=$OPTARG
|
||
;;
|
||
f )
|
||
FILTERREGION=$OPTARG
|
||
;;
|
||
m )
|
||
MAXITEMS=$OPTARG
|
||
;;
|
||
M )
|
||
MODE=$OPTARG
|
||
;;
|
||
n )
|
||
NUMERAL=1
|
||
;;
|
||
b )
|
||
BANNER=0
|
||
;;
|
||
e )
|
||
EXTRAS=1
|
||
;;
|
||
E )
|
||
EXCLUDE_CHECK_ID=$OPTARG
|
||
;;
|
||
V )
|
||
echo "Prowler $PROWLER_VERSION"
|
||
EXITCODE=0
|
||
exit $EXITCODE
|
||
;;
|
||
: )
|
||
echo ""
|
||
echo "$OPTRED ERROR!$OPTNORMAL -$OPTARG requires an argument"
|
||
usage
|
||
EXITCODE=1
|
||
exit $EXITCODE
|
||
;;
|
||
? )
|
||
echo ""
|
||
echo "$OPTRED ERROR!$OPTNORMAL Invalid option"
|
||
usage
|
||
EXITCODE=1
|
||
exit $EXITCODE
|
||
;;
|
||
esac
|
||
done
|
||
|
||
. $PROWLER_DIR/include/colors
|
||
. $PROWLER_DIR/include/os_detector
|
||
. $PROWLER_DIR/include/aws_profile_loader
|
||
. $PROWLER_DIR/include/awscli_detector
|
||
. $PROWLER_DIR/include/outputs
|
||
. $PROWLER_DIR/include/csv_header
|
||
. $PROWLER_DIR/include/banner
|
||
. $PROWLER_DIR/include/whoami
|
||
. $PROWLER_DIR/include/credentials_report
|
||
|
||
# Get a list of all available AWS Regions
|
||
REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \
|
||
--output text \
|
||
$PROFILE_OPT \
|
||
--region $REGION \
|
||
--region-names $FILTERREGION)
|
||
|
||
# Load all of the groups of checks inside groups folder named as "groupNumber*"
|
||
for group in $(ls $PROWLER_DIR/groups/group[0-9]*|grep -v groupN_sample); do
|
||
. "$group"
|
||
done
|
||
|
||
# Load all of the checks inside checks folder named as "check*"
|
||
# this includes also extra checks since they are "check_extraNN"
|
||
for checks in $(ls $PROWLER_DIR/checks/check*|grep -v check_sample); do
|
||
. "$checks"
|
||
done
|
||
|
||
# Function to show the title of the check
|
||
# using this way instead of arrays to keep bash3 (osx) and bash4(linux) compatibility
|
||
show_check_title() {
|
||
local check_id=CHECK_ID_$1
|
||
local check_title=CHECK_TITLE_$1
|
||
local check_scored=CHECK_SCORED_$1
|
||
local check_type=CHECK_TYPE_$1
|
||
textTitle "${!check_id}" "${!check_title}" "${!check_scored}" "${!check_type}"
|
||
}
|
||
|
||
# Function to show the title of a group, by numeric id
|
||
show_group_title() {
|
||
# when csv mode is used, no group tittle is shown
|
||
if [[ "$MODE" != "csv" ]]; then
|
||
textTitle "${GROUP_NUMBER[$1]}" "${GROUP_TITLE[$1]}" "NOT_SCORED" "SUPPORT"
|
||
fi
|
||
}
|
||
|
||
# Function to execute the check
|
||
execute_check() {
|
||
# See if this is an alternate name for a check
|
||
# for example, we might have been passed 1.01 which is another name for 1.1
|
||
local alternate_name_var=CHECK_ALTERNATE_$1
|
||
local alternate_name=${!alternate_name_var}
|
||
# Generate the credential report, only if it is group1 related which checks we
|
||
# run so that the checks can safely assume it's available
|
||
if [ ${alternate_name} ];then
|
||
if [[ ${alternate_name} == check1* ]];then
|
||
if [ ! -s $TEMP_REPORT_FILE ];then
|
||
genCredReport
|
||
saveReport
|
||
fi
|
||
fi
|
||
show_check_title ${alternate_name}
|
||
${alternate_name}
|
||
else
|
||
# Check to see if this is a real check
|
||
local check_id_var=CHECK_ID_$1
|
||
local check_id=${!check_id_var}
|
||
if [ ${check_id} ]; then
|
||
if [[ ${check_id} == 1* ]];then
|
||
if [ ! -s $TEMP_REPORT_FILE ];then
|
||
genCredReport
|
||
saveReport
|
||
fi
|
||
fi
|
||
show_check_title $1
|
||
$1
|
||
else
|
||
textFail "ERROR! Use a valid check name (i.e. check41 or extra71)";
|
||
exit $EXITCODE
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Function to execute all checks in a group
|
||
execute_group() {
|
||
show_group_title $1
|
||
# run the checks in the group
|
||
IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$1]}
|
||
for i in ${CHECKS[@]}; do
|
||
execute_check $i
|
||
done
|
||
}
|
||
|
||
# Function to execute group by name
|
||
execute_group_by_id() {
|
||
if [ "${GROUP_ID[$1]}" == "group1" ]; then
|
||
genCredReport
|
||
saveReport
|
||
fi
|
||
for i in "${!GROUP_ID[@]}"; do
|
||
if [ "${GROUP_ID[$i]}" == "$1" ]; then
|
||
execute_group $i
|
||
fi
|
||
done
|
||
}
|
||
|
||
# Function to execute all checks in all groups
|
||
execute_all() {
|
||
for i in "${!GROUP_TITLE[@]}"; do
|
||
if [ "${GROUP_RUN_BY_DEFAULT[$i]}" == "Y" ]; then
|
||
execute_group $i
|
||
fi
|
||
done
|
||
}
|
||
|
||
# Function to show the titles of everything
|
||
show_all_titles() {
|
||
for i in "${!GROUP_TITLE[@]}"; do
|
||
show_group_title $i
|
||
# Display the title of the checks
|
||
IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$i]}
|
||
for j in ${CHECKS[@]}; do
|
||
show_check_title $j
|
||
done
|
||
done
|
||
}
|
||
|
||
show_all_group_titles() {
|
||
for i in "${!GROUP_TITLE[@]}"; do
|
||
show_group_title $i
|
||
done
|
||
}
|
||
|
||
|
||
# Function to execute all checks but exclude some of them
|
||
get_all_checks_without_exclusion() {
|
||
CHECKS_EXCLUDED=()
|
||
local CHECKS_TO_EXCLUDE=()
|
||
local TOTAL_CHECKS=()
|
||
#Get a list of checks to exclude
|
||
IFS=',' read -ra E_CHECKS <<< "$1"
|
||
for E_CHECK in "${E_CHECKS[@]}"; do
|
||
CHECKS_TO_EXCLUDE+=($E_CHECK)
|
||
done
|
||
#Get a list of total checks available by ID
|
||
for i in "${!GROUP_TITLE[@]}"; do
|
||
#show_group_title $i
|
||
IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$i]}
|
||
for j in ${CHECKS[@]}; do
|
||
TOTAL_CHECKS+=($CHECK_ID_$j)
|
||
done
|
||
done
|
||
TOTAL_CHECKS=($(echo "${TOTAL_CHECKS[*]}" | tr ' ' '\n' | sort -u)) #removes duplicate and store the result as an array
|
||
#Create a list that contains all checks but excluded ones
|
||
for i in "${TOTAL_CHECKS[@]}"; do
|
||
local COINCIDENCE=false
|
||
for x in "${CHECKS_TO_EXCLUDE[@]}"; do
|
||
if [[ "$i" == "$x" ]]; then
|
||
COINCIDENCE=true
|
||
fi
|
||
done
|
||
if [[ "$COINCIDENCE" = false ]]; then
|
||
CHECKS_EXCLUDED+=($i)
|
||
fi
|
||
done
|
||
}
|
||
|
||
### All functions defined above ... run the workflow
|
||
if [[ $MODE != "csv" ]]; then
|
||
prowlerBanner
|
||
fi
|
||
|
||
# List only check tittles
|
||
if [[ $PRINTCHECKSONLY == "1" ]]; then
|
||
show_all_titles
|
||
exit $EXITCODE
|
||
fi
|
||
|
||
# List only group tittles
|
||
if [[ $PRINTGROUPSONLY == "1" ]]; then
|
||
show_all_group_titles
|
||
exit $EXITCODE
|
||
fi
|
||
|
||
# Check that jq is installed for JSON output
|
||
if [[ $MODE == "json" ]]; then
|
||
. $PROWLER_DIR/include/jq_detector
|
||
fi
|
||
|
||
# Gather account data / test aws cli connectivity
|
||
getWhoami
|
||
|
||
# Get a list of total checks excluding a list provided by the user and overwrite CHECK_ID with the result
|
||
# if the list provided by the user contains an invalid check, this will be discarded.
|
||
# if the list provided by the user contains just one argument and is invalid, then it will be discarded and all tests will be executed
|
||
if [[ $EXCLUDE_CHECK_ID ]];then
|
||
get_all_checks_without_exclusion $EXCLUDE_CHECK_ID
|
||
function join { local IFS="$1"; shift; echo "$*"; }
|
||
CHECKS_EXCLUDED=$(join , "${CHECKS_EXCLUDED[@]}")
|
||
CHECK_ID=$CHECKS_EXCLUDED
|
||
fi
|
||
|
||
# Execute single check if called with -c
|
||
if [[ $CHECK_ID ]];then
|
||
IFS=',' read -ra CHECKS <<< "$CHECK_ID"
|
||
for CHECK in "${CHECKS[@]}"; do
|
||
execute_check $CHECK
|
||
done
|
||
cleanTemp
|
||
exit $EXITCODE
|
||
fi
|
||
|
||
# Execute group of checks if called with -g
|
||
if [[ $GROUP_ID_READ ]];then
|
||
if [[ " ${GROUP_ID[@]} " =~ " ${GROUP_ID_READ} " ]]; then
|
||
if [[ $MODE == "csv" ]]; then
|
||
BANNER=0
|
||
fi
|
||
execute_group_by_id $GROUP_ID_READ
|
||
cleanTemp
|
||
exit $EXITCODE
|
||
else
|
||
textFail "Use a valid check group ID i.e.: group1, extras, forensics-ready, etc."
|
||
show_all_group_titles
|
||
exit $EXITCODE
|
||
fi
|
||
fi
|
||
|
||
execute_all
|
||
cleanTemp
|
||
exit $EXITCODE
|