#!/usr/bin/env bash # Prowler is a tool that provides automate auditing and hardening guidance of an AWS account. # It is based on AWS-CLI commands. It follows guidelines present in the CIS Amazon # Web Services Foundations Benchmark at: # https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf # This work is 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 # # Author: Toni de la Fuente - @ToniBlyx - https://blyx.com/contact # http://prowler.cloud # 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="" OPTNORMAL="" # Set the defaults for these getopts variables REGION="us-east-1" 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 -r -h ] Options: -p specify your AWS profile to use (i.e.: default) -r 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 specify a check id, to see all available checks use "-l" option (i.e.: "check11" for check 1.1 or "extra71" for extra check 71) -g 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 specify an AWS region to run checks against (i.e.: us-west-1) -m specify the maximum number of items to return for long-running requests (default: 100) -M output mode: text (default), mono, 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) -e exclude group extras -b do not print Prowler banner -h this help " exit } while getopts ":hlkp:r:c:g:f:m:M:enb" OPTION; do case $OPTION in h ) usage EXITCODE=1 exit $EXITCODE ;; l ) PRINTCHECKSONLY=1 ;; k ) KEEPCREDREPORT=1 ;; p ) PROFILE=$OPTARG ;; r ) REGION=$OPTARG ;; c ) CHECK_ID=$OPTARG ;; g ) GROUP_ID=$OPTARG ;; f ) FILTERREGION=$OPTARG ;; m ) MAXITEMS=$OPTARG ;; M ) MODE=$OPTARG ;; n ) NUMERAL=1 ;; b ) BANNER=0 ;; e ) EXTRAS=1 ;; : ) 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 . include/colors . include/os_detector . include/aws_profile_loader . include/awscli_detector . include/outputs . include/csv_header . include/banner . include/whoami . 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 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 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} if [ ${alternate_name} ];then 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 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 prowlerBanner 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 } # Execute single check if called with -c if [[ $CHECK_ID ]];then execute_check $CHECK_ID cleanTemp exit $EXITCODE fi # Execute group of checks if called with -g if [[ $GROUP_ID ]];then if [[ $MODE == "csv" ]]; then BANNER=0 fi execute_group_by_id $GROUP_ID cleanTemp exit $EXITCODE fi # List only check tittles if [[ $PRINTCHECKSONLY == "1" ]]; then prowlerBanner show_all_titles exit $EXITCODE fi ### All functions defined above ... run the workflow if [[ $MODE != "csv" ]]; then prowlerBanner fi genCredReport saveReport getWhoami execute_all cleanTemp exit $EXITCODE