diff --git a/checks/check11 b/checks/check11 index 07ea5c2c..d56d70f6 100644 --- a/checks/check11 +++ b/checks/check11 @@ -6,6 +6,5 @@ CHECK_ALTERNATE_check101="check11" check11(){ # "Avoid the use of the root account (Scored)." COMMAND11=$(cat $TEMP_REPORT_FILE| grep '' | cut -d, -f5,11,16 | sed 's/,/\ /g') - textTitle "$ID11" "$TITLE11" "SCORED" "LEVEL1" textNotice "Root account last accessed (password key_1 key_2): $COMMAND11" } diff --git a/checks/check12 b/checks/check12 index 62f803df..54d68e8b 100644 --- a/checks/check12 +++ b/checks/check12 @@ -11,7 +11,6 @@ check12(){ for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$8 }' |grep "$i " |grep false | awk '{ print $1 }' done) - textTitle "$ID12" "$TITLE12" "SCORED" "LEVEL1" if [[ $COMMAND12 ]]; then for u in $COMMAND12; do textWarn "User $u has Password enabled but MFA disabled" diff --git a/checks/list b/checks/list deleted file mode 100644 index bb66eca3..00000000 --- a/checks/list +++ /dev/null @@ -1,166 +0,0 @@ - _ - _ __ _ __ _____ _| | ___ _ __ - | '_ \| '__/ _ \ \ /\ / / |/ _ \ '__| - | |_) | | | (_) \ V V /| | __/ | - | .__/|_| \___/ \_/\_/ |_|\___|_| - |_| CIS based AWS Account Hardening Tool - - Date: Tue Mar 20 21:50:38 EDT 2018 - - 1 Identity and Access Management **************************************** - - 1.1 Avoid the use of the root account (Scored). - - 1.2 Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored) - - 1.3 Ensure credentials unused for 90 days or greater are disabled (Scored) - - 1.4 Ensure access keys are rotated every 90 days or less (Scored) - - 1.5 Ensure IAM password policy requires at least one uppercase letter (Scored) - - 1.6 Ensure IAM password policy require at least one lowercase letter (Scored) - - 1.7 Ensure IAM password policy require at least one symbol (Scored) - - 1.8 Ensure IAM password policy require at least one number (Scored) - - 1.9 Ensure IAM password policy requires minimum length of 14 or greater (Scored) - - 1.10 Ensure IAM password policy prevents password reuse: 24 or greater (Scored) - - 1.11 Ensure IAM password policy expires passwords within 90 days or less (Scored) - - 1.12 Ensure no root account access key exists (Scored) - - 1.13 Ensure MFA is enabled for the root account (Scored) - - 1.14 Ensure hardware MFA is enabled for the root account (Scored) - - 1.15 Ensure security questions are registered in the AWS account (Not Scored) - - 1.16 Ensure IAM policies are attached only to groups or roles (Scored) - - 1.17 Enable detailed billing (Scored) - - 1.18 Ensure IAM Master and IAM Manager roles are active (Scored) - - 1.19 Maintain current contact details (Scored) - - 1.20 Ensure security contact information is registered (Scored) - - 1.21 Ensure IAM instance roles are used for AWS resource access from instances (Not Scored) - - 1.22 Ensure a support role has been created to manage incidents with AWS Support (Scored) - - 1.23 Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored) - - 1.24 Ensure IAM policies that allow full "*:*" administrative privileges are not created (Scored) - - 2 Logging *************************************************************** - - 2.1 Ensure CloudTrail is enabled in all regions (Scored) - - 2.2 Ensure CloudTrail log file validation is enabled (Scored) - - 2.3 Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored) - - 2.4 Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored) - - 2.5 Ensure AWS Config is enabled in all regions (Scored) - - 2.6 Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored) - - 2.7 Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored) - - 2.8 Ensure rotation for customer created CMKs is enabled (Scored) - - 3 Monitoring ************************************************************ - - 3.1 Ensure a log metric filter and alarm exist for unauthorized API calls (Scored) - - 3.2 Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored) - - 3.3 Ensure a log metric filter and alarm exist for usage of root account (Scored) - - 3.4 Ensure a log metric filter and alarm exist for IAM policy changes (Scored) - - 3.5 Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored) - - 3.6 Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored) - - 3.7 Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored) - - 3.8 Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored) - - 3.9 Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored) - - 3.10 Ensure a log metric filter and alarm exist for security group changes (Scored) - - 3.11 Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored) - - 3.12 Ensure a log metric filter and alarm exist for changes to network gateways (Scored) - - 3.13 Ensure a log metric filter and alarm exist for route table changes (Scored) - - 3.14 Ensure a log metric filter and alarm exist for VPC changes (Scored) - - 3.15 Ensure appropriate subscribers to each SNS topic (Not Scored) - - 4 Networking ************************************************************ - - 4.1 Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored) - - 4.2 Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored) - - 4.3 Ensure VPC Flow Logging is Enabled in all VPCs (Scored) - - 4.4 Ensure the default security group of every VPC restricts all traffic (Scored) - - 4.5 Ensure routing tables for VPC peering are "least access" (Not Scored) - - 7 Extras **************************************************************** - - 7.1 Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark) - - 7.2 Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark) - - 7.3 Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark) - - 7.4 Ensure there are no Security Groups without ingress filtering being used (Not Scored) (Not part of CIS benchmark) - - 7.5 Ensure there are no Security Groups not being used (Not Scored) (Not part of CIS benchmark) - - 7.6 Ensure there are no EC2 AMIs set as Public (Not Scored) (Not part of CIS benchmark) - - 7.7 Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark) - - 7.8 Ensure there are no Public Accessible RDS instances (Not Scored) (Not part of CIS benchmark) - - 7.9 Check for internet facing Elastic Load Balancers (Not Scored) (Not part of CIS benchmark) - - 7.10 Check for internet facing EC2 Instances (Not Scored) (Not part of CIS benchmark) - - 7.11 Check for Publicly Accessible Redshift Clusters (Not Scored) (Not part of CIS benchmark) - - 7.12 Check if Amazon Macie is enabled (Not Scored) (Not part of CIS benchmark) - - 7.13 Check if GuardDuty is enabled (Not Scored) (Not part of CIS benchmark) - - 7.14 Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark) - - 7.15 Check if Elasticsearch Service domains have logging enabled (Not Scored) (Not part of CIS benchmark) - - 7.16 Check if Elasticsearch Service domains allow open access (Not Scored) (Not part of CIS benchmark) - - 7.17 Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark) - - 7.18 Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark) - - 7.19 Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark) - - 7.20 Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark) - - 7.21 Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark) - - 7.22 Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark) diff --git a/checks/lista b/checks/lista deleted file mode 100644 index 4640ead0..00000000 --- a/checks/lista +++ /dev/null @@ -1,73 +0,0 @@ -check13 -check14 -check15 -check16 -check17 -check18 -check19 -check110 -check111 -check112 -check113 -check114 -check115 -check116 -check117 -check118 -check119 -check120 -check121 -check122 -check123 -check124 -check21 -check22 -check23 -check24 -check25 -check26 -check27 -check28 -check31 -check32 -check33 -check34 -check35 -check36 -check37 -check38 -check39 -check310 -check311 -check312 -check313 -check314 -check315 -check41 -check42 -check43 -check44 -check45 -check_extra71 -check_extra72 -check_extra73 -check_extra74 -check_extra75 -check_extra76 -check_extra77 -check_extra78 -check_extra79 -check_extra710 -check_extra711 -check_extra712 -check_extra713 -check_extra714 -check_extra715 -check_extra716 -check_extra717 -check_extra718 -check_extra719 -check_extra720 -check_extra721 -check_extra722 -check_extra723 diff --git a/include/outputs b/include/outputs index 42ad7336..ae9e260e 100644 --- a/include/outputs +++ b/include/outputs @@ -8,7 +8,7 @@ textOK(){ fi echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}PASS${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1" else - echo " $OK OK! $NORMAL $1" + echo " $OK PASS! $NORMAL $1" fi } @@ -35,7 +35,7 @@ textWarn(){ fi echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}WARNING${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1" else - echo " $BAD WARNING! $1 $NORMAL" + echo " $BAD FAIL! $1 $NORMAL" fi } diff --git a/prowler2 b/prowler similarity index 99% rename from prowler2 rename to prowler index 9fc59819..4ac90f44 100755 --- a/prowler2 +++ b/prowler @@ -222,7 +222,7 @@ show_all_titles() { show_group_title $i # Display the title of the checks IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$i]} - for j in "${GROUP_CHECKS[@]}"; do + for j in $CHECKS; do show_check_title $j done done diff --git a/prowler1 b/prowler1 deleted file mode 100755 index fffc0d8c..00000000 --- a/prowler1 +++ /dev/null @@ -1,2471 +0,0 @@ -#!/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 / Alfresco Software Inc. - -# 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... - -# Exit if a pipeline results in an error. -# set -ue -# set -o pipefail -# set -vx -# Exits if any error is found -# set -e - -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 - - -# 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 - -h this help - " - exit -} - -while getopts ":hlkp:r:c:f:m:M:en" 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 - ;; - 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 - -if [[ $MODE != "mono" && $MODE != "text" && $MODE != "csv" ]]; then - echo "" - echo "$OPTRED ERROR!$OPTNORMAL Invalid output mode. Choose text, mono, or csv." - usage - EXITCODE=1 - exit $EXITCODE -fi - -if [[ "$MODE" == "mono" || "$MODE" == "csv" ]]; then - MONOCHROME=1 -fi - -if [[ $MONOCHROME -eq 1 ]]; then - # Colors - NORMAL='' - WARNING='' # Bad (red) - SECTION='' # Section (yellow) - NOTICE='' # Notice (yellow) - OK='' # Ok (green) - BAD='' # Bad (red) - CYAN='' - BLUE='' - BROWN='' - DARKGRAY='' - GRAY='' - GREEN='' - MAGENTA='' - PURPLE='' - RED='' - YELLOW='' - WHITE='' -else - # Colors - # NOTE: Your editor may NOT show the 0x1b / escape character left of the '[' - NORMAL="" - WARNING="" # Bad (red) - SECTION="" # Section (yellow) - NOTICE="" # Notice (yellow) - OK="" # Ok (green) - BAD="" # Bad (red) - CYAN="" - BLUE="" - BROWN="" - DARKGRAY="" - GRAY="" - GREEN="" - MAGENTA="" - PURPLE="" - RED="" - YELLOW="" - WHITE="" -fi - -SCRIPT_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" ) - -# Functions to manage dates depending on OS -if [ "$OSTYPE" == "linux-gnu" ] || [ "$OSTYPE" == "linux-musl" ]; then - TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX) - # function to compare in days, usage how_older_from_today date - # date format %Y-%m-%d - how_older_from_today() - { - DATE_TO_COMPARE=$1 - TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s) - DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s) - DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24)) - echo $DAYS_SINCE - } - # function to convert from timestamp to date, usage timestamp_to_date timestamp - # output date format %Y-%m-%d - timestamp_to_date() - { - # remove fractions of a second - TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".") - OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d') - echo $OUTPUT_DATE - } - decode_report() - { - base64 -d - } -elif [[ "$OSTYPE" == "darwin"* ]]; then - # BSD/OSX commands compatibility - TEMP_REPORT_FILE=$(mktemp -t prowler.cred_report-XXXXXX) - how_older_from_today() - { - DATE_TO_COMPARE=$1 - TODAY_IN_DAYS=$(date +%s) - DATE_FROM_IN_DAYS=$(date -jf %Y-%m-%d $DATE_TO_COMPARE +%s) - DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24)) - echo $DAYS_SINCE - } - timestamp_to_date() - { - # remove fractions of a second - TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".") - OUTPUT_DATE=$(date -r $TIMESTAMP_TO_CONVERT +'%Y-%m-%d') - echo $OUTPUT_DATE - } - decode_report() - { - base64 -D - } -elif [[ "$OSTYPE" == "cygwin" ]]; then - # POSIX compatibility layer and Linux environment emulation for Windows - TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX) - how_older_from_today() - { - DATE_TO_COMPARE=$1 - TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s) - DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s) - DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24)) - echo $DAYS_SINCE - } - timestamp_to_date() - { - # remove fractions of a second - TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".") - OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d') - echo $OUTPUT_DATE - } - decode_report() - { - base64 -d - } -else - echo "Unknown Operating System! Valid \$OSTYPE: linux-gnu, linux-musl, darwin* or cygwin" - echo "Found: $OSTYPE" - EXITCODE=1 - exit $EXITCODE -fi - -# It checks -p optoin first and use it as profile, if not -p provided then -# check environment variables and if not, it checks and loads credentials from -# instance profile (metadata server) if runs in an EC2 instance - -if [[ $PROFILE ]]; then - PROFILE_OPT="--profile $PROFILE" -else - # if Prowler runs insinde an AWS instance with IAM instance profile attached - INSTANCE_PROFILE=$(curl -s -m 1 http://169.254.169.254/latest/meta-data/iam/security-credentials/) - if [[ $INSTANCE_PROFILE ]]; then - AWS_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g') - AWS_SECRET_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') - AWS_SESSION_TOKEN=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} grep Token| cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g') - fi - if [[ $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY || $AWS_SESSION_TOKEN ]];then - PROFILE="ENV" - PROFILE_OPT="" - else - PROFILE="default" - PROFILE_OPT="--profile $PROFILE" - fi -fi - -# AWS-CLI variables -AWSCLI=$(which aws) -if [ -z "${AWSCLI}" ]; then - echo -e "\n$RED ERROR!$NORMAL AWS-CLI (aws command) not found. Make sure it is installed correctly and in your \$PATH\n" - EXITCODE=1 - exit $EXITCODE -fi - -TITLE_ID="" -TITLE_TEXT="CALLER ERROR - UNSET TITLE" -## Output formatting functions -textOK(){ - if [[ "$MODE" == "csv" ]]; then - if [[ $2 ]]; then - REPREGION=$2 - else - REPREGION=$REGION - fi - echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}PASS${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1" - else - echo " $OK OK! $NORMAL $1" - fi -} - -textNotice(){ - if [[ "$MODE" == "csv" ]]; then - if [[ $2 ]]; then - REPREGION=$2 - else - REPREGION=$REGION - fi - echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}INFO${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1" - else - echo " $NOTICE INFO! $1 $NORMAL" - fi -} - -textWarn(){ - EXITCODE=3 - if [[ "$MODE" == "csv" ]]; then - if [[ $2 ]]; then - REPREGION=$2 - else - REPREGION=$REGION - fi - echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}WARNING${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1" - else - echo " $BAD WARNING! $1 $NORMAL" - fi -} - -textTitle(){ - TITLE_ID=$1 - if [[ $NUMERAL ]]; then - TITLE_ID=$(echo $TITLE_ID | cut -d, -f2) - else - TITLE_ID=$(echo $TITLE_ID | cut -d, -f1) - 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 - - if [[ "$MODE" == "csv" ]]; then - >&2 echo "$TITLE_ID $TITLE_TEXT" - else - if [[ "$ITEM_SCORED" == "Scored" ]]; then - echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT" - else - echo -e "\n$PURPLE $TITLE_ID $TITLE_TEXT $NORMAL" - fi - fi -} - -# List of checks IDs and Titles -TITLE1="Identity and Access Management ****************************************" -ID11="1.1,1.01" -TITLE11="Avoid the use of the root account (Scored)." -ID12="1.2,1.02" -TITLE12="Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)" -ID13="1.3,1.03" -TITLE13="Ensure credentials unused for 90 days or greater are disabled (Scored)" -ID14="1.4,1.04" -TITLE14="Ensure access keys are rotated every 90 days or less (Scored)" # also checked by Security Monkey -ID15="1.5,1.05" -TITLE15="Ensure IAM password policy requires at least one uppercase letter (Scored)" -ID16="1.6,1.06" -TITLE16="Ensure IAM password policy require at least one lowercase letter (Scored)" -ID17="1.7,1.07" -TITLE17="Ensure IAM password policy require at least one symbol (Scored)" -ID18="1.8,1.08" -TITLE18="Ensure IAM password policy require at least one number (Scored)" -ID19="1.9,1.09" -TITLE19="Ensure IAM password policy requires minimum length of 14 or greater (Scored)" -ID110="1.10" -TITLE110="Ensure IAM password policy prevents password reuse: 24 or greater (Scored)" -ID111="1.11" -TITLE111="Ensure IAM password policy expires passwords within 90 days or less (Scored)" -ID112="1.12" -TITLE112="Ensure no root account access key exists (Scored)" -ID113="1.13" -TITLE113="Ensure MFA is enabled for the root account (Scored)" -ID114="1.14" -TITLE114="Ensure hardware MFA is enabled for the root account (Scored)" -ID115="1.15" -TITLE115="Ensure security questions are registered in the AWS account (Not Scored)" -ID116="1.16" -TITLE116="Ensure IAM policies are attached only to groups or roles (Scored)" -ID117="1.17" -TITLE117="Enable detailed billing (Scored)" -ID118="1.18" -TITLE118="Ensure IAM Master and IAM Manager roles are active (Scored)" -ID119="1.19" -TITLE119="Maintain current contact details (Scored)" -ID120="1.20" -TITLE120="Ensure security contact information is registered (Scored)" -ID121="1.21" -TITLE121="Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)" -ID122="1.22" -TITLE122="Ensure a support role has been created to manage incidents with AWS Support (Scored)" -ID123="1.23" -TITLE123="Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)" -ID124="1.24" -TITLE124="Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)" -TITLE2="Logging ***************************************************************" -ID21="2.1,2.01" -TITLE21="Ensure CloudTrail is enabled in all regions (Scored)" -ID22="2.2,2.02" -TITLE22="Ensure CloudTrail log file validation is enabled (Scored)" -ID23="2.3,2.03" -TITLE23="Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)" -ID24="2.4,2.04" -TITLE24="Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)" -ID25="2.5,2.05" -TITLE25="Ensure AWS Config is enabled in all regions (Scored)" -ID26="2.6,2.06" -TITLE26="Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)" -ID27="2.7,2.07" -TITLE27="Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)" -ID28="2.8,2.08" -TITLE28="Ensure rotation for customer created CMKs is enabled (Scored)" -TITLE3="Monitoring ************************************************************" -ID31="3.1,3.01" -TITLE31="Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)" -ID32="3.2,3.02" -TITLE32="Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)" -ID33="3.3,3.03" -TITLE33="Ensure a log metric filter and alarm exist for usage of root account (Scored)" -ID34="3.4,3.04" -TITLE34="Ensure a log metric filter and alarm exist for IAM policy changes (Scored)" -ID35="3.5,3.05" -TITLE35="Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)" -ID36="3.6,3.06" -TITLE36="Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)" -ID37="3.7,3.07" -TITLE37="Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)" -ID38="3.8,3.08" -TITLE38="Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)" -ID39="3.9,3.09" -TITLE39="Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)" -ID310="3.10" -TITLE310="Ensure a log metric filter and alarm exist for security group changes (Scored)" -ID311="3.11" -TITLE311="Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)" -ID312="3.12" -TITLE312="Ensure a log metric filter and alarm exist for changes to network gateways (Scored)" -ID313="3.13" -TITLE313="Ensure a log metric filter and alarm exist for route table changes (Scored)" -ID314="3.14" -TITLE314="Ensure a log metric filter and alarm exist for VPC changes (Scored)" -ID315="3.15" -TITLE315="Ensure appropriate subscribers to each SNS topic (Not Scored)" -TITLE4="Networking ************************************************************" -ID41="4.1,4.01" -TITLE41="Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)" -ID42="4.2,4.02" -TITLE42="Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)" -ID43="4.3,4.03" -TITLE43="Ensure VPC Flow Logging is Enabled in all VPCs (Scored)" -ID44="4.4,4.04" -TITLE44="Ensure the default security group of every VPC restricts all traffic (Scored)" -ID45="4.5,4.05" -TITLE45="Ensure routing tables for VPC peering are \"least access\" (Not Scored)" -TITLE7="Extras ****************************************************************" -ID71="7.1,7.01" -TITLE71="Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark)" -ID72="7.2,7.02" -TITLE72="Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark)" -ID73="7.3,7.03" -TITLE73="Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark)" -ID74="7.4,7.04" -TITLE74="Ensure there are no Security Groups without ingress filtering being used (Not Scored) (Not part of CIS benchmark)" -ID75="7.5,7.05" -TITLE75="Ensure there are no Security Groups not being used (Not Scored) (Not part of CIS benchmark)" -ID76="7.6,7.06" -TITLE76="Ensure there are no EC2 AMIs set as Public (Not Scored) (Not part of CIS benchmark)" -ID77="7.7,7.07" -TITLE77="Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark)" -ID78="7.8,7.08" -TITLE78="Ensure there are no Public Accessible RDS instances (Not Scored) (Not part of CIS benchmark)" -ID79="7.9,7.09" -TITLE79="Check for internet facing Elastic Load Balancers (Not Scored) (Not part of CIS benchmark)" -ID710="7.10,7.10" -TITLE710="Check for internet facing EC2 Instances (Not Scored) (Not part of CIS benchmark)" -ID711="7.11,7.11" -TITLE711="Check for Publicly Accessible Redshift Clusters (Not Scored) (Not part of CIS benchmark)" -ID712="7.12,7.12" -TITLE712="Check if Amazon Macie is enabled (Not Scored) (Not part of CIS benchmark)" -ID713="7.13,7.13" -TITLE713="Check if GuardDuty is enabled (Not Scored) (Not part of CIS benchmark)" -ID714="7.14,7.14" -TITLE714="Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark)" -ID715="7.15,7.15" -TITLE715="Check if Elasticsearch Service domains have logging enabled (Not Scored) (Not part of CIS benchmark)" -ID716="7.16,7.16" -TITLE716="Check if Elasticsearch Service domains allow open access (Not Scored) (Not part of CIS benchmark)" -ID717="7.17,7.17" -TITLE717="Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark)" -ID718="7.18,7.18" -TITLE718="Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark)" -ID719="7.19,7.19" -TITLE719="Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark)" -ID720="7.20,7.20" -TITLE720="Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark)" -ID721="7.21,7.21" -TITLE721="Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark)" -ID722="7.22,7.22" -TITLE722="Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark)" - -printCsvHeader() { - >&2 echo "" - >&2 echo "Generating \"${SEP}\" delimited report on stdout for profile $PROFILE, account $ACCOUNT_NUM" - echo "PROFILE${SEP}ACCOUNT_NUM${SEP}REGION${SEP}TITLE_ID${SEP}RESULT${SEP}SCORED${SEP}LEVEL${SEP}TITLE_TEXT${SEP}NOTES" -} - -prowlerBanner() { - echo -e "$CYAN _" - echo -e " _ __ _ __ _____ _| | ___ _ __" - echo -e " | '_ \| '__/ _ \ \ /\ / / |/ _ \ '__|" - echo -e " | |_) | | | (_) \ V V /| | __/ |" - echo -e " | .__/|_| \___/ \_/\_/ |_|\___|_|" - echo -e " |_|$NORMAL$BLUE CIS based AWS Account Hardening Tool$NORMAL\n" - echo -e "$YELLOW Date: $(date)" -} - -# Get whoami in AWS, who is the user running this shell script -getWhoami(){ - ACCOUNT_NUM=$($AWSCLI sts get-caller-identity --output json $PROFILE_OPT --region $REGION --query "Account" | tr -d '"') - if [[ "$MODE" == "csv" ]]; then - CALLER_ARN_RAW=$($AWSCLI sts get-caller-identity --output json $PROFILE_OPT --region $REGION --query "Arn") - if [[ 255 -eq $? ]]; then - # Failed to get own identity ... exit - echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - EXITCODE=2 - exit $EXITCODE - fi - CALLER_ARN=$(echo $CALLER_ARN_RAW | tr -d '"') - printCsvHeader - textTitle "0.0" "Show report generation info" "NOT_SCORED" "SUPPORT" - textNotice "ARN: $CALLER_ARN TIMESTAMP: $SCRIPT_START_TIME" - else - echo "" - echo "This report is being generated using credentials below:" - echo "" - echo -e "AWS-CLI Profile: $NOTICE[$PROFILE]$NORMAL AWS API Region: $NOTICE[$REGION]$NORMAL AWS Filter Region: $NOTICE[${FILTERREGION:-all}]$NORMAL\n" - if [[ $MONOCHROME -eq 1 ]]; then - echo "Caller Identity:" - $AWSCLI sts get-caller-identity --output text $PROFILE_OPT --region $REGION --query "Arn" - if [[ 255 -eq $? ]]; then - # Failed to get own identity ... exit - echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - exit 2 - fi - echo "" - else - echo "Caller Identity:" - $AWSCLI sts get-caller-identity --output table $PROFILE_OPT --region $REGION - if [[ 255 -eq $? ]]; then - # Failed to get own identity ... exit - echo variable $PROFILE_OPT - echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!" - EXITCODE=2 - exit $EXITCODE - fi - echo "" - fi - fi -} - -printColorsCode(){ - if [[ $MONOCHROME -eq 0 ]]; then - echo -e "\nColors Code for results: $NOTICE INFORMATIVE$NORMAL,$OK OK (RECOMMENDED VALUE)$NORMAL, $BAD WARNING (FIX REQUIRED)$NORMAL \n" - fi -} - -# Generate Credential Report -genCredReport() { - textTitle "0.1" "Generating AWS IAM Credential Report..." "NOT_SCORED" "SUPPORT" - until $( $AWSCLI iam generate-credential-report --output text --query 'State' $PROFILE_OPT --region $REGION |grep -q -m 1 "COMPLETE") ; do - sleep 1 - done -} - -# Save report to a file, decode it, deletion at finish and after every single check -saveReport(){ - $AWSCLI iam get-credential-report --query 'Content' --output text $PROFILE_OPT --region $REGION | decode_report > $TEMP_REPORT_FILE - if [[ $KEEPCREDREPORT -eq 1 ]]; then - textTitle "0.2" "Saving IAM Credential Report ..." "NOT_SCORED" "SUPPORT" - textNotice "IAM Credential Report saved in $TEMP_REPORT_FILE" - fi -} - -# Delete temporary report file -cleanTemp(){ - if [[ $KEEPCREDREPORT -ne 1 ]]; then - rm -fr $TEMP_REPORT_FILE - fi -} - -# Delete the temporary report file if we get interrupted/terminated -trap cleanTemp EXIT - -# 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) - -infoReferenceLong(){ - # Report review note: - echo -e "" - echo -e "For more information on the Prowler, feedback and issue reporting:" - echo -e "https://github.com/Alfresco/prowler" - echo -e "" - echo -e "For more information on the CIS benchmark:" - echo -e "https://benchmarks.cisecurity.org/tools2/amazon/CIS_Amazon_Web_Services_Foundations_Benchmark_v1.1.0.pdf" - -} - -check11(){ - # "Avoid the use of the root account (Scored)." - COMMAND11=$(cat $TEMP_REPORT_FILE| grep '' | cut -d, -f5,11,16 | sed 's/,/\ /g') - textTitle "$ID11" "$TITLE11" "SCORED" "LEVEL1" - textNotice "Root account last accessed (password key_1 key_2): $COMMAND11" -} - -check12(){ - # "Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)" - # List users with password enabled - COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }') - COMMAND12=$( - for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do - cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$8 }' |grep "$i " |grep false | awk '{ print $1 }' - done) - textTitle "$ID12" "$TITLE12" "SCORED" "LEVEL1" - if [[ $COMMAND12 ]]; then - for u in $COMMAND12; do - textWarn "User $u has Password enabled but MFA disabled" - done - else - textOK "No users found with Password enabled and MFA disabled" - fi -} - -check13(){ - # "Ensure credentials unused for 90 days or greater are disabled (Scored)" - textTitle "$ID13" "$TITLE13" "SCORED" "LEVEL1" - COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }') - if [[ $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED ]]; then - COMMAND13=$( - for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do - cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$5 }' |grep $i| awk '{ print $1 }'|tr '\n' ' '; - done) - # list of users that have used password - USERS_PASSWORD_USED=$($AWSCLI iam list-users --query "Users[?PasswordLastUsed].UserName" --output text $PROFILE_OPT --region $REGION) - if [[ $USERS_PASSWORD_USED ]]; then - # look for users with a password last used more or equal to 90 days - for i in $USERS_PASSWORD_USED; do - DATEUSED=$($AWSCLI iam list-users --query "Users[?UserName=='$i'].PasswordLastUsed" --output text $PROFILE_OPT --region $REGION | cut -d'T' -f1) - HOWOLDER=$(how_older_from_today $DATEUSED) - if [ $HOWOLDER -gt "90" ];then - textWarn "User \"$i\" has not logged in during the last 90 days " - else - textOK "User \"$i\" found with credentials used in the last 90 days" - fi - done - fi - else - textOK "No users found with password enabled" - fi - -} - -check14(){ - # "Ensure access keys are rotated every 90 days or less (Scored)" # also checked by Security Monkey - LIST_OF_USERS_WITH_ACCESS_KEY1=$(cat $TEMP_REPORT_FILE| awk -F, '{ print $1, $9 }' |grep "\ true" | awk '{ print $1 }') - LIST_OF_USERS_WITH_ACCESS_KEY2=$(cat $TEMP_REPORT_FILE| awk -F, '{ print $1, $14 }' |grep "\ true" | awk '{ print $1 }') - textTitle "$ID14" "$TITLE14" "SCORED" "LEVEL1" - C14_NUM_USERS1=0 - C14_NUM_USERS2=0 - if [[ $LIST_OF_USERS_WITH_ACCESS_KEY1 ]]; then - # textWarn "Users with access key 1 older than 90 days:" - for user in $LIST_OF_USERS_WITH_ACCESS_KEY1; do - # check access key 1 - DATEROTATED1=$(cat $TEMP_REPORT_FILE | grep -v user_creation_time | grep "^${user},"| awk -F, '{ print $10 }' | grep -v "N/A" | awk -F"T" '{ print $1 }') - HOWOLDER=$(how_older_from_today $DATEROTATED1) - - if [ $HOWOLDER -gt "90" ];then - textWarn " $user has not rotated access key1 in over 90 days " - C14_NUM_USERS1=$(expr $C14_NUM_USERS1 + 1) - fi - done - if [[ $C14_NUM_USERS1 -eq 0 ]]; then - textOK "No users with access key 1 older than 90 days." - fi - else - textOK "No users with access key 1." - fi - - if [[ $LIST_OF_USERS_WITH_ACCESS_KEY2 ]]; then - # textWarn "Users with access key 2 older than 90 days:" - for user in $LIST_OF_USERS_WITH_ACCESS_KEY2; do - # check access key 2 - DATEROTATED2=$(cat $TEMP_REPORT_FILE | grep -v user_creation_time | grep "^${user},"| awk -F, '{ print $10 }' | grep -v "N/A" | awk -F"T" '{ print $1 }') - HOWOLDER=$(how_older_from_today $DATEROTATED2) - if [ $HOWOLDER -gt "90" ];then - textWarn " $user has not rotated access key2. " - C14_NUM_USERS2=$(expr $C14_NUM_USERS2 + 1) - fi - done - if [[ $C14_NUM_USERS2 -eq 0 ]]; then - textOK "No users with access key 2 older than 90 days." - fi - else - textOK "No users with access key 2." - fi -} - -check15(){ - # "Ensure IAM password policy requires at least one uppercase letter (Scored)" - COMMAND15=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireUppercaseCharacters' 2> /dev/null) # must be true - textTitle "$ID15" "$TITLE15" "SCORED" "LEVEL1" - if [[ "$COMMAND15" == "true" ]];then - textOK "Password Policy requires upper case" - else - textWarn "Password Policy missing upper-case requirement" - fi -} - -check16(){ - # "Ensure IAM password policy require at least one lowercase letter (Scored)" - COMMAND16=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireLowercaseCharacters' 2> /dev/null) # must be true - textTitle "$ID16" "$TITLE16" "SCORED" "LEVEL1" - if [[ "$COMMAND16" == "true" ]];then - textOK "Password Policy requires lower case" - else - textWarn "Password Policy missing lower-case requirement" - fi -} - -check17(){ - # "Ensure IAM password policy require at least one symbol (Scored)" - COMMAND17=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireSymbols' 2> /dev/null) # must be true - textTitle "$ID17" "$TITLE17" "SCORED" "LEVEL1" - if [[ "$COMMAND17" == "true" ]];then - textOK "Password Policy requires symbol" - else - textWarn "Password Policy missing symbol requirement" - fi -} - -check18(){ - # "Ensure IAM password policy require at least one number (Scored)" - COMMAND18=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireNumbers' 2> /dev/null) # must be true - textTitle "$ID18" "$TITLE18" "SCORED" "LEVEL1" - if [[ "$COMMAND18" == "true" ]];then - textOK "Password Policy requires number" - else - textWarn "Password Policy missing number requirement" - fi -} - -check19(){ - # "Ensure IAM password policy requires minimum length of 14 or greater (Scored)" - COMMAND19=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.MinimumPasswordLength' 2> /dev/null) - textTitle "$ID19" "$TITLE19" "SCORED" "LEVEL1" - if [[ $COMMAND19 -gt "13" ]];then - textOK "Password Policy requires more than 13 characters" - else - textWarn "Password Policy missing or weak length requirement" - fi -} - -check110(){ - # "Ensure IAM password policy prevents password reuse: 24 or greater (Scored)" - COMMAND110=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --query 'PasswordPolicy.PasswordReusePrevention' --output text 2> /dev/null) - textTitle "$ID110" "$TITLE110" "SCORED" "LEVEL1" - if [[ $COMMAND110 ]];then - if [[ $COMMAND110 -gt "23" ]];then - textOK "Password Policy limits reuse" - else - textWarn "Password Policy has weak reuse requirement (lower than 24)" - fi - else - textWarn "Password Policy missing reuse requirement" - fi -} - -check111(){ - # "Ensure IAM password policy expires passwords within 90 days or less (Scored)" - COMMAND111=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json | grep MaxPasswordAge | awk -F: '{ print $2 }'|sed 's/\ //g'|sed 's/,/ /g' 2> /dev/null) - textTitle "$ID111" "$TITLE111" "SCORED" "LEVEL1" - if [[ $COMMAND111 ]];then - if [ "$COMMAND111" == "90" ];then - textOK "Password Policy includes expiration" - fi - else - textWarn "Password expiration not set or set greater than 90 days " - fi -} - -check112(){ - # "Ensure no root account access key exists (Scored)" - # ensure the access_key_1_active and access_key_2_active fields are set to FALSE. - ROOTKEY1=$(cat $TEMP_REPORT_FILE |grep root_account|awk -F',' '{ print $9 }') - ROOTKEY2=$(cat $TEMP_REPORT_FILE |grep root_account|awk -F',' '{ print $14 }') - textTitle "$ID112" "$TITLE112" "SCORED" "LEVEL1" - if [ "$ROOTKEY1" == "false" ];then - textOK "No access key 1 found for root" - else - textWarn "Found access key 1 for root " - fi - if [ "$ROOTKEY2" == "false" ];then - textOK "No access key 2 found for root" - else - textWarn "Found access key 2 for root " - fi -} - -check113(){ - # "Ensure MFA is enabled for the root account (Scored)" - COMMAND113=$($AWSCLI iam get-account-summary $PROFILE_OPT --region $REGION --output json --query 'SummaryMap.AccountMFAEnabled') - textTitle "$ID113" "$TITLE113" "SCORED" "LEVEL1" - if [ "$COMMAND113" == "1" ]; then - textOK "Virtual MFA is enabled for root" - else - textWarn "MFA is not ENABLED for root account " - fi -} - -check114(){ - # "Ensure hardware MFA is enabled for the root account (Scored)" - COMMAND113=$($AWSCLI iam get-account-summary $PROFILE_OPT --region $REGION --output json --query 'SummaryMap.AccountMFAEnabled') - textTitle "$ID114" "$TITLE114" "SCORED" "LEVEL1" - if [ "$COMMAND113" == "1" ]; then - COMMAND114=$($AWSCLI iam list-virtual-mfa-devices $PROFILE_OPT --region $REGION --output text --assignment-status Assigned --query 'VirtualMFADevices[*].[SerialNumber]' | grep '^arn:aws:iam::[0-9]\{12\}:mfa/root-account-mfa-device$') - if [[ "$COMMAND114" ]]; then - textWarn "Only Virtual MFA is enabled for root" - else - textOK "Hardware MFA is enabled for root " - fi - else - textWarn "MFA is not ENABLED for root account " - fi -} - -check115(){ - # "Ensure security questions are registered in the AWS account (Not Scored)" - textTitle "$ID115" "$TITLE115" "NOT_SCORED" "LEVEL2" - textNotice "No command available for check 1.15 " - textNotice "Login to the AWS Console as root & click on the Account " - textNotice "Name -> My Account -> Configure Security Challenge Questions " -} - -check116(){ - # "Ensure IAM policies are attached only to groups or roles (Scored)" - textTitle "$ID116" "$TITLE116" "SCORED" "LEVEL1" - LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text $PROFILE_OPT --region $REGION) - C116_NUM_USERS=0 - for user in $LIST_USERS;do - USER_POLICY=$($AWSCLI iam list-attached-user-policies --output text $PROFILE_OPT --region $REGION --user-name $user) - if [[ $USER_POLICY ]]; then - textWarn "$user has policy directly attached " - C116_NUM_USERS=$(expr $C116_NUM_USERS + 1) - fi - done - if [[ $C116_NUM_USERS -eq 0 ]]; then - textOK "No policies attached to users." - fi -} - -check117(){ - # "Enable detailed billing (Scored)" - # No command available - textTitle "$ID117" "$TITLE117" "SCORED" "LEVEL1" - textNotice "No command available for check 1.17 " - textNotice "See section 1.17 on the CIS Benchmark guide for details " -} - -check118(){ - # "Ensure IAM Master and IAM Manager roles are active (Scored)" - textTitle "$ID118" "$TITLE118" "SCORED" "LEVEL1" - FINDMASTERANDMANAGER=$($AWSCLI iam list-roles $PROFILE_OPT --region $REGION --query "Roles[*].{RoleName:RoleName}" --output text | grep -E 'Master|Manager'| tr '\n' ' ') - if [[ $FINDMASTERANDMANAGER ]];then - textNotice "Found next roles as possible IAM Master and IAM Manager candidates: " - textNotice "$FINDMASTERANDMANAGER " - textNotice "run the commands below to check their policies with section 1.18 in the guide..." - for role in $FINDMASTERANDMANAGER;do - # find inline policies in found roles - INLINEPOLICIES=$($AWSCLI iam list-role-policies --role-name $role $PROFILE_OPT --region $REGION --query "PolicyNames[*]" --output text) - for policy in $INLINEPOLICIES;do - textNotice "INLINE: $AWSCLI iam get-role-policy --role-name $role --policy-name $policy $PROFILE_OPT --region $REGION --output json" - done - # find attached policies in found roles - ATTACHEDPOLICIES=$($AWSCLI iam list-attached-role-policies --role-name $role $PROFILE_OPT --region $REGION --query "AttachedPolicies[*]" --output text) - for policy in $ATTACHEDPOLICIES;do - textNotice "ATTACHED: $AWSCLI iam get-role-policy --role-name $role --policy-name $policy $PROFILE_OPT --region $REGION --output json" - done - done - else - textWarn "IAM Master and IAM Manager roles not found" - fi -} - -check119(){ - # "Maintain current contact details (Scored)" - # No command available - textTitle "$ID119" "$TITLE119" "SCORED" "LEVEL1" - textNotice "No command available for check 1.19 " - textNotice "See section 1.19 on the CIS Benchmark guide for details " -} - -check120(){ - # "Ensure security contact information is registered (Scored)" - # No command available - textTitle "$ID120" "$TITLE120" "SCORED" "LEVEL1" - textNotice "No command available for check 1.20 " - textNotice "See section 1.20 on the CIS Benchmark guide for details " -} - -check121(){ - # "Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)" - textTitle "$ID121" "$TITLE121" "NOT_SCORED" "LEVEL2" - textNotice "No command available for check 1.21 " - textNotice "See section 1.21 on the CIS Benchmark guide for details " -} - -check122(){ - # "Ensure a support role has been created to manage incidents with AWS Support (Scored)" - textTitle "$ID122" "$TITLE122" "SCORED" "LEVEL1" - SUPPORTPOLICYARN=$($AWSCLI iam list-policies --query "Policies[?PolicyName == 'AWSSupportAccess'].Arn" $PROFILE_OPT --region $REGION --output text) - if [[ $SUPPORTPOLICYARN ]];then - for policyarn in $SUPPORTPOLICYARN;do - POLICYUSERS=$($AWSCLI iam list-entities-for-policy --policy-arn $SUPPORTPOLICYARN $PROFILE_OPT --region $REGION --output json) - if [[ $POLICYUSERS ]];then - textOK "Support Policy attached to $policyarn" - for user in $(echo "$POLICYUSERS" | grep UserName | cut -d'"' -f4) ; do - textNotice "User $user has support access via $policyarn" - done - # textNotice "Make sure your team can create a Support case with AWS " - else - textWarn "Support Policy not applied to any Group / User / Role " - fi - done - else - textWarn "No Support Policy found" - fi -} - -check123(){ - # "Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)" - textTitle "$ID123" "$TITLE123" "NOT_SCORED" "LEVEL1" - LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text $PROFILE_OPT --region $REGION) - # List of USERS with KEY1 last_used_date as N/A - LIST_USERS_KEY1_NA=$(for user in $LIST_USERS; do grep "^${user}," $TEMP_REPORT_FILE|awk -F, '{ print $1,$11 }'|grep N/A |awk '{ print $1 }'; done) - LIST_USERS_KEY1_ACTIVE=$(for user in $LIST_USERS_KEY1_NA; do grep "^${user}," $TEMP_REPORT_FILE|awk -F, '{ print $1,$9 }'|grep "true$"|awk '{ print $1 }'|sed 's/[[:blank:]]+/,/g' ; done) - if [[ $LIST_USERS_KEY1_ACTIVE ]]; then - for user in $LIST_USERS_KEY1_ACTIVE; do - textNotice "$user has never used Access Key 1" - done - else - textOK "No users found with Access Key 1 never used" - fi - # List of USERS with KEY2 last_used_date as N/A - LIST_USERS_KEY2_NA=$(for user in $LIST_USERS; do grep "^${user}," $TEMP_REPORT_FILE|awk -F, '{ print $1,$16 }'|grep N/A |awk '{ print $1 }' ; done) - LIST_USERS_KEY2_ACTIVE=$(for user in $LIST_USERS_KEY2_NA; do grep "^${user}," $TEMP_REPORT_FILE|awk -F, '{ print $1,$14 }'|grep "true$" |awk '{ print $1 }' ; done) - if [[ $LIST_USERS_KEY2_ACTIVE ]]; then - for user in $LIST_USERS_KEY2_ACTIVE; do - textNotice "$user has never used Access Key 2" - done - else - textOK "No users found with Access Key 2 never used" - fi -} - -check124(){ - # "Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)" - textTitle "$ID124" "$TITLE124" "SCORED" "LEVEL1" - LIST_CUSTOM_POLICIES=$($AWSCLI iam list-policies --output text $PROFILE_OPT --region $REGION|grep 'arn:aws:iam::[0-9]\{12\}:'|awk '{ print $2 }') - if [[ $LIST_CUSTOM_POLICIES ]]; then - textNotice "Looking for custom policies: (skipping default policies - it may take few seconds...)" - for policy in $LIST_CUSTOM_POLICIES; do - POLICY_VERSION=$($AWSCLI iam list-policies $PROFILE_OPT --region $REGION --query 'Policies[*].[Arn,DefaultVersionId]' --output text |awk "\$1 == \"$policy\" { print \$2 }") - POLICY_WITH_FULL=$($AWSCLI iam get-policy-version --output text --policy-arn $policy --version-id $POLICY_VERSION --query "PolicyVersion.Document.Statement[?Effect == 'Allow' && contains(Resource, '*') && contains (Action, '*')]" $PROFILE_OPT --region $REGION) - if [[ $POLICY_WITH_FULL ]]; then - POLICIES_ALLOW_LIST="$POLICIES_ALLOW_LIST $policy" - fi - done - if [[ $POLICIES_ALLOW_LIST ]]; then - textNotice "List of custom policies: " - for policy in $POLICIES_ALLOW_LIST; do - textNotice "Policy $policy allows \"*:*\"" - done - else - textOK "No custom policy found that allow full \"*:*\" administrative privileges" - fi - else - textOK "No custom policies found" - fi -} - -check21(){ - # "Ensure CloudTrail is enabled in all regions (Scored)" - textTitle "$ID21" "$TITLE21" "SCORED" "LEVEL1" - LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].Name' --output text) - if [[ $LIST_OF_TRAILS ]];then - for trail in $LIST_OF_TRAILS;do - MULTIREGION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].IsMultiRegionTrail' --output text --trail-name-list $trail) - if [[ "$MULTIREGION_TRAIL_STATUS" == 'False' ]];then - textWarn "$trail trail in $REGION is not enabled in multi region mode" - else - textOK "$trail trail in $REGION is enabled for all regions" - fi - done - else - textWarn "No CloudTrail trails found!" - fi -} - -check22(){ - # "Ensure CloudTrail log file validation is enabled (Scored)" - textTitle "$ID22" "$TITLE22" "SCORED" "LEVEL2" - LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].Name' --output text) - if [[ $LIST_OF_TRAILS ]];then - for trail in $LIST_OF_TRAILS;do - LOGFILEVALIDATION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].LogFileValidationEnabled' --output text --trail-name-list $trail) - if [[ "$LOGFILEVALIDATION_TRAIL_STATUS" == 'False' ]];then - textWarn "$trail trail in $REGION has not log file validation enabled" - else - textOK "$trail trail in $REGION has log file validation enabled" - fi - done - else - textWarn "No CloudTrail trails found!" - fi -} - -check23(){ - # "Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)" - textTitle "$ID23" "$TITLE23" "SCORED" "LEVEL1" - CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text $PROFILE_OPT --region $REGION) - if [[ $CLOUDTRAILBUCKET ]];then - for bucket in $CLOUDTRAILBUCKET;do - CLOUDTRAILBUCKET_HASALLPERMISIONS=$($AWSCLI s3api get-bucket-acl --bucket $bucket --query 'Grants[?Grantee.URI==`http://acs.amazonaws.com/groups/global/AllUsers`]' $PROFILE_OPT --region $REGION --output text) - if [[ $CLOUDTRAILBUCKET_HASALLPERMISIONS ]];then - textWarn "check your $bucket CloudTrail bucket ACL and Policy!" - else - textOK "Bucket $bucket is set correctly" - fi - done - else - textWarn "No CloudTrail bucket found!" - fi -} - -check24(){ - # "Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)" - textTitle "$ID24" "$TITLE24" "SCORED" "LEVEL1" - TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].{Name:Name, HomeRegion:HomeRegion}' --output text | tr "\t" ',') - if [[ $TRAILS_AND_REGIONS ]];then - for reg_trail in $TRAILS_AND_REGIONS;do - trail=$(echo $reg_trail | cut -d',' -f2) - TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1) - LATESTDELIVERY_TIMESTAMP=$($AWSCLI cloudtrail get-trail-status --name $trail $PROFILE_OPT --region $TRAIL_REGION --query 'LatestCloudWatchLogsDeliveryTime' --output text|grep -v None) - if [[ ! $LATESTDELIVERY_TIMESTAMP ]];then - textWarn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)" - else - LATESTDELIVERY_DATE=$(timestamp_to_date $LATESTDELIVERY_TIMESTAMP) - HOWOLDER=$(how_older_from_today $LATESTDELIVERY_DATE) - if [ $HOWOLDER -gt "1" ];then - textWarn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)" - else - textOK "$trail trail has been logging during the last 24h (it is in $TRAIL_REGION)" - fi - fi - done - else - textWarn "No CloudTrail trails found!" - fi -} - -check25(){ - # "Ensure AWS Config is enabled in all regions (Scored)" - textTitle "$ID25" "$TITLE25" "SCORED" "LEVEL1" - for regx in $REGIONS; do - CHECK_AWSCONFIG_STATUS=$($AWSCLI configservice get-status $PROFILE_OPT --region $regx --output json| grep "recorder: ON") - if [[ $CHECK_AWSCONFIG_STATUS ]];then - textOK "Region $regx has AWS Config recorder: ON" "$regx" - else - textWarn "Region $regx has AWS Config disabled or not configured" "$regx" - fi - done -} - -check26(){ - # "Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)" - textTitle "$ID26" "$TITLE26" "SCORED" "LEVEL1" - CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text $PROFILE_OPT --region $REGION) - if [[ $CLOUDTRAILBUCKET ]];then - for bucket in $CLOUDTRAILBUCKET;do - CLOUDTRAILBUCKET_LOGENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket $PROFILE_OPT --region $REGION --query 'LoggingEnabled.TargetBucket' --output text|grep -v None) - if [[ $CLOUDTRAILBUCKET_LOGENABLED ]];then - textOK "Bucket access logging enabled in $bucket" - else - textWarn "access logging is not enabled in $bucket CloudTrail S3 bucket!" - fi - done - else - textWarn "CloudTrail bucket not found!" - fi -} - -check27(){ - # "Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)" - textTitle "$ID27" "$TITLE27" "SCORED" "LEVEL2" - CLOUDTRAILNAME=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].Name' --output text $PROFILE_OPT --region $REGION) - if [[ $CLOUDTRAILNAME ]];then - for trail in $CLOUDTRAILNAME;do - CLOUDTRAILENC_ENABLED=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --trail $trail --query 'trailList[*].KmsKeyId' --output text) - if [[ $CLOUDTRAILENC_ENABLED ]];then - textOK "KMS key found for $trail" - else - textWarn "encryption is not enabled in your CloudTrail trail $trail (KMS key not found)!" - fi - done - else - textWarn "CloudTrail bucket doesn't exist!" - fi -} - -check28(){ - # "Ensure rotation for customer created CMKs is enabled (Scored)" - textTitle "$ID28" "$TITLE28" "SCORED" "LEVEL2" - for regx in $REGIONS; do - CHECK_KMS_KEYLIST=$($AWSCLI kms list-keys $PROFILE_OPT --region $regx --output text --query 'Keys[*].KeyId') - if [[ $CHECK_KMS_KEYLIST ]];then - CHECK_KMS_KEYLIST_NO_DEFAULT=$(for key in $CHECK_KMS_KEYLIST ; do $AWSCLI kms describe-key --key-id $key $PROFILE_OPT --region $regx --output text --query 'KeyMetadata.[KeyId, KeyManager]'|grep -v 'AWS'|awk '{ print $1 }'; done) - for key in $CHECK_KMS_KEYLIST_NO_DEFAULT; do - CHECK_KMS_KEY_TYPE=$($AWSCLI kms describe-key --key-id $key $PROFILE_OPT --region $regx --query 'KeyMetadata.Origin' | sed 's/["]//g') - if [[ "$CHECK_KMS_KEY_TYPE" == "EXTERNAL" ]];then - textOK "Key $key in Region $regx Customer Uploaded Key Material." "$regx" - else - CHECK_KMS_KEY_ROTATION=$($AWSCLI kms get-key-rotation-status --key-id $key $PROFILE_OPT --region $regx --output text) - if [[ "$CHECK_KMS_KEY_ROTATION" == "True" ]];then - textOK "Key $key in Region $regx is set correctly" - else - textWarn "Key $key in Region $regx is not set to rotate!!!" "$regx" - fi - fi - done - - else - textNotice "Region $regx doesn't have encryption keys" "$regx" - fi - done -} - -check31(){ - # "Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)" - textTitle "$ID31" "$TITLE31" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text| tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | grep $group | awk -F: '{ print $4 }') - #METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | awk '/UnauthorizedOperation/ || /AccessDenied/ {print $3}') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --output text | grep METRICFILTERS | awk 'BEGIN {IGNORECASE=1}; /UnauthorizedOperation/ || /AccessDenied/ {print $3};') - if [[ $METRICFILTER_SET ]];then - for metric in $METRICFILTER_SET; do - metric_name=$($AWSCLI logs describe-metric-filters $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --log-group-name $group --filter-name-prefix $metric --output text --query 'metricFilters[0].metricTransformations[0].metricName') - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[?MetricName==`'$metric_name'`]' --output text) - if [[ $HAS_ALARM_ASSOCIATED ]];then - CHECK31OK="$CHECK31OK $group:$metric" - else - CHECK31WARN="$CHECK31WARN $group:$metric" - fi - done - else - CHECK31WARN="$CHECK31WARN $group" - fi - done - - if [[ $CHECK31OK ]]; then - for group in $CHECK31OK; do - metric=${group#*:} - group=${group%:*} - textOK "CloudWatch group $group found with metric filter $metric and alarms set for Unauthorized Operation and Access Denied" - done - fi - if [[ $CHECK31WARN ]]; then - for group in $CHECK31WARN; do - case $group in - *:*) metric=${group#*:} - group=${group%:*} - textWarn "CloudWatch group $group found with metric filter $metric but no alarms associated" - ;; - *) textWarn "CloudWatch group $group found but no metric filters or alarms associated" - esac - done - fi - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check32(){ - # "Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)" - textTitle "$ID32" "$TITLE32" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' |grep filterPattern|grep MFAUsed| awk '/ConsoleLogin/ && (/additionalEventData.MFAUsed.*\!=.*\"Yes/) {print $1}') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /ConsoleLogin/ || /MFAUsed/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms set for sign-in Console without MFA enabled" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check33(){ - # "Ensure a log metric filter and alarm exist for usage of root account (Scored)" - textTitle "$ID33" "$TITLE33" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION |grep -E 'userIdentity.*Root.*AwsServiceEvent') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /userIdentity/ || /Root/ || /AwsServiceEvent/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms set for usage of root account" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check34(){ - # "Ensure a log metric filter and alarm exist for IAM policy changes (Scored)" - textTitle "$ID34" "$TITLE34" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'DeleteGroupPolicy.*DeleteRolePolicy.*DeleteUserPolicy.*PutGroupPolicy.*PutRolePolicy.*PutUserPolicy.*CreatePolicy.*DeletePolicy.*CreatePolicyVersion.*DeletePolicyVersion.*AttachRolePolicy.*DetachRolePolicy.*AttachUserPolicy.*DetachUserPolicy.*AttachGroupPolicy.*DetachGroupPolicy') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /DeletePolicy/ || /DeletePolicies/ || /Policies/ || /Policy/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for IAM policy changes" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check35(){ - # "Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)" - textTitle "$ID35" "$TITLE35" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateTrail.*UpdateTrail.*DeleteTrail.*StartLogging.*StopLogging') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /TrailChange/ || /Trails/ || /CreateTrail/ || /UpdateTrail/ || /DeleteTrail/ || /StartLogging/ || /StopLogging/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for CloudTrail configuration changes" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check36(){ - # "Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)" - textTitle "$ID36" "$TITLE36" "SCORED" "LEVEL2" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'ConsoleLogin.*Failed') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /FailedLogin/ || /ConsoleLogin/ || /Failed/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for AWS Management Console authentication failures" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check37(){ - # "Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)" - textTitle "$ID37" "$TITLE37" "SCORED" "LEVEL2" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'kms.amazonaws.com.*DisableKey.*ScheduleKeyDeletion') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /DisableKey/ || /ScheduleKeyDeletion/ || /kms/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for changes of customer created CMKs" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check38(){ - # "Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)" - textTitle "$ID38" "$TITLE38" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 's3.amazonaws.com.*PutBucketAcl.*PutBucketPolicy.*PutBucketCors.*PutBucketLifecycle.*PutBucketReplication.*DeleteBucketPolicy.*DeleteBucketCors.*DeleteBucketLifecycle.*DeleteBucketReplication') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /S3/ || /BucketPolicy/ || /BucketPolicies/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for S3 bucket policy changes" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check39(){ - # "Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)" - textTitle "$ID39" "$TITLE39" "SCORED" "LEVEL2" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'config.amazonaws.com.*StopConfigurationRecorder.*DeleteDeliveryChannel.*PutDeliveryChannel.*PutConfigurationRecorder') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /config/ || /ConfigurationRecorder/ || /DeliveryChannel/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for AWS Config configuration changes" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check310(){ - # "Ensure a log metric filter and alarm exist for security group changes (Scored)" - textTitle "$ID310" "$TITLE310" "SCORED" "LEVEL2" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'AuthorizeSecurityGroupIngress.*AuthorizeSecurityGroupEgress.*RevokeSecurityGroupIngress.*RevokeSecurityGroupEgress.*CreateSecurityGroup.*DeleteSecurityGroup') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /SecurityGroup/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for security group changes" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check311(){ - # "Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)" - textTitle "$ID311" "$TITLE311" "SCORED" "LEVEL2" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateNetworkAcl.*CreateNetworkAclEntry.*DeleteNetworkAcl.*DeleteNetworkAclEntry.*ReplaceNetworkAclEntry.*ReplaceNetworkAclAssociation') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /NetworkAcl/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for changes to NACLs" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check312(){ - # "Ensure a log metric filter and alarm exist for changes to network gateways (Scored)" - textTitle "$ID312" "$TITLE312" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateCustomerGateway.*DeleteCustomerGateway.*AttachInternetGateway.*CreateInternetGateway.*DeleteInternetGateway.*DetachInternetGateway') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /InternetGateway/ || /CustomerGateway/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for changes to network gateways" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check313(){ - # "Ensure a log metric filter and alarm exist for route table changes (Scored)" - textTitle "$ID313" "$TITLE313" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateRoute.*CreateRouteTable.*ReplaceRoute.*ReplaceRouteTableAssociation.*DeleteRouteTable.*DeleteRoute.*DisassociateRouteTable') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /Route/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for route table changes" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check314(){ - # "Ensure a log metric filter and alarm exist for VPC changes (Scored)" - textTitle "$ID314" "$TITLE314" "SCORED" "LEVEL1" - CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }') - if [[ $CLOUDWATCH_GROUP ]];then - for group in $CLOUDWATCH_GROUP; do - CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }') - METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateVpc.*DeleteVpc.*ModifyVpcAttribute.*AcceptVpcPeeringConnection.*CreateVpcPeeringConnection.*DeleteVpcPeeringConnection.*RejectVpcPeeringConnection.*AttachClassicLinkVpc.*DetachClassicLinkVpc.*DisableVpcClassicLink.*EnableVpcClassicLink') - if [[ $METRICFILTER_SET ]];then - HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /VPC/;') - if [[ $HAS_ALARM_ASSOCIATED ]];then - textOK "CloudWatch group $group found with metric filters and alarms for VPC changes" - else - textWarn "CloudWatch group $group found with metric filters but no alarms associated" - fi - else - textWarn "CloudWatch group $group found but no metric filters or alarms associated" - fi - done - else - textWarn "No CloudWatch group found for CloudTrail events" - fi -} - -check315(){ - # "Ensure appropriate subscribers to each SNS topic (Not Scored)" - textTitle "$ID315" "$TITLE315" "NOT_SCORED" "LEVEL1" - CAN_SNS_LIST_SUBS=1 - for regx in $REGIONS; do - TOPICS_LIST=$($AWSCLI sns list-topics $PROFILE_OPT --region $regx --output text --query 'Topics[*].TopicArn') - ntopics=$(echo $TOPICS_LIST | wc -w ) - if [[ $TOPICS_LIST && $CAN_SNS_LIST_SUBS -eq 1 ]];then - textNotice "Region $regx has $ntopics topics" "$regx" - for topic in $TOPICS_LIST; do - TOPIC_SHORT=$(echo $topic | awk -F: '{ print $6 }') - CHECK_TOPIC_LIST=$($AWSCLI sns list-subscriptions-by-topic --topic-arn $topic $PROFILE_OPT --region $regx --query 'Subscriptions[*].{Endpoint:Endpoint,Protocol:Protocol}' --output text --max-items $MAXITEMS 2> /dev/null) - if [[ $? -eq 255 ]]; then - # Permission error - export CAN_SNS_LIST_SUBS=0 - ntopics=$(echo $TOPICS_LIST | wc -w ) - textNotice "Region $regx / $ntopics Topics / Subscriptions NO_PERMISSION" "$regx" - break; - fi - if [[ "Z" != "Z${CHECK_TOPIC_LIST}" ]]; then - printf '%s\n' "$CHECK_TOPIC_LIST" | while IFS= read -r dest ; do - textNotice "Region $regx / Topic $TOPIC_SHORT / Subscription $dest" "$regx" - done - else - textWarn "Region $regx / Topic $TOPIC_SHORT / Subscription NONE" "$regx" - fi - done - elif [[ $CAN_SNS_LIST_SUBS -eq 0 ]]; then - textNotice "Region $regx has $ntopics topics - unable to list subscribers" "$regx" - # break - else - textOK "Region $regx has 0 topics" "$regx" - fi - done -} - -check41(){ - # "Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)" - textTitle "$ID41" "$TITLE41" "SCORED" "LEVEL1" - for regx in $REGIONS; do - SG_LIST=$($AWSCLI ec2 describe-security-groups --query 'SecurityGroups[?length(IpPermissions[?((FromPort==null && ToPort==null) || (FromPort<=`22` && ToPort>=`22`)) && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupId:GroupId}' $PROFILE_OPT --region $regx --output text) - if [[ $SG_LIST ]];then - for SG in $SG_LIST;do - textWarn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx" "$regx" - done - else - textOK "No Security Groups found in $regx with port 22 TCP open to 0.0.0.0/0" "$regx" - fi - done -} - -check42(){ - # "Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)" - textTitle "$ID42" "$TITLE42" "SCORED" "LEVEL1" - for regx in $REGIONS; do - SG_LIST=$($AWSCLI ec2 describe-security-groups --query 'SecurityGroups[?length(IpPermissions[?((FromPort==null && ToPort==null) || (FromPort<=`3389` && ToPort>=`3389`)) && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupId:GroupId}' $PROFILE_OPT --region $regx --output text) - if [[ $SG_LIST ]];then - for SG in $SG_LIST;do - textWarn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx" "$regx" - done - else - textOK "No Security Groups found in $regx with port 3389 TCP open to 0.0.0.0/0" "$regx" - fi - done -} - -check43(){ - # "Ensure VPC Flow Logging is Enabled in all VPCs (Scored)" - textTitle "$ID43" "$TITLE43" "SCORED" "LEVEL2" - for regx in $REGIONS; do - CHECK_FL=$($AWSCLI ec2 describe-flow-logs $PROFILE_OPT --region $regx --query 'FlowLogs[?FlowLogStatus==`ACTIVE`].LogGroupName' --output text) - if [[ $CHECK_FL ]];then - for FL in $CHECK_FL;do - textOK "VPCFlowLog is enabled for LogGroupName: $FL in Region $regx" "$regx" - done - else - textWarn "No VPCFlowLog has been found in Region $regx" "$regx" - fi - done -} - -check44(){ - # "Ensure the default security group of every VPC restricts all traffic (Scored)" - textTitle "$ID44" "$TITLE44" "SCORED" "LEVEL2" - for regx in $REGIONS; do - CHECK_SGDEFAULT=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --filters Name=group-name,Values='default' --query 'SecurityGroups[*].{IpPermissions:IpPermissions,IpPermissionsEgress:IpPermissionsEgress,GroupId:GroupId}' --output text |grep 0.0.0.0) - if [[ $CHECK_SGDEFAULT ]];then - textWarn "Default Security Groups found that allow 0.0.0.0 IN or OUT traffic in Region $regx" "$regx" - else - textOK "No Default Security Groups open to 0.0.0.0 found in Region $regx" "$regx" - fi - done -} - -check45(){ - # "Ensure routing tables for VPC peering are \"least access\" (Not Scored)" - textTitle "$ID45" "$TITLE45" "NOT_SCORED" "LEVEL2" - textNotice "Looking for VPC peering in all regions... " - for regx in $REGIONS; do - LIST_OF_VPCS_PEERING_CONNECTIONS=$($AWSCLI ec2 describe-vpc-peering-connections --output text $PROFILE_OPT --region $regx --query 'VpcPeeringConnections[*].VpcPeeringConnectionId') - if [[ $LIST_OF_VPCS_PEERING_CONNECTIONS ]];then - textNotice "$regx: $LIST_OF_VPCS_PEERING_CONNECTIONS - review routing tables" "$regx" - #LIST_OF_VPCS=$($AWSCLI ec2 describe-vpcs $PROFILE_OPT --region $regx --query 'Vpcs[*].VpcId' --output text) - #aws ec2 describe-route-tables --filter "Name=vpc-id,Values=vpc-0213e864" --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" $PROFILE_OPT --region $regx - # for vpc in $LIST_OF_VPCS; do - # VPCS_WITH_PEERING=$($AWSCLI ec2 describe-route-tables --filter "Name=vpc-id,Values=$vpc" $PROFILE_OPT --region $regx --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" |grep GatewayId|grep pcx-) - # done - #echo $VPCS_WITH_PEERING - else - textOK "$regx: No VPC peering found" "$regx" - fi - done -} - -extra71(){ - # "Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID71" "$TITLE71" "NOT_SCORED" "EXTRA" - - ADMIN_GROUPS='' - AWS_GROUPS=$($AWSCLI $PROFILE_OPT iam list-groups --output text --query 'Groups[].GroupName') - for grp in $AWS_GROUPS; do - # aws --profile onlinetraining iam list-attached-group-policies --group-name Administrators --query 'AttachedPolicies[].PolicyArn' | grep 'arn:aws:iam::aws:policy/AdministratorAccess' - # list-attached-group-policies - CHECK_ADMIN_GROUP=$($AWSCLI $PROFILE_OPT iam list-attached-group-policies --group-name $grp --output json --query 'AttachedPolicies[].PolicyArn' | grep 'arn:aws:iam::aws:policy/AdministratorAccess') - if [[ $CHECK_ADMIN_GROUP ]]; then - ADMIN_GROUPS="$ADMIN_GROUPS $grp" - textNotice "$grp group provides administrative access" - ADMIN_USERS=$($AWSCLI $PROFILE_OPT iam get-group --group-name $grp --output json --query 'Users[].UserName' | grep '"' | cut -d'"' -f2 ) - for auser in $ADMIN_USERS; do - # users in group are Administrators - # users - # check for user MFA device in credential report - USER_MFA_ENABLED=$( cat $TEMP_REPORT_FILE | grep "^$auser," | cut -d',' -f8) - if [[ "true" == $USER_MFA_ENABLED ]]; then - textOK "$auser / MFA Enabled / admin via group $grp" - else - textWarn "$auser / MFA DISABLED / admin via group $grp" - fi - done - else - textNotice "$grp group provides non-administrative access" - fi - done - # set +x -} - -extra72(){ - # "Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID72" "$TITLE72" "NOT_SCORED" "EXTRA" - textNotice "Looking for EBS Snapshots in all regions... " - for regx in $REGIONS; do - LIST_OF_EBS_SNAPSHOTS=$($AWSCLI ec2 describe-snapshots $PROFILE_OPT --region $regx --owner-ids $ACCOUNT_NUM --output text --query 'Snapshots[*].{ID:SnapshotId}' --max-items $MAXITEMS | grep -v None 2> /dev/null) - for snapshot in $LIST_OF_EBS_SNAPSHOTS; do - SNAPSHOT_IS_PUBLIC=$($AWSCLI ec2 describe-snapshot-attribute $PROFILE_OPT --region $regx --output text --snapshot-id $snapshot --attribute createVolumePermission --query "CreateVolumePermissions[?Group=='all']") - if [[ $SNAPSHOT_IS_PUBLIC ]];then - textWarn "$regx: $snapshot is currently Public!" "$regx" - else - textOK "$regx: $snapshot is not Public" "$regx" - fi - done - done - -} - -extra73(){ - # "Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID73" "$TITLE73" "NOT_SCORED" "EXTRA" - textNotice "Looking for open S3 Buckets (ACLs and Policies) in all regions... " - ALL_BUCKETS_LIST=$($AWSCLI s3api list-buckets --query 'Buckets[*].{Name:Name}' $PROFILE_OPT --region $REGION --output text) - for bucket in $ALL_BUCKETS_LIST; do - BUCKET_LOCATION=$($AWSCLI s3api get-bucket-location --bucket $bucket $PROFILE_OPT --region $REGION --output text) - if [[ "None" == $BUCKET_LOCATION ]]; then - BUCKET_LOCATION="us-east-1" - fi - if [[ "EU" == $BUCKET_LOCATION ]]; then - BUCKET_LOCATION="eu-west-1" - fi - # check if AllUsers is in the ACL as Grantee - CHECK_BUCKET_ALLUSERS_ACL=$($AWSCLI s3api get-bucket-acl $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --query "Grants[?Grantee.URI == 'http://acs.amazonaws.com/groups/global/AllUsers']" --output text |grep -v GRANTEE) - CHECK_BUCKET_ALLUSERS_ACL_SINGLE_LINE=$(echo -ne $CHECK_BUCKET_ALLUSERS_ACL) - # check if AuthenticatedUsers is in the ACL as Grantee, they will have access with sigened URL only - CHECK_BUCKET_AUTHUSERS_ACL=$($AWSCLI s3api get-bucket-acl $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --query "Grants[?Grantee.URI == 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers']" --output text |grep -v GRANTEE) - CHECK_BUCKET_AUTHUSERS_ACL_SINGLE_LINE=$(echo -ne $CHECK_BUCKET_AUTHUSERS_ACL) - # to prevent error NoSuchBucketPolicy first clean the output controlling stderr - TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-${bucket}.policy.XXXXXXXXXX) - $AWSCLI s3api get-bucket-policy $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --output text --query Policy > $TEMP_POLICY_FILE 2> /dev/null - # check if the S3 policy has Principal as * - CHECK_BUCKET_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | sed -e 's/[{}]/''/g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}'|awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep ^\"Principal|grep \*) - if [[ $CHECK_BUCKET_ALLUSERS_ACL || $CHECK_BUCKET_AUTHUSERS_ACL || $CHECK_BUCKET_ALLUSERS_POLICY ]];then - if [[ $CHECK_BUCKET_ALLUSERS_ACL ]];then - textWarn "$BUCKET_LOCATION: $bucket bucket is open to the Internet (Everyone) with permissions: $CHECK_BUCKET_ALLUSERS_ACL_SINGLE_LINE" "$regx" - fi - if [[ $CHECK_BUCKET_AUTHUSERS_ACL ]];then - textWarn "$BUCKET_LOCATION: $bucket bucket is open to Authenticated users (Any AWS user) with permissions: $CHECK_BUCKET_AUTHUSERS_ACL_SINGLE_LINE" "$regx" - fi - if [[ $CHECK_BUCKET_ALLUSERS_POLICY ]];then - textWarn "$BUCKET_LOCATION: $bucket bucket policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx" - fi - else - textOK "$BUCKET_LOCATION: $bucket bucket is not open" "$regx" - fi - rm -fr $TEMP_POLICY_FILE - done -} - -extra74(){ - # "Ensure there are no Security Groups without ingress filtering being used (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID74" "$TITLE74" "NOT_SCORED" "EXTRA" - textNotice "Looking for Security Groups in all regions... " - for regx in $REGIONS; do - LIST_OF_SECURITYGROUPS=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --filters "Name=ip-permission.cidr,Values=0.0.0.0/0" --query "SecurityGroups[].[GroupId]" --output text --max-items $MAXITEMS) - for SG_ID in $LIST_OF_SECURITYGROUPS; do - SG_NO_INGRESS_FILTER=$($AWSCLI ec2 describe-network-interfaces $PROFILE_OPT --region $regx --filters "Name=group-id,Values=$SG_ID" --query "length(NetworkInterfaces)" --output text) - if [[ $SG_NO_INGRESS_FILTER -ne 0 ]];then - textWarn "$regx: $SG_ID has not ingress filtering and it is being used!" "$regx" - else - textNotice "$regx: $SG_ID has not ingress filtering but it is no being used" "$regx" - fi - done - done - -} - -extra75(){ - # "Ensure there are no Security Groups not being used (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID75" "$TITLE75" "NOT_SCORED" "EXTRA" - textNotice "Looking for Security Groups in all regions... " - for regx in $REGIONS; do - LIST_OF_SECURITYGROUPS=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --query "SecurityGroups[].[GroupId]" --output text --max-items $MAXITEMS) - for SG_ID in $LIST_OF_SECURITYGROUPS; do - SG_NOT_USED=$($AWSCLI ec2 describe-network-interfaces $PROFILE_OPT --region $regx --filters "Name=group-id,Values=$SG_ID" --query "length(NetworkInterfaces)" --output text) - if [[ $SG_NOT_USED -eq 0 ]];then - textWarn "$regx: $SG_ID is not being used!" "$regx" - else - textOK "$regx: $SG_ID is being used" "$regx" - fi - done - done -} - -extra76(){ - # "Ensure there are no EC2 AMIs set as Public (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID76" "$TITLE76" "NOT_SCORED" "EXTRA" - textNotice "Looking for AMIs in all regions... " - for regx in $REGIONS; do - LIST_OF_PUBLIC_AMIS=$($AWSCLI ec2 describe-images --owners self $PROFILE_OPT --region $regx --filters "Name=is-public,Values=true" --query 'Images[*].{ID:ImageId}' --output text) - if [[ $LIST_OF_PUBLIC_AMIS ]];then - for ami in $LIST_OF_PUBLIC_AMIS; do - textWarn "$regx: $ami is currently Public!" "$regx" - done - else - textOK "$regx: No Public AMIs found" "$regx" - fi - done -} - -extra77(){ - # "Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID77" "$TITLE77" "NOT_SCORED" "EXTRA" - textNotice "Looking for ECR repos in all regions... " - for regx in $REGIONS; do - LIST_OF_ECR_REPOS=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $regx --query 'repositories[*].{Name:repositoryName}' --output text) - for ecr_repo in $LIST_OF_ECR_REPOS; do - TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-ecr-repo.policy.XXXXXXXXXX) - $AWSCLI ecr get-repository-policy --repository-name $ecr_repo $PROFILE_OPT --region $regx --output text > $TEMP_POLICY_FILE 2> /dev/null - # check if the policy has Principal as * - CHECK_ECR_REPO_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep \"Principal|grep \*) - if [[ $CHECK_ECR_REPO_ALLUSERS_POLICY ]];then - textWarn "$regx: $ecr_repo policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx" - else - textOK "$regx: $ecr_repo is not open" "$regx" - fi - done - rm -fr $TEMP_POLICY_FILE - done -} - -extra78(){ - # "Ensure there are no Public Accessible RDS instances (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID78" "$TITLE78" "NOT_SCORED" "EXTRA" - textNotice "Looking for RDS instances in all regions... " - for regx in $REGIONS; do - LIST_OF_RDS_PUBLIC_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[?PubliclyAccessible==`true`].[DBInstanceIdentifier,Endpoint.Address]' --output text) - if [[ $LIST_OF_RDS_PUBLIC_INSTANCES ]];then - while read -r rds_instance;do - RDS_NAME=$(echo $rds_instance | awk '{ print $1; }') - RDS_DNSNAME=$(echo $rds_instance | awk '{ print $2; }') - textWarn "$regx: RDS instance: $RDS_NAME at $RDS_DNSNAME is set as Publicly Accessible!" "$regx" - done <<< "$LIST_OF_RDS_PUBLIC_INSTANCES" - else - textOK "$regx: no Publicly Accessible RDS instances found" "$regx" - fi - done -} - -extra79(){ - # "Check for internet facing Elastic Load Balancers (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID79" "$TITLE79" "NOT_SCORED" "EXTRA" - textNotice "Looking for Elastic Load Balancers in all regions... " - for regx in $REGIONS; do - LIST_OF_PUBLIC_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[?Scheme == `internet-facing`].[LoadBalancerName,DNSName]' --output text) - LIST_OF_PUBLIC_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[?Scheme == `internet-facing`].[LoadBalancerName,DNSName]' --output text) - LIST_OF_ALL_ELBS=$( echo $LIST_OF_PUBLIC_ELBS; echo $LIST_OF_PUBLIC_ELBSV2) - LIST_OF_ALL_ELBS_PER_LINE=$( echo $LIST_OF_ALL_ELBS| xargs -n2 ) - if [[ $LIST_OF_ALL_ELBS ]];then - while read -r elb;do - ELB_NAME=$(echo $elb | awk '{ print $1; }') - ELB_DNSNAME=$(echo $elb | awk '{ print $2; }') - textWarn "$regx: ELB: $ELB_NAME at DNS: $ELB_DNSNAME is internet-facing!" "$regx" - done <<< "$LIST_OF_ALL_ELBS_PER_LINE" - else - textOK "$regx: no Internet Facing ELBs found" "$regx" - fi - done -} - -extra710(){ - # "Check for internet facing EC2 Instances (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID710" "$TITLE710" "NOT_SCORED" "EXTRA" - textNotice "Looking for instances in all regions... " - for regx in $REGIONS; do - LIST_OF_PUBLIC_INSTANCES=$($AWSCLI ec2 describe-instances $PROFILE_OPT --region $regx --query 'Reservations[*].Instances[?PublicIpAddress].[InstanceId,PublicIpAddress]' --output text) - if [[ $LIST_OF_PUBLIC_INSTANCES ]];then - while read -r instance;do - INSTANCE_ID=$(echo $instance | awk '{ print $1; }') - PUBLIC_IP=$(echo $instance | awk '{ print $2; }') - textWarn "$regx: Instance: $INSTANCE_ID at IP: $PUBLIC_IP is internet-facing!" "$regx" - done <<< "$LIST_OF_PUBLIC_INSTANCES" - else - textOK "$regx: no Internet Facing EC2 Instances found" "$regx" - fi - done -} - -extra711(){ - # "Check for Publicly Accessible Redshift Clusters (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID711" "$TITLE711" "NOT_SCORED" "EXTRA" - textNotice "Looking for Reshift clusters in all regions... " - for regx in $REGIONS; do - LIST_OF_PUBLIC_REDSHIFT_CLUSTERS=$($AWSCLI redshift describe-clusters $PROFILE_OPT --region $regx --query 'Clusters[?PubliclyAccessible == `true`].[ClusterIdentifier,Endpoint.Address]' --output text) - if [[ $LIST_OF_PUBLIC_REDSHIFT_CLUSTERS ]];then - while read -r cluster;do - CLUSTER_ID=$(echo $cluster | awk '{ print $1; }') - CLUSTER_ENDPOINT=$(echo $cluster | awk '{ print $2; }') - textWarn "$regx: Cluster: $CLUSTER_ID at Endpoint: $CLUSTER_ENDPOINT is publicly accessible!" "$regx" - done <<< "$LIST_OF_PUBLIC_REDSHIFT_CLUSTERS" - else - textOK "$regx: no Publicly Accessible Redshift Clusters found" "$regx" - fi - done -} - -extra712(){ - # "Check if Amazon Macie is enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID712" "$TITLE712" "NOT_SCORED" "EXTRA" - textNotice "No API commands available to check if Macie is enabled," - textNotice "just looking if IAM Macie related permissions exist. " - MACIE_IAM_ROLES_CREATED=$($AWSCLI iam list-roles $PROFILE_OPT --query 'Roles[*].Arn'|grep AWSMacieServiceCustomer|wc -l) - if [[ $MACIE_IAM_ROLES_CREATED -eq 2 ]];then - textOK "Macie related IAM roles exist, so it might be enabled. Check it out manually." - else - textWarn "No Macie related IAM roles found. It is most likely not to be enabled" - fi -} - -extra713(){ - # "Check if GuardDuty is enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID713" "$TITLE713" "NOT_SCORED" "EXTRA" - for regx in $REGIONS; do - LIST_OF_GUARDDUTY_DETECTORS=$($AWSCLI guardduty list-detectors $PROFILE_OPT --region $regx --output text |cut -f2) - if [[ $LIST_OF_GUARDDUTY_DETECTORS ]];then - while read -r detector;do - DETECTOR_ENABLED=$($AWSCLI guardduty get-detector --detector-id $detector $PROFILE_OPT --region $regx --output text| cut -f3|grep ENABLED) - if [[ $DETECTOR_ENABLED ]]; then - textOK "$regx: GuardDuty detector $detector enabled" "$regx" - else - textWarn "$regx: GuardDuty detector $detector configured but suspended" "$regx" - fi - done <<< "$LIST_OF_GUARDDUTY_DETECTORS" - else - textWarn "$regx: GuardDuty detector not configured!" "$regx" - fi - done -} - -extra714(){ - # "Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID714" "$TITLE714" "NOT_SCORED" "EXTRA" - for regx in $REGIONS; do - LIST_OF_DISTRIBUTIONS=$($AWSCLI cloudfront list-distributions $PROFILE_OPT --region $regx --query 'DistributionList.Items[].Id' --output text |grep -v "^None") - if [[ $LIST_OF_DISTRIBUTIONS ]]; then - for cdn in $LIST_OF_DISTRIBUTIONS;do - CDN_LOG_ENABLED=$($AWSCLI cloudfront get-distribution $PROFILE_OPT --region $regx --id "$cdn" --query 'Distribution.DistributionConfig.Logging.Enabled' | grep true) - if [[ $CDN_LOG_ENABLED ]];then - textOK "$regx: CDN $cdn logging enabled" "$regx" - else - textWarn "$regx: CDN $cdn logging disabled!" "$regx" - fi - done - else - textNotice "$regx: No CDN configured" "$regx" - fi - done -} - -extra715(){ - # "Check if Elasticsearch Service domains have logging enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID715" "$TITLE715" "NOT_SCORED" "EXTRA" - for regx in $REGIONS; do - LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query DomainNames --output text) - if [[ $LIST_OF_DOMAINS ]]; then - for domain in $LIST_OF_DOMAINS;do - SEARCH_SLOWLOG_ENABLED=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.LogPublishingOptions.Options.SEARCH_SLOW_LOGS.Enabled --output text |grep -v ^None|grep -v ^False) - if [[ $SEARCH_SLOWLOG_ENABLED ]];then - textOK "$regx: ElasticSearch Service domain $domain SEARCH_SLOW_LOGS enabled" "$regx" - else - textWarn "$regx: ElasticSearch Service domain $domain SEARCH_SLOW_LOGS disabled!" "$regx" - fi - INDEX_SLOWLOG_ENABLED=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.LogPublishingOptions.Options.INDEX_SLOW_LOGS.Enabled --output text |grep -v ^None|grep -v ^False) - if [[ $INDEX_SLOWLOG_ENABLED ]];then - textOK "$regx: ElasticSearch Service domain $domain INDEX_SLOW_LOGS enabled" "$regx" - else - textWarn "$regx: ElasticSearch Service domain $domain INDEX_SLOW_LOGS disabled!" "$regx" - fi - done - else - textNotice "$regx: No Elasticsearch Service domain found" "$regx" - fi - done -} - -extra716(){ - # "Check if Elasticsearch Service domains allow open access (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID716" "$TITLE716" "NOT_SCORED" "EXTRA" - for regx in $REGIONS; do - LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query DomainNames --output text) - if [[ $LIST_OF_DOMAINS ]]; then - for domain in $LIST_OF_DOMAINS;do - CHECK_IF_MEMBER_OF_VPC=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.VPCOptions.Options.VPCId --output text|grep -v ^None) - if [[ ! $CHECK_IF_MEMBER_OF_VPC ]];then - TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-es-domain.policy.XXXXXXXXXX) - $AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.AccessPolicies.Options --output text > $TEMP_POLICY_FILE 2> /dev/null - # check if the policy has Principal as * - CHECK_ES_DOMAIN_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep \"Principal|grep \*) - if [[ $CHECK_ES_DOMAIN_ALLUSERS_POLICY ]];then - textWarn "$regx: $domain policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx" - else - textOK "$regx: $domain is not open" "$regx" - fi - else - textOK "$regx: $domain is in a VPC" "$regx" - fi - done - fi - textNotice "$regx: No Elasticsearch Service domain found" "$regx" - rm -fr $TEMP_POLICY_FILE - done -} - -extra717(){ - # "Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID717" "$TITLE717" "NOT_SCORED" "EXTRA" - for regx in $REGIONS; do - LIST_OF_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[*].LoadBalancerName' --output text|xargs -n1) - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[*].LoadBalancerArn' --output text|xargs -n1) - if [[ $LIST_OF_ELBS || $LIST_OF_ELBSV2 ]]; then - if [[ $LIST_OF_ELBS ]]; then - for elb in $LIST_OF_ELBS; do - CHECK_ELBS_LOG_ENABLED=$($AWSCLI elb describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-name $elb --query 'LoadBalancerAttributes.AccessLog.Enabled'|grep "^true") - if [[ $CHECK_ELBS_LOG_ENABLED ]]; then - textOK "$regx: $elb has access logs to S3 configured" "$regx" - else - textWarn "$regx: $elb has not configured access logs" "$regx" - fi - done - fi - if [[ $LIST_OF_ELBSV2 ]]; then - for elbarn in $LIST_OF_ELBSV2; do - CHECK_ELBSV2_LOG_ENABLED=$($AWSCLI elbv2 describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-arn $elbarn --query Attributes[*] --output text|grep "^access_logs.s3.enabled"|cut -f2|grep true) - ELBV2_NAME=$(echo $elbarn|cut -d\/ -f3) - if [[ $CHECK_ELBSV2_LOG_ENABLED ]]; then - textOK "$regx: $ELBV2_NAME has access logs to S3 configured" "$regx" - else - textWarn "$regx: $ELBV2_NAME has not configured access logs" "$regx" - fi - done - fi - else - textNotice "$regx: No ELBs found" "$regx" - fi - done -} - -extra718(){ - # "Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID718" "$TITLE718" "NOT_SCORED" "EXTRA" - LIST_OF_BUCKETS=$($AWSCLI s3api list-buckets $PROFILE_OPT --query Buckets[*].Name --output text|xargs -n1) - if [[ $LIST_OF_BUCKETS ]]; then - for bucket in $LIST_OF_BUCKETS;do - BUCKET_SERVER_LOG_ENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket $PROFILE_OPT --query [LoggingEnabled] --output text|grep -v "^None$") - if [[ $BUCKET_SERVER_LOG_ENABLED ]];then - textOK "Bucket $bucket has server access logging enabled" - else - textWarn "Bucket $bucket has server access logging disabled!" - fi - done - else - textNotice "No S3 Buckets found" - fi -} - -extra719(){ - # "Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID719" "$TITLE719" "NOT_SCORED" "EXTRA" - LIST_OF_HOSTED_ZONES=$($AWSCLI route53 list-hosted-zones $PROFILE_OPT --query HostedZones[*].Id --output text|xargs -n1) - if [[ $LIST_OF_HOSTED_ZONES ]]; then - for hostedzoneid in $LIST_OF_HOSTED_ZONES;do - HOSTED_ZONE_QUERY_LOG_ENABLED=$($AWSCLI route53 list-query-logging-configs --hosted-zone-id $hostedzoneid $PROFILE_OPT --query QueryLoggingConfigs[*].CloudWatchLogsLogGroupArn --output text|cut -d: -f7) - if [[ $HOSTED_ZONE_QUERY_LOG_ENABLED ]];then - textOK "Route53 hosted zone Id $hostedzoneid has query logging enabled in Log Group $HOSTED_ZONE_QUERY_LOG_ENABLED" - else - textWarn "Route53 hosted zone Id $hostedzoneid has query logging disabled!" - fi - done - else - textNotice "No Route53 hosted zones found" - fi -} - -extra720(){ - # "Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID720" "$TITLE720" "NOT_SCORED" "EXTRA" - for regx in $REGIONS; do - LIST_OF_FUNCTIONS=$($AWSCLI lambda list-functions $PROFILE_OPT --region $regx --query Functions[*].FunctionName --output text) - if [[ $LIST_OF_FUNCTIONS ]]; then - for lambdafunction in $LIST_OF_FUNCTIONS;do - LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query trailList[?HomeRegion==\`$regx\`].Name --output text) - if [[ $LIST_OF_TRAILS ]]; then - for trail in $LIST_OF_TRAILS; do - FUNCTION_ENABLED_IN_TRAIL=$($AWSCLI cloudtrail get-event-selectors $PROFILE_OPT --trail-name $trail --region $regx --query "EventSelectors[*].DataResources[?Type == \`AWS::Lambda::Function\`].Values" --output text |xargs -n1| grep -E "^arn:aws:lambda.*function:$lambdafunction$") - if [[ $FUNCTION_ENABLED_IN_TRAIL ]]; then - textOK "$regx: Lambda function $lambdafunction enabled in trail $trail" "$regx" - else - textWarn "$regx: Lambda function $lambdafunction NOT enabled in trail $trail" "$regx" - fi - done - # LIST_OF_MULTIREGION_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query "trailList[?IsMultiRegionTrail == \`true\`].Name" --output text) - # if [[ $LIST_OF_MULTIREGION_TRAILS ]]; then - # for trail in $LIST_OF_MULTIREGION_TRAILS; do - # REGION_OF_TRAIL=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query "trailList[?IsMultiRegionTrail == \`true\` && Name == \`$trail\` ].HomeRegion" --output text) - # FUNCTION_ENABLED_IN_THIS_REGION=$($AWSCLI cloudtrail get-event-selectors $PROFILE_OPT --trail-name $trail --region $REGION_OF_TRAIL --query "EventSelectors[*].DataResources[?Type == \`AWS::Lambda::Function\`].Values" --output text |xargs -n1| grep -E "^arn:aws:lambda.*function:$lambdafunction$") - # if [[ $FUNCTION_ENABLED_IN_THIS_REGION ]]; then - # textOK "$regx: Lambda function $lambdafunction enabled in trail $trail" "$regx" - # else - # textWarn "$regx: Lambda function $lambdafunction NOT enabled in trail $trail" "$regx" - # fi - # done - # else - # textWarn "$regx: Lambda function $lambdafunction is not being recorded!" "$regx" - # fi - else - textWarn "$regx: Lambda function $lambdafunction is not being recorded no CloudTrail found!" "$regx" - fi - done - else - textNotice "$regx: No Lambda functions found" "$regx" - fi - done -} - -extra721(){ - # "Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID721" "$TITLE721" "NOT_SCORED" "EXTRA" - for regx in $REGIONS; do - LIST_OF_REDSHIFT_CLUSTERS=$($AWSCLI redshift describe-clusters $PROFILE_OPT --region $regx --query 'Clusters[*].ClusterIdentifier' --output text) - if [[ $LIST_OF_REDSHIFT_CLUSTERS ]]; then - for redshiftcluster in $LIST_OF_REDSHIFT_CLUSTERS;do - REDSHIFT_LOG_ENABLED=$($AWSCLI redshift describe-logging-status $PROFILE_OPT --region $regx --cluster-identifier $redshiftcluster --query LoggingEnabled --output text | grep True) - if [[ $REDSHIFT_LOG_ENABLED ]];then - REDSHIFT_LOG_ENABLED_BUCKET=$($AWSCLI redshift describe-logging-status $PROFILE_OPT --region $regx --cluster-identifier $redshiftcluster --query BucketName --output text) - textOK "$regx: Redshift cluster $redshiftcluster has audit logging enabled to bucket $REDSHIFT_LOG_ENABLED_BUCKET" "$regx" - else - textWarn "$regx: Redshift cluster $redshiftcluster logging disabled!" "$regx" - fi - done - else - textNotice "$regx: No Redshift cluster configured" "$regx" - fi - done -} - -extra722(){ - # "Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark)" - textTitle "$ID722" "$TITLE722" "NOT_SCORED" "EXTRA" - for regx in $REGIONS; do - LIST_OF_API_GW=$($AWSCLI apigateway get-rest-apis $PROFILE_OPT --region $regx --query items[*].id --output text) - if [[ $LIST_OF_API_GW ]];then - for apigwid in $LIST_OF_API_GW;do - API_GW_NAME=$($AWSCLI apigateway get-rest-apis $PROFILE_OPT --region $regx --query "items[?id==\`$apigwid\`].name" --output text) - CHECK_STAGES_NAME=$($AWSCLI apigateway get-stages $PROFILE_OPT --region $regx --rest-api-id $apigwid --query "item[*].stageName" --output text) - if [[ $CHECK_STAGES_NAME ]];then - for stagname in $CHECK_STAGES_NAME;do - CHECK_STAGE_METHOD_LOGGING=$($AWSCLI apigateway get-stages $PROFILE_OPT --region $regx --rest-api-id $apigwid --query "item[?stageName == \`$stagname\` ].methodSettings" --output text|awk '{ print $1" log level "$6}') - if [[ $CHECK_STAGE_METHOD_LOGGING ]];then - textOK "$regx: API Gateway $API_GW_NAME has stage logging enabled for $CHECK_STAGE_METHOD_LOGGING" "$regx" - else - textWarn "$regx: API Gateway $API_GW_NAME logging disabled for stage $stagname!" "$regx" - fi - done - else - textWarn "$regx: No Stage name found for $API_GW_NAME" "$regx" - fi - done - else - textNotice "$regx: No API Gateway found" "$regx" - fi - done -} - -callCheck(){ - if [[ $CHECKNUMBER ]];then - case "$CHECKNUMBER" in - check11|check101 ) check11;; - check12|check102 ) check12;; - check13|check103 ) check13;; - check14|check104 ) check14;; - check15|check105 ) check15;; - check16|check106 ) check16;; - check17|check107 ) check17;; - check18|check108 ) check18;; - check19|check109 ) check19;; - check110 ) check110;; - check111 ) check111;; - check112 ) check112;; - check113 ) check113;; - check114 ) check114;; - check115 ) check115;; - check116 ) check116;; - check117 ) check117;; - check118 ) check118;; - check119 ) check119;; - check120 ) check120;; - check121 ) check121;; - check122 ) check122;; - check123 ) check123;; - check124 ) check124;; - check21|check201 ) check21;; - check22|check202 ) check22;; - check23|check203 ) check23;; - check24|check204 ) check24;; - check25|check205 ) check25;; - check26|check206 ) check26;; - check27|check207 ) check27;; - check28|check208 ) check28;; - check31|check301 ) check31;; - check32|check302 ) check32;; - check33|check303 ) check33;; - check34|check304 ) check34;; - check35|check305 ) check35;; - check36|check306 ) check36;; - check37|check307 ) check37;; - check38|check308 ) check38;; - check39|check309 ) check39;; - check310 ) check310;; - check311 ) check311;; - check312 ) check312;; - check313 ) check313;; - check314 ) check314;; - check315 ) check315;; - check41|check401 ) check41;; - check42|check402 ) check42;; - check43|check403 ) check43;; - check44|check404 ) check44;; - check45|check405 ) check45;; - extra71|extra701 ) extra71;; - extra72|extra702 ) extra72;; - extra73|extra703 ) extra73;; - extra74|extra704 ) extra74;; - extra75|extra705 ) extra75;; - extra76|extra706 ) extra76;; - extra77|extra707 ) extra77;; - extra78|extra708 ) extra78;; - extra79|extra709 ) extra79;; - extra710 ) extra710;; - extra711 ) extra711;; - extra712 ) extra712;; - extra713 ) extra713;; - extra714 ) extra714;; - extra715 ) extra715;; - extra716 ) extra716;; - extra717 ) extra717;; - extra718 ) extra718;; - extra719 ) extra719;; - extra720 ) extra720;; - extra721 ) extra721;; - extra722 ) extra722;; - - - ## Groups of Checks - check1 ) - check11;check12;check13;check14;check15;check16;check17;check18; - check19;check110;check111;check112;check113;check114;check115; - check116;check117;check118;check119;check120;check121;check122; - check123;check124; - ;; - check2 ) - check21;check22;check23;check24;check25;check26;check27;check28 - ;; - check3 ) - check31;check32;check33;check34;check35;check36;check37;check38; - check39;check310;check311;check312;check313;check314;check315 - ;; - check4 ) - check41;check42;check43;check44;check45 - ;; - level1 ) - check11;check12;check13;check14;check15;check16;check17;check18; - check19;check110;check111;check112;check113;check115;check116;check117; - check118;check119;check120;check122;check123;check124;check21;check23; - check24;check25;check26;check31;check32;check33;check34;check35; - check38;check312;check313;check314;check315;check41;check42 - ;; - level2 ) - check11;check12;check13;check14;check15;check16;check17;check18; - check19;check110;check111;check112;check113;check114;check115;check116; - check117;check118;check119;check120;check121;check122;check123;check124; - check21;check22;check23;check24;check25;check26;check27;check28;check31; - check32;check33;check34;check35;check36;check37;check38;check39; - check310;check311;check312;check313;check314;check315;check41;check42; - check43;check44;check45 - ;; - extras ) - extra71;extra72;extra73;extra74;extra75;extra76;extra77;extra78; - extra79;extra710;extra711;extra712;extra713;extra714;extra715;extra716; - extra717;extra718;extra719;extra720;extra721;extra722 - ;; - forensics-ready ) - check21;check22;check23;check24;check25;check26;check27; - check43; - extra712;extra713;extra714;extra715;extra717;extra718;extra719; - extra720;extra721;extra722 - ;; - * ) - 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 - textTitle "1" "$TITLE1" "NOT_SCORED" "SUPPORT" - textTitle "$ID11" "$TITLE11" "SCORED" "LEVEL1" - textTitle "$ID12" "$TITLE12" "SCORED" "LEVEL1" - textTitle "$ID13" "$TITLE13" "SCORED" "LEVEL1" - textTitle "$ID14" "$TITLE14" "SCORED" "LEVEL1" - textTitle "$ID15" "$TITLE15" "SCORED" "LEVEL1" - textTitle "$ID16" "$TITLE16" "SCORED" "LEVEL1" - textTitle "$ID17" "$TITLE17" "SCORED" "LEVEL1" - textTitle "$ID18" "$TITLE18" "SCORED" "LEVEL1" - textTitle "$ID19" "$TITLE19" "SCORED" "LEVEL1" - textTitle "$ID110" "$TITLE110" "SCORED" "LEVEL1" - textTitle "$ID111" "$TITLE111" "SCORED" "LEVEL1" - textTitle "$ID112" "$TITLE112" "SCORED" "LEVEL1" - textTitle "$ID113" "$TITLE113" "SCORED" "LEVEL1" - textTitle "$ID114" "$TITLE114" "SCORED" "LEVEL1" - textTitle "$ID115" "$TITLE115" "NOT_SCORED" "LEVEL2" - textTitle "$ID116" "$TITLE116" "SCORED" "LEVEL1" - textTitle "$ID117" "$TITLE117" "SCORED" "LEVEL1" - textTitle "$ID118" "$TITLE118" "SCORED" "LEVEL1" - textTitle "$ID119" "$TITLE119" "SCORED" "LEVEL1" - textTitle "$ID120" "$TITLE120" "SCORED" "LEVEL1" - textTitle "$ID121" "$TITLE121" "NOT_SCORED" "LEVEL2" - textTitle "$ID122" "$TITLE122" "SCORED" "LEVEL1" - textTitle "$ID123" "$TITLE123" "NOT_SCORED" "LEVEL1" - textTitle "$ID124" "$TITLE124" "SCORED" "LEVEL1" - textTitle "2" "$TITLE2" "NOT_SCORED" "SUPPORT" - textTitle "$ID21" "$TITLE21" "SCORED" "LEVEL1" - textTitle "$ID22" "$TITLE22" "SCORED" "LEVEL2" - textTitle "$ID23" "$TITLE23" "SCORED" "LEVEL1" - textTitle "$ID24" "$TITLE24" "SCORED" "LEVEL1" - textTitle "$ID25" "$TITLE25" "SCORED" "LEVEL1" - textTitle "$ID26" "$TITLE26" "SCORED" "LEVEL1" - textTitle "$ID27" "$TITLE27" "SCORED" "LEVEL2" - textTitle "$ID28" "$TITLE28" "SCORED" "LEVEL2" - textTitle "3" "$TITLE3" "NOT_SCORED" "SUPPORT" - textTitle "$ID31" "$TITLE31" "SCORED" "LEVEL1" - textTitle "$ID32" "$TITLE32" "SCORED" "LEVEL1" - textTitle "$ID33" "$TITLE33" "SCORED" "LEVEL1" - textTitle "$ID34" "$TITLE34" "SCORED" "LEVEL1" - textTitle "$ID35" "$TITLE35" "SCORED" "LEVEL1" - textTitle "$ID36" "$TITLE36" "SCORED" "LEVEL2" - textTitle "$ID37" "$TITLE37" "SCORED" "LEVEL2" - textTitle "$ID38" "$TITLE38" "SCORED" "LEVEL1" - textTitle "$ID39" "$TITLE39" "SCORED" "LEVEL2" - textTitle "$ID310" "$TITLE310" "SCORED" "LEVEL2" - textTitle "$ID311" "$TITLE311" "SCORED" "LEVEL2" - textTitle "$ID312" "$TITLE312" "SCORED" "LEVEL1" - textTitle "$ID313" "$TITLE313" "SCORED" "LEVEL1" - textTitle "$ID314" "$TITLE314" "SCORED" "LEVEL1" - textTitle "$ID315" "$TITLE315" "NOT_SCORED" "LEVEL1" - textTitle "4" "$TITLE4" "NOT_SCORED" "SUPPORT" - textTitle "$ID41" "$TITLE41" "SCORED" "LEVEL1" - textTitle "$ID42" "$TITLE42" "SCORED" "LEVEL1" - textTitle "$ID43" "$TITLE43" "SCORED" "LEVEL2" - textTitle "$ID44" "$TITLE44" "SCORED" "LEVEL2" - textTitle "$ID45" "$TITLE45" "NOT_SCORED" "LEVEL2" - textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT" - textTitle "$ID71" "$TITLE71" "NOT_SCORED" "EXTRA" - textTitle "$ID72" "$TITLE72" "NOT_SCORED" "EXTRA" - textTitle "$ID73" "$TITLE73" "NOT_SCORED" "EXTRA" - textTitle "$ID74" "$TITLE74" "NOT_SCORED" "EXTRA" - textTitle "$ID75" "$TITLE75" "NOT_SCORED" "EXTRA" - textTitle "$ID76" "$TITLE76" "NOT_SCORED" "EXTRA" - textTitle "$ID77" "$TITLE77" "NOT_SCORED" "EXTRA" - textTitle "$ID78" "$TITLE78" "NOT_SCORED" "EXTRA" - textTitle "$ID79" "$TITLE79" "NOT_SCORED" "EXTRA" - textTitle "$ID710" "$TITLE710" "NOT_SCORED" "EXTRA" - textTitle "$ID711" "$TITLE711" "NOT_SCORED" "EXTRA" - textTitle "$ID712" "$TITLE712" "NOT_SCORED" "EXTRA" - textTitle "$ID713" "$TITLE713" "NOT_SCORED" "EXTRA" - textTitle "$ID714" "$TITLE714" "NOT_SCORED" "EXTRA" - textTitle "$ID715" "$TITLE715" "NOT_SCORED" "EXTRA" - textTitle "$ID716" "$TITLE716" "NOT_SCORED" "EXTRA" - textTitle "$ID717" "$TITLE717" "NOT_SCORED" "EXTRA" - textTitle "$ID718" "$TITLE718" "NOT_SCORED" "EXTRA" - textTitle "$ID719" "$TITLE719" "NOT_SCORED" "EXTRA" - textTitle "$ID720" "$TITLE720" "NOT_SCORED" "EXTRA" - textTitle "$ID721" "$TITLE721" "NOT_SCORED" "EXTRA" - textTitle "$ID722" "$TITLE722" "NOT_SCORED" "EXTRA" - exit $EXITCODE -fi - -### All functions defined above ... run the workflow - -if [[ $MODE != "csv" ]]; then - prowlerBanner - printColorsCode -fi -getWhoami -genCredReport -saveReport -callCheck - -textTitle "1" "$TITLE1" "NOT_SCORED" "SUPPORT" -check11 -check12 -check13 -check14 -check15 -check16 -check17 -check18 -check19 -check110 -check111 -check112 -check113 -check114 -check115 -check116 -check117 -check118 -check119 -check120 -check121 -check122 -check123 -check124 - -textTitle "2" "$TITLE2" "NOT_SCORED" "SUPPORT" -check21 -check22 -check23 -check24 -check25 -check26 -check27 -check28 - -textTitle "3" "$TITLE3" "NOT_SCORED" "SUPPORT" -# 3 Monitoring check commands / Mostly covered by SecurityMonkey -check31 -check32 -check33 -check34 -check35 -check36 -check37 -check38 -check39 -check310 -check311 -check312 -check313 -check314 -check315 - -textTitle "4" "$TITLE4" "NOT_SCORED" "SUPPORT" -check41 -check42 -check43 -check44 -check45 - -if [[ ! $EXTRAS ]]; then - textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT" - extra71 - extra72 - extra73 - extra74 - extra75 - extra76 - extra77 - extra78 - extra79 - extra710 - extra711 - extra712 - extra713 - extra714 - extra715 - extra716 - extra717 - extra718 - extra719 - extra720 - extra721 - extra722 -fi - -cleanTemp -exit $EXITCODE