#!/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 # 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 -c specify a check number or group from the AWS CIS benchmark (i.e.: "check11" for check 1.1, "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 (defalut), 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 extras -b do not print Prowler banner -h this help " exit } while getopts ":hlkp:r:c: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 ) CHECKNUMBER=$OPTARG ;; f ) FILTERREGION=$OPTARG ;; m ) MAXITEMS=$OPTARG ;; M ) MODE=$OPTARG ;; n ) NUMERAL=1 ;; b ) NOBANNER="true" ;; 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) callCheck(){ if [[ $CHECKNUMBER ]];then case "$CHECKNUMBER" in check11|check101 ) execute_check check11;; check12|check102 ) execute_check check12;; * ) textWarn "ERROR! Use a valid check name (i.e. check41 or extra71)\n"; esac cleanTemp exit $EXITCODE fi } # List only check tittles if [[ $PRINTCHECKSONLY == "1" ]]; then prowlerBanner show_all_titles exit $EXITCODE fi # Load all of the groups of checks inside groups folder named as "group*" for group in $(ls groups/group*); 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*); do . "$checks" done # Function to show the title of the check show_check_title() { # This would just call textTitle textTitle "${CHECK_ID[$1]}" "${CHECK_TITLE[$1]}" "${CHECK_SCORED[$1]}" "${CHECK_TYPE[$1]}" } # 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 if [ ${CHECK_ALTERNATE[$1]} ];then show_check_title ${CHECK_ALTERNATE[$1]} ${CHECK_ALTERNATE[$1]} else show_check_title $1 $1 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_name() { for i in ${#GROUP_NAME[@]}; do if [ "${GROUP_NAME[$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 } ### All functions defined above ... run the workflow if [[ $MODE != "csv" || $NOBANNER != "true" ]]; then prowlerBanner printColorsCode fi getWhoami genCredReport saveReport #callCheck execute_all # if [[ ! $EXTRAS ]]; then # textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT" # execute_group 7 # fi cleanTemp exit $EXITCODE