From a2806ad86bbad2bc7ebec24e5a06e221f3d539ad Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Fri, 23 Mar 2018 10:05:20 -0400 Subject: [PATCH] populated checks --- checks/check11 | 21 ++++++------ checks/check110 | 20 +++++++++++ checks/check111 | 18 ++++++++++ checks/check112 | 23 +++++++++++++ checks/check113 | 16 +++++++++ checks/check114 | 21 ++++++++++++ checks/check115 | 13 ++++++++ checks/check116 | 22 ++++++++++++ checks/check117 | 13 ++++++++ checks/check118 | 31 +++++++++++++++++ checks/check119 | 13 ++++++++ checks/check12 | 15 ++++----- checks/check120 | 13 ++++++++ checks/check121 | 12 +++++++ checks/check122 | 27 +++++++++++++++ checks/check123 | 31 +++++++++++++++++ checks/check124 | 31 +++++++++++++++++ checks/check13 | 34 +++++++++++++++++++ checks/check14 | 50 ++++++++++++++++++++++++++++ checks/check15 | 16 +++++++++ checks/check16 | 16 +++++++++ checks/check17 | 16 +++++++++ checks/check18 | 16 +++++++++ checks/check19 | 16 +++++++++ checks/check21 | 23 +++++++++++++ checks/check22 | 23 +++++++++++++ checks/check23 | 23 +++++++++++++ checks/check24 | 31 +++++++++++++++++ checks/check25 | 18 ++++++++++ checks/check26 | 23 +++++++++++++ checks/check27 | 23 +++++++++++++ checks/check28 | 32 ++++++++++++++++++ checks/check31 | 54 ++++++++++++++++++++++++++++++ checks/check310 | 30 +++++++++++++++++ checks/check311 | 30 +++++++++++++++++ checks/check312 | 30 +++++++++++++++++ checks/check313 | 30 +++++++++++++++++ checks/check314 | 30 +++++++++++++++++ checks/check315 | 42 +++++++++++++++++++++++ checks/check32 | 30 +++++++++++++++++ checks/check33 | 30 +++++++++++++++++ checks/check34 | 30 +++++++++++++++++ checks/check35 | 30 +++++++++++++++++ checks/check36 | 30 +++++++++++++++++ checks/check37 | 30 +++++++++++++++++ checks/check38 | 30 +++++++++++++++++ checks/check39 | 30 +++++++++++++++++ checks/check41 | 20 +++++++++++ checks/check42 | 20 +++++++++++ checks/check43 | 20 +++++++++++ checks/check44 | 18 ++++++++++ checks/check45 | 25 ++++++++++++++ checks/check_extra71 | 37 +++++++++++++++++++++ checks/check_extra710 | 23 +++++++++++++ checks/check_extra711 | 23 +++++++++++++ checks/check_extra712 | 18 ++++++++++ checks/check_extra713 | 25 ++++++++++++++ checks/check_extra714 | 25 ++++++++++++++ checks/check_extra715 | 31 +++++++++++++++++ checks/check_extra716 | 33 ++++++++++++++++++ checks/check_extra717 | 39 ++++++++++++++++++++++ checks/check_extra718 | 23 +++++++++++++ checks/check_extra719 | 23 +++++++++++++ checks/check_extra72 | 22 ++++++++++++ checks/check_extra720 | 46 +++++++++++++++++++++++++ checks/check_extra721 | 26 +++++++++++++++ checks/check_extra722 | 33 ++++++++++++++++++ checks/check_extra723 | 40 ++++++++++++++++++++++ checks/check_extra73 | 46 +++++++++++++++++++++++++ checks/check_extra74 | 22 ++++++++++++ checks/check_extra75 | 22 ++++++++++++ checks/check_extra76 | 21 ++++++++++++ checks/check_extra77 | 26 +++++++++++++++ checks/check_extra78 | 23 +++++++++++++ checks/check_extra79 | 26 +++++++++++++++ checks/{check => check_sample} | 0 checks/lista | 52 +++++++++++++---------------- groups/group1 | 7 ---- groups/group1_iam | 5 +++ groups/group2 | 0 groups/group2_logging | 5 +++ groups/group3 | 0 groups/group3_monitoring | 5 +++ groups/group4 | 0 groups/group4_networking | 5 +++ groups/group5_cislevel1 | 5 +++ groups/group6_cislevel2 | 5 +++ groups/group7_extras | 5 +++ groups/group8_forensics | 5 +++ groups/groupN_sample | 5 +++ groups/group_cislevel1 | 0 groups/group_cislevel2 | 0 groups/group_extras | 0 groups/group_forensics | 0 include/banner | 4 +-- include/colors | 2 +- lll | 2 ++ prowler2 | 61 +++++++++++++++++++++------------- 98 files changed, 2034 insertions(+), 81 deletions(-) rename checks/{check => check_sample} (100%) delete mode 100644 groups/group1 create mode 100644 groups/group1_iam delete mode 100644 groups/group2 create mode 100644 groups/group2_logging delete mode 100644 groups/group3 create mode 100644 groups/group3_monitoring delete mode 100644 groups/group4 create mode 100644 groups/group4_networking create mode 100644 groups/group5_cislevel1 create mode 100644 groups/group6_cislevel2 create mode 100644 groups/group7_extras create mode 100644 groups/group8_forensics create mode 100644 groups/groupN_sample delete mode 100644 groups/group_cislevel1 delete mode 100644 groups/group_cislevel2 delete mode 100644 groups/group_extras delete mode 100644 groups/group_forensics create mode 100644 lll diff --git a/checks/check11 b/checks/check11 index 457faed1..deee3ed5 100644 --- a/checks/check11 +++ b/checks/check11 @@ -1,13 +1,12 @@ -#!/usr/bin/env bash +CHECK_ID_check11="1.1,1.01" +CHECK_TITLE_check11="Avoid the use of the root account (Scored)" +CHECK_SCORED_check11="SCORED" +CHECK_TYPE_check11="LEVEL1" +CHECK_ALTERNATE_check101="check11" -CHECK_ID[check11]="1.1,1.01" -CHECK_TITLE[check11]="Avoid the use of the root account (Scored)" -CHECK_SCORED[check11]="SCORED" -CHECK_TYPE[check11]="LEVEL1" -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') - textNotice "Root account last accessed (password key_1 key_2): $COMMAND11" +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/check110 b/checks/check110 index e69de29b..3de5513a 100644 --- a/checks/check110 +++ b/checks/check110 @@ -0,0 +1,20 @@ +CHECK_ID_check110="" +CHECK_TITLE_check110="" +CHECK_SCORED_check110="" +CHECK_TYPE_check110="" +CHECK_ALTERNATE_check110="check110" + +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 +} diff --git a/checks/check111 b/checks/check111 index e69de29b..8348e4b4 100644 --- a/checks/check111 +++ b/checks/check111 @@ -0,0 +1,18 @@ +CHECK_ID_check111="" +CHECK_TITLE_check111="" +CHECK_SCORED_check111="" +CHECK_TYPE_check111="" +CHECK_ALTERNATE_check111="check111" + +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 +} diff --git a/checks/check112 b/checks/check112 index e69de29b..c1e85c3f 100644 --- a/checks/check112 +++ b/checks/check112 @@ -0,0 +1,23 @@ +CHECK_ID_check112="" +CHECK_TITLE_check112="" +CHECK_SCORED_check112="" +CHECK_TYPE_check112="" +CHECK_ALTERNATE_check112="check112" + +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 +} diff --git a/checks/check113 b/checks/check113 index e69de29b..83985785 100644 --- a/checks/check113 +++ b/checks/check113 @@ -0,0 +1,16 @@ +CHECK_ID_check113="" +CHECK_TITLE_check113="" +CHECK_SCORED_check113="" +CHECK_TYPE_check113="" +CHECK_ALTERNATE_check113="check113" + +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 +} diff --git a/checks/check114 b/checks/check114 index e69de29b..8fae9dcb 100644 --- a/checks/check114 +++ b/checks/check114 @@ -0,0 +1,21 @@ +CHECK_ID_check114="" +CHECK_TITLE_check114="" +CHECK_SCORED_check114="" +CHECK_TYPE_check114="" +CHECK_ALTERNATE_check114="check114" + +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 +} diff --git a/checks/check115 b/checks/check115 index e69de29b..95128057 100644 --- a/checks/check115 +++ b/checks/check115 @@ -0,0 +1,13 @@ +CHECK_ID_check115="" +CHECK_TITLE_check115="" +CHECK_SCORED_check115="" +CHECK_TYPE_check115="" +CHECK_ALTERNATE_check115="check115" + +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 " +} diff --git a/checks/check116 b/checks/check116 index e69de29b..5df186fc 100644 --- a/checks/check116 +++ b/checks/check116 @@ -0,0 +1,22 @@ +CHECK_ID_check116="" +CHECK_TITLE_check116="" +CHECK_SCORED_check116="" +CHECK_TYPE_check116="" +CHECK_ALTERNATE_check116="check116" + +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 +} diff --git a/checks/check117 b/checks/check117 index e69de29b..4badfe9a 100644 --- a/checks/check117 +++ b/checks/check117 @@ -0,0 +1,13 @@ +CHECK_ID_check117="" +CHECK_TITLE_check117="" +CHECK_SCORED_check117="" +CHECK_TYPE_check117="" +CHECK_ALTERNATE_check117="check117" + +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 " +} diff --git a/checks/check118 b/checks/check118 index e69de29b..d81bb6ad 100644 --- a/checks/check118 +++ b/checks/check118 @@ -0,0 +1,31 @@ +CHECK_ID_check118="" +CHECK_TITLE_check118="" +CHECK_SCORED_check118="" +CHECK_TYPE_check118="" +CHECK_ALTERNATE_check118="check118" + +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 ' +' ' ') + 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 +} diff --git a/checks/check119 b/checks/check119 index e69de29b..c1095b3d 100644 --- a/checks/check119 +++ b/checks/check119 @@ -0,0 +1,13 @@ +CHECK_ID_check119="" +CHECK_TITLE_check119="" +CHECK_SCORED_check119="" +CHECK_TYPE_check119="" +CHECK_ALTERNATE_check119="check119" + +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 " +} diff --git a/checks/check12 b/checks/check12 index 2c520084..638e29d9 100644 --- a/checks/check12 +++ b/checks/check12 @@ -1,16 +1,15 @@ -#!/usr/bin/env bash - -CHECK_ID[check12]="1.2,1.02" -CHECK_TITLE[check12]="Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)" -CHECK_SCORED[check12]="SCORED" -CHECK_TYPE[check12]="LEVEL1" -CHECK_ALTERNATE[check102]="check12" +CHECK_ID_check12="1.2,1.02" +CHECK_TITLE_check12="Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)" +CHECK_SCORED_check12="SCORED" +CHECK_TYPE_check12="LEVEL1" +CHECK_ALTERNATE_check102="check12" 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 + 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" diff --git a/checks/check120 b/checks/check120 index e69de29b..d319f64d 100644 --- a/checks/check120 +++ b/checks/check120 @@ -0,0 +1,13 @@ +CHECK_ID_check120="" +CHECK_TITLE_check120="" +CHECK_SCORED_check120="" +CHECK_TYPE_check120="" +CHECK_ALTERNATE_check120="check120" + +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 " +} diff --git a/checks/check121 b/checks/check121 index e69de29b..fc0f048f 100644 --- a/checks/check121 +++ b/checks/check121 @@ -0,0 +1,12 @@ +CHECK_ID_check121="" +CHECK_TITLE_check121="" +CHECK_SCORED_check121="" +CHECK_TYPE_check121="" +CHECK_ALTERNATE_check121="check121" + +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 " +} diff --git a/checks/check122 b/checks/check122 index e69de29b..aa5117c4 100644 --- a/checks/check122 +++ b/checks/check122 @@ -0,0 +1,27 @@ +CHECK_ID_check122="" +CHECK_TITLE_check122="" +CHECK_SCORED_check122="" +CHECK_TYPE_check122="" +CHECK_ALTERNATE_check122="check122" + +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 +} diff --git a/checks/check123 b/checks/check123 index e69de29b..b4624896 100644 --- a/checks/check123 +++ b/checks/check123 @@ -0,0 +1,31 @@ +CHECK_ID_check123="" +CHECK_TITLE_check123="" +CHECK_SCORED_check123="" +CHECK_TYPE_check123="" +CHECK_ALTERNATE_check123="check123" + +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 +} diff --git a/checks/check124 b/checks/check124 index e69de29b..d5635f20 100644 --- a/checks/check124 +++ b/checks/check124 @@ -0,0 +1,31 @@ +CHECK_ID_check124="" +CHECK_TITLE_check124="" +CHECK_SCORED_check124="" +CHECK_TYPE_check124="" +CHECK_ALTERNATE_check124="check124" + +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 +} diff --git a/checks/check13 b/checks/check13 index e69de29b..a981b900 100644 --- a/checks/check13 +++ b/checks/check13 @@ -0,0 +1,34 @@ +CHECK_ID_check13="" +CHECK_TITLE_check13="" +CHECK_SCORED_check13="" +CHECK_TYPE_check13="" +CHECK_ALTERNATE_check13="check13" + +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 ' +' ' '; + 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 +} diff --git a/checks/check14 b/checks/check14 index e69de29b..129b69f7 100644 --- a/checks/check14 +++ b/checks/check14 @@ -0,0 +1,50 @@ +CHECK_ID_check14="" +CHECK_TITLE_check14="" +CHECK_SCORED_check14="" +CHECK_TYPE_check14="" +CHECK_ALTERNATE_check14="check14" + +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 +} diff --git a/checks/check15 b/checks/check15 index e69de29b..9ef12a6b 100644 --- a/checks/check15 +++ b/checks/check15 @@ -0,0 +1,16 @@ +CHECK_ID_check15="" +CHECK_TITLE_check15="" +CHECK_SCORED_check15="" +CHECK_TYPE_check15="" +CHECK_ALTERNATE_check15="check15" + +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 +} diff --git a/checks/check16 b/checks/check16 index e69de29b..5562a6ea 100644 --- a/checks/check16 +++ b/checks/check16 @@ -0,0 +1,16 @@ +CHECK_ID_check16="" +CHECK_TITLE_check16="" +CHECK_SCORED_check16="" +CHECK_TYPE_check16="" +CHECK_ALTERNATE_check16="check16" + +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 +} diff --git a/checks/check17 b/checks/check17 index e69de29b..97162b44 100644 --- a/checks/check17 +++ b/checks/check17 @@ -0,0 +1,16 @@ +CHECK_ID_check17="" +CHECK_TITLE_check17="" +CHECK_SCORED_check17="" +CHECK_TYPE_check17="" +CHECK_ALTERNATE_check17="check17" + +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 +} diff --git a/checks/check18 b/checks/check18 index e69de29b..48bf56e0 100644 --- a/checks/check18 +++ b/checks/check18 @@ -0,0 +1,16 @@ +CHECK_ID_check18="" +CHECK_TITLE_check18="" +CHECK_SCORED_check18="" +CHECK_TYPE_check18="" +CHECK_ALTERNATE_check18="check18" + +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 +} diff --git a/checks/check19 b/checks/check19 index e69de29b..f4e56471 100644 --- a/checks/check19 +++ b/checks/check19 @@ -0,0 +1,16 @@ +CHECK_ID_check19="" +CHECK_TITLE_check19="" +CHECK_SCORED_check19="" +CHECK_TYPE_check19="" +CHECK_ALTERNATE_check19="check19" + +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 +} diff --git a/checks/check21 b/checks/check21 index e69de29b..627b738b 100644 --- a/checks/check21 +++ b/checks/check21 @@ -0,0 +1,23 @@ +CHECK_ID_check21="" +CHECK_TITLE_check21="" +CHECK_SCORED_check21="" +CHECK_TYPE_check21="" +CHECK_ALTERNATE_check21="check21" + +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 +} diff --git a/checks/check22 b/checks/check22 index e69de29b..55a9c9b6 100644 --- a/checks/check22 +++ b/checks/check22 @@ -0,0 +1,23 @@ +CHECK_ID_check22="" +CHECK_TITLE_check22="" +CHECK_SCORED_check22="" +CHECK_TYPE_check22="" +CHECK_ALTERNATE_check22="check22" + +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 +} diff --git a/checks/check23 b/checks/check23 index e69de29b..b06dbc05 100644 --- a/checks/check23 +++ b/checks/check23 @@ -0,0 +1,23 @@ +CHECK_ID_check23="" +CHECK_TITLE_check23="" +CHECK_SCORED_check23="" +CHECK_TYPE_check23="" +CHECK_ALTERNATE_check23="check23" + +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 +} diff --git a/checks/check24 b/checks/check24 index e69de29b..95f22f79 100644 --- a/checks/check24 +++ b/checks/check24 @@ -0,0 +1,31 @@ +CHECK_ID_check24="" +CHECK_TITLE_check24="" +CHECK_SCORED_check24="" +CHECK_TYPE_check24="" +CHECK_ALTERNATE_check24="check24" + +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 " " ',') + 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 +} diff --git a/checks/check25 b/checks/check25 index e69de29b..b6c9cff5 100644 --- a/checks/check25 +++ b/checks/check25 @@ -0,0 +1,18 @@ +CHECK_ID_check25="" +CHECK_TITLE_check25="" +CHECK_SCORED_check25="" +CHECK_TYPE_check25="" +CHECK_ALTERNATE_check25="check25" + +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 +} diff --git a/checks/check26 b/checks/check26 index e69de29b..7158da61 100644 --- a/checks/check26 +++ b/checks/check26 @@ -0,0 +1,23 @@ +CHECK_ID_check26="" +CHECK_TITLE_check26="" +CHECK_SCORED_check26="" +CHECK_TYPE_check26="" +CHECK_ALTERNATE_check26="check26" + +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 +} diff --git a/checks/check27 b/checks/check27 index e69de29b..0106ad70 100644 --- a/checks/check27 +++ b/checks/check27 @@ -0,0 +1,23 @@ +CHECK_ID_check27="" +CHECK_TITLE_check27="" +CHECK_SCORED_check27="" +CHECK_TYPE_check27="" +CHECK_ALTERNATE_check27="check27" + +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 +} diff --git a/checks/check28 b/checks/check28 index e69de29b..56cd89a2 100644 --- a/checks/check28 +++ b/checks/check28 @@ -0,0 +1,32 @@ +CHECK_ID_check28="" +CHECK_TITLE_check28="" +CHECK_SCORED_check28="" +CHECK_TYPE_check28="" +CHECK_ALTERNATE_check28="check28" + +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 +} diff --git a/checks/check31 b/checks/check31 index e69de29b..59ae384b 100644 --- a/checks/check31 +++ b/checks/check31 @@ -0,0 +1,54 @@ +CHECK_ID_check31="" +CHECK_TITLE_check31="" +CHECK_SCORED_check31="" +CHECK_TYPE_check31="" +CHECK_ALTERNATE_check31="check31" + +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 ' ' ' +' | 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 ' ' ' +' | 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 +} diff --git a/checks/check310 b/checks/check310 index e69de29b..9860acdc 100644 --- a/checks/check310 +++ b/checks/check310 @@ -0,0 +1,30 @@ +CHECK_ID_check310="" +CHECK_TITLE_check310="" +CHECK_SCORED_check310="" +CHECK_TYPE_check310="" +CHECK_ALTERNATE_check310="check310" + +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 ' ' ' +' | 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 +} diff --git a/checks/check311 b/checks/check311 index e69de29b..d097c8b4 100644 --- a/checks/check311 +++ b/checks/check311 @@ -0,0 +1,30 @@ +CHECK_ID_check311="" +CHECK_TITLE_check311="" +CHECK_SCORED_check311="" +CHECK_TYPE_check311="" +CHECK_ALTERNATE_check311="check311" + +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 ' ' ' +' | 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 +} diff --git a/checks/check312 b/checks/check312 index e69de29b..70a0aa9f 100644 --- a/checks/check312 +++ b/checks/check312 @@ -0,0 +1,30 @@ +CHECK_ID_check312="" +CHECK_TITLE_check312="" +CHECK_SCORED_check312="" +CHECK_TYPE_check312="" +CHECK_ALTERNATE_check312="check312" + +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 ' ' ' +' | 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 +} diff --git a/checks/check313 b/checks/check313 index e69de29b..7e293c35 100644 --- a/checks/check313 +++ b/checks/check313 @@ -0,0 +1,30 @@ +CHECK_ID_check313="" +CHECK_TITLE_check313="" +CHECK_SCORED_check313="" +CHECK_TYPE_check313="" +CHECK_ALTERNATE_check313="check313" + +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 ' ' ' +' | 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 +} diff --git a/checks/check314 b/checks/check314 index e69de29b..437452d6 100644 --- a/checks/check314 +++ b/checks/check314 @@ -0,0 +1,30 @@ +CHECK_ID_check314="" +CHECK_TITLE_check314="" +CHECK_SCORED_check314="" +CHECK_TYPE_check314="" +CHECK_ALTERNATE_check314="check314" + +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 ' ' ' +' | 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 +} diff --git a/checks/check315 b/checks/check315 index e69de29b..df71f786 100644 --- a/checks/check315 +++ b/checks/check315 @@ -0,0 +1,42 @@ +CHECK_ID_check315="" +CHECK_TITLE_check315="" +CHECK_SCORED_check315="" +CHECK_TYPE_check315="" +CHECK_ALTERNATE_check315="check315" + +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 +' "$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 +} diff --git a/checks/check32 b/checks/check32 index e69de29b..68e42787 100644 --- a/checks/check32 +++ b/checks/check32 @@ -0,0 +1,30 @@ +CHECK_ID_check32="" +CHECK_TITLE_check32="" +CHECK_SCORED_check32="" +CHECK_TYPE_check32="" +CHECK_ALTERNATE_check32="check32" + +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 ' ' ' +' | 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 +} diff --git a/checks/check33 b/checks/check33 index e69de29b..6e6362e2 100644 --- a/checks/check33 +++ b/checks/check33 @@ -0,0 +1,30 @@ +CHECK_ID_check33="" +CHECK_TITLE_check33="" +CHECK_SCORED_check33="" +CHECK_TYPE_check33="" +CHECK_ALTERNATE_check33="check33" + +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 ' ' ' +' | 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 | tr '[:upper:]' '[:lower:]'| grep -Ei '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 +} diff --git a/checks/check34 b/checks/check34 index e69de29b..e84790c7 100644 --- a/checks/check34 +++ b/checks/check34 @@ -0,0 +1,30 @@ +CHECK_ID_check34="" +CHECK_TITLE_check34="" +CHECK_SCORED_check34="" +CHECK_TYPE_check34="" +CHECK_ALTERNATE_check34="check34" + +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 ' ' ' +' | 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 +} diff --git a/checks/check35 b/checks/check35 index e69de29b..0d93635a 100644 --- a/checks/check35 +++ b/checks/check35 @@ -0,0 +1,30 @@ +CHECK_ID_check35="" +CHECK_TITLE_check35="" +CHECK_SCORED_check35="" +CHECK_TYPE_check35="" +CHECK_ALTERNATE_check35="check35" + +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 ' ' ' +' | 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 +} diff --git a/checks/check36 b/checks/check36 index e69de29b..62f31c75 100644 --- a/checks/check36 +++ b/checks/check36 @@ -0,0 +1,30 @@ +CHECK_ID_check36="" +CHECK_TITLE_check36="" +CHECK_SCORED_check36="" +CHECK_TYPE_check36="" +CHECK_ALTERNATE_check36="check36" + +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 ' ' ' +' | 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 +} diff --git a/checks/check37 b/checks/check37 index e69de29b..6809b5d0 100644 --- a/checks/check37 +++ b/checks/check37 @@ -0,0 +1,30 @@ +CHECK_ID_check37="" +CHECK_TITLE_check37="" +CHECK_SCORED_check37="" +CHECK_TYPE_check37="" +CHECK_ALTERNATE_check37="check37" + +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 ' ' ' +' | 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 +} diff --git a/checks/check38 b/checks/check38 index e69de29b..e51edccf 100644 --- a/checks/check38 +++ b/checks/check38 @@ -0,0 +1,30 @@ +CHECK_ID_check38="" +CHECK_TITLE_check38="" +CHECK_SCORED_check38="" +CHECK_TYPE_check38="" +CHECK_ALTERNATE_check38="check38" + +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 ' ' ' +' | 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 +} diff --git a/checks/check39 b/checks/check39 index e69de29b..7a66fceb 100644 --- a/checks/check39 +++ b/checks/check39 @@ -0,0 +1,30 @@ +CHECK_ID_check39="" +CHECK_TITLE_check39="" +CHECK_SCORED_check39="" +CHECK_TYPE_check39="" +CHECK_ALTERNATE_check39="check39" + +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 ' ' ' +' | 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 +} diff --git a/checks/check41 b/checks/check41 index e69de29b..0e63eb4d 100644 --- a/checks/check41 +++ b/checks/check41 @@ -0,0 +1,20 @@ +CHECK_ID_check41="" +CHECK_TITLE_check41="" +CHECK_SCORED_check41="" +CHECK_TYPE_check41="" +CHECK_ALTERNATE_check41="check41" + +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 +} diff --git a/checks/check42 b/checks/check42 index e69de29b..7334c304 100644 --- a/checks/check42 +++ b/checks/check42 @@ -0,0 +1,20 @@ +CHECK_ID_check42="" +CHECK_TITLE_check42="" +CHECK_SCORED_check42="" +CHECK_TYPE_check42="" +CHECK_ALTERNATE_check42="check42" + +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 +} diff --git a/checks/check43 b/checks/check43 index e69de29b..627eca2b 100644 --- a/checks/check43 +++ b/checks/check43 @@ -0,0 +1,20 @@ +CHECK_ID_check43="" +CHECK_TITLE_check43="" +CHECK_SCORED_check43="" +CHECK_TYPE_check43="" +CHECK_ALTERNATE_check43="check43" + +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 +} diff --git a/checks/check44 b/checks/check44 index e69de29b..afdbbfcb 100644 --- a/checks/check44 +++ b/checks/check44 @@ -0,0 +1,18 @@ +CHECK_ID_check44="" +CHECK_TITLE_check44="" +CHECK_SCORED_check44="" +CHECK_TYPE_check44="" +CHECK_ALTERNATE_check44="check44" + +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 +} diff --git a/checks/check45 b/checks/check45 index e69de29b..ac8764d0 100644 --- a/checks/check45 +++ b/checks/check45 @@ -0,0 +1,25 @@ +CHECK_ID_check45="" +CHECK_TITLE_check45="" +CHECK_SCORED_check45="" +CHECK_TYPE_check45="" +CHECK_ALTERNATE_check45="check45" + +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 +} diff --git a/checks/check_extra71 b/checks/check_extra71 index e69de29b..b7575598 100644 --- a/checks/check_extra71 +++ b/checks/check_extra71 @@ -0,0 +1,37 @@ +CHECK_ID_check_extra71="" +CHECK_TITLE_check_extra71="" +CHECK_SCORED_check_extra71="" +CHECK_TYPE_check_extra71="" +CHECK_ALTERNATE_check_extra71="check_extra71" + +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 +} diff --git a/checks/check_extra710 b/checks/check_extra710 index e69de29b..1f7cb26e 100644 --- a/checks/check_extra710 +++ b/checks/check_extra710 @@ -0,0 +1,23 @@ +CHECK_ID_check_extra710="" +CHECK_TITLE_check_extra710="" +CHECK_SCORED_check_extra710="" +CHECK_TYPE_check_extra710="" +CHECK_ALTERNATE_check_extra710="check_extra710" + +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 +} diff --git a/checks/check_extra711 b/checks/check_extra711 index e69de29b..b2a0c8e4 100644 --- a/checks/check_extra711 +++ b/checks/check_extra711 @@ -0,0 +1,23 @@ +CHECK_ID_check_extra711="" +CHECK_TITLE_check_extra711="" +CHECK_SCORED_check_extra711="" +CHECK_TYPE_check_extra711="" +CHECK_ALTERNATE_check_extra711="check_extra711" + +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 +} diff --git a/checks/check_extra712 b/checks/check_extra712 index e69de29b..3515484c 100644 --- a/checks/check_extra712 +++ b/checks/check_extra712 @@ -0,0 +1,18 @@ +CHECK_ID_check_extra712="" +CHECK_TITLE_check_extra712="" +CHECK_SCORED_check_extra712="" +CHECK_TYPE_check_extra712="" +CHECK_ALTERNATE_check_extra712="check_extra712" + +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 +} diff --git a/checks/check_extra713 b/checks/check_extra713 index e69de29b..a64cd24f 100644 --- a/checks/check_extra713 +++ b/checks/check_extra713 @@ -0,0 +1,25 @@ +CHECK_ID_check_extra713="" +CHECK_TITLE_check_extra713="" +CHECK_SCORED_check_extra713="" +CHECK_TYPE_check_extra713="" +CHECK_ALTERNATE_check_extra713="check_extra713" + +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 +} diff --git a/checks/check_extra714 b/checks/check_extra714 index e69de29b..5fab1be2 100644 --- a/checks/check_extra714 +++ b/checks/check_extra714 @@ -0,0 +1,25 @@ +CHECK_ID_check_extra714="" +CHECK_TITLE_check_extra714="" +CHECK_SCORED_check_extra714="" +CHECK_TYPE_check_extra714="" +CHECK_ALTERNATE_check_extra714="check_extra714" + +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 +} diff --git a/checks/check_extra715 b/checks/check_extra715 index e69de29b..503d7c2c 100644 --- a/checks/check_extra715 +++ b/checks/check_extra715 @@ -0,0 +1,31 @@ +CHECK_ID_check_extra715="" +CHECK_TITLE_check_extra715="" +CHECK_SCORED_check_extra715="" +CHECK_TYPE_check_extra715="" +CHECK_ALTERNATE_check_extra715="check_extra715" + +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 +} diff --git a/checks/check_extra716 b/checks/check_extra716 index e69de29b..7749af1d 100644 --- a/checks/check_extra716 +++ b/checks/check_extra716 @@ -0,0 +1,33 @@ +CHECK_ID_check_extra716="" +CHECK_TITLE_check_extra716="" +CHECK_SCORED_check_extra716="" +CHECK_TYPE_check_extra716="" +CHECK_ALTERNATE_check_extra716="check_extra716" + +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 +} diff --git a/checks/check_extra717 b/checks/check_extra717 index e69de29b..7dac48b5 100644 --- a/checks/check_extra717 +++ b/checks/check_extra717 @@ -0,0 +1,39 @@ +CHECK_ID_check_extra717="" +CHECK_TITLE_check_extra717="" +CHECK_SCORED_check_extra717="" +CHECK_TYPE_check_extra717="" +CHECK_ALTERNATE_check_extra717="check_extra717" + +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 +} diff --git a/checks/check_extra718 b/checks/check_extra718 index e69de29b..6d3108b6 100644 --- a/checks/check_extra718 +++ b/checks/check_extra718 @@ -0,0 +1,23 @@ +CHECK_ID_check_extra718="" +CHECK_TITLE_check_extra718="" +CHECK_SCORED_check_extra718="" +CHECK_TYPE_check_extra718="" +CHECK_ALTERNATE_check_extra718="check_extra718" + +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 +} diff --git a/checks/check_extra719 b/checks/check_extra719 index e69de29b..d260f66f 100644 --- a/checks/check_extra719 +++ b/checks/check_extra719 @@ -0,0 +1,23 @@ +CHECK_ID_check_extra719="" +CHECK_TITLE_check_extra719="" +CHECK_SCORED_check_extra719="" +CHECK_TYPE_check_extra719="" +CHECK_ALTERNATE_check_extra719="check_extra719" + +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 +} diff --git a/checks/check_extra72 b/checks/check_extra72 index e69de29b..86fd88f2 100644 --- a/checks/check_extra72 +++ b/checks/check_extra72 @@ -0,0 +1,22 @@ +CHECK_ID_check_extra72="" +CHECK_TITLE_check_extra72="" +CHECK_SCORED_check_extra72="" +CHECK_TYPE_check_extra72="" +CHECK_ALTERNATE_check_extra72="check_extra72" + +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 +} diff --git a/checks/check_extra720 b/checks/check_extra720 index e69de29b..975d6f92 100644 --- a/checks/check_extra720 +++ b/checks/check_extra720 @@ -0,0 +1,46 @@ +CHECK_ID_check_extra720="" +CHECK_TITLE_check_extra720="" +CHECK_SCORED_check_extra720="" +CHECK_TYPE_check_extra720="" +CHECK_ALTERNATE_check_extra720="check_extra720" + +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 +} diff --git a/checks/check_extra721 b/checks/check_extra721 index e69de29b..2a5f5f4f 100644 --- a/checks/check_extra721 +++ b/checks/check_extra721 @@ -0,0 +1,26 @@ +CHECK_ID_check_extra721="" +CHECK_TITLE_check_extra721="" +CHECK_SCORED_check_extra721="" +CHECK_TYPE_check_extra721="" +CHECK_ALTERNATE_check_extra721="check_extra721" + +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 +} diff --git a/checks/check_extra722 b/checks/check_extra722 index e69de29b..bcd7ab47 100644 --- a/checks/check_extra722 +++ b/checks/check_extra722 @@ -0,0 +1,33 @@ +CHECK_ID_check_extra722="" +CHECK_TITLE_check_extra722="" +CHECK_SCORED_check_extra722="" +CHECK_TYPE_check_extra722="" +CHECK_ALTERNATE_check_extra722="check_extra722" + +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 +} diff --git a/checks/check_extra723 b/checks/check_extra723 index e69de29b..470ce3ad 100644 --- a/checks/check_extra723 +++ b/checks/check_extra723 @@ -0,0 +1,40 @@ +CHECK_ID_check_extra723="" +CHECK_TITLE_check_extra723="" +CHECK_SCORED_check_extra723="" +CHECK_TYPE_check_extra723="" +CHECK_ALTERNATE_check_extra723="check_extra723" + +extra723(){ + # "Check if RDS Snapshots are public (Not Scored) (Not part of CIS benchmark)" + textTitle "$ID723" "$TITLE723" "NOT_SCORED" "EXTRA" + for regx in $REGIONS; do + # RDS snapshots + LIST_OF_RDS_SNAPSHOTS=$($AWSCLI rds describe-db-snapshots $PROFILE_OPT --region $regx --query DBSnapshots[*].DBSnapshotIdentifier --output text) + if [[ $LIST_OF_RDS_SNAPSHOTS ]]; then + for rdssnapshot in $LIST_OF_RDS_SNAPSHOTS;do + SNAPSHOT_IS_PUBLIC=$($AWSCLI rds describe-db-snapshot-attributes $PROFILE_OPT --region $regx --db-snapshot-identifier $rdssnapshot --query DBSnapshotAttributesResult.DBSnapshotAttributes[*] --output text|grep ^ATTRIBUTEVALUES|cut -f2|grep all) + if [[ $SNAPSHOT_IS_PUBLIC ]];then + textWarn "$regx: RDS Snapshot $rdssnapshot is public!" "$regx" + else + textOK "$regx: RDS Snapshot $rdssnapshot is not shared" "$regx" + fi + done + else + textNotice "$regx: No RDS Snapshots found" "$regx" + fi + # RDS cluster snapshots + LIST_OF_RDS_CLUSTER_SNAPSHOTS=$($AWSCLI rds describe-db-cluster-snapshots $PROFILE_OPT --region $regx --query DBClusterSnapshots[*].DBClusterSnapshotIdentifier --output text) + if [[ $LIST_OF_RDS_CLUSTER_SNAPSHOTS ]]; then + for rdsclustersnapshot in $LIST_OF_RDS_CLUSTER_SNAPSHOTS;do + CLUSTER_SNAPSHOT_IS_PUBLIC=$($AWSCLI rds describe-db-cluster-snapshot-attributes $PROFILE_OPT --region $regx --db-cluster-snapshot-identifier $rdsclustersnapshot --query DBClusterSnapshotAttributesResult.DBClusterSnapshotAttributes[*] --output text|grep ^ATTRIBUTEVALUES|cut -f2|grep all) + if [[ $CLUSTER_SNAPSHOT_IS_PUBLIC ]];then + textWarn "$regx: RDS Cluster Snapshot $rdsclustersnapshot is public!" "$regx" + else + textOK "$regx: RDS Cluster Snapshot $rdsclustersnapshot is not shared" "$regx" + fi + done + else + textNotice "$regx: No RDS Cluster Snapshots found" "$regx" + fi + done +} diff --git a/checks/check_extra73 b/checks/check_extra73 index e69de29b..273d0813 100644 --- a/checks/check_extra73 +++ b/checks/check_extra73 @@ -0,0 +1,46 @@ +CHECK_ID_check_extra73="" +CHECK_TITLE_check_extra73="" +CHECK_SCORED_check_extra73="" +CHECK_TYPE_check_extra73="" +CHECK_ALTERNATE_check_extra73="check_extra73" + +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 +} diff --git a/checks/check_extra74 b/checks/check_extra74 index e69de29b..c1e52727 100644 --- a/checks/check_extra74 +++ b/checks/check_extra74 @@ -0,0 +1,22 @@ +CHECK_ID_check_extra74="" +CHECK_TITLE_check_extra74="" +CHECK_SCORED_check_extra74="" +CHECK_TYPE_check_extra74="" +CHECK_ALTERNATE_check_extra74="check_extra74" + +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 +} diff --git a/checks/check_extra75 b/checks/check_extra75 index e69de29b..bc5a8a87 100644 --- a/checks/check_extra75 +++ b/checks/check_extra75 @@ -0,0 +1,22 @@ +CHECK_ID_check_extra75="" +CHECK_TITLE_check_extra75="" +CHECK_SCORED_check_extra75="" +CHECK_TYPE_check_extra75="" +CHECK_ALTERNATE_check_extra75="check_extra75" + +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 +} diff --git a/checks/check_extra76 b/checks/check_extra76 index e69de29b..bd0ebafc 100644 --- a/checks/check_extra76 +++ b/checks/check_extra76 @@ -0,0 +1,21 @@ +CHECK_ID_check_extra76="" +CHECK_TITLE_check_extra76="" +CHECK_SCORED_check_extra76="" +CHECK_TYPE_check_extra76="" +CHECK_ALTERNATE_check_extra76="check_extra76" + +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 +} diff --git a/checks/check_extra77 b/checks/check_extra77 index e69de29b..f90c34ff 100644 --- a/checks/check_extra77 +++ b/checks/check_extra77 @@ -0,0 +1,26 @@ +CHECK_ID_check_extra77="" +CHECK_TITLE_check_extra77="" +CHECK_SCORED_check_extra77="" +CHECK_TYPE_check_extra77="" +CHECK_ALTERNATE_check_extra77="check_extra77" + +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 +} diff --git a/checks/check_extra78 b/checks/check_extra78 index e69de29b..8baf0f59 100644 --- a/checks/check_extra78 +++ b/checks/check_extra78 @@ -0,0 +1,23 @@ +CHECK_ID_check_extra78="" +CHECK_TITLE_check_extra78="" +CHECK_SCORED_check_extra78="" +CHECK_TYPE_check_extra78="" +CHECK_ALTERNATE_check_extra78="check_extra78" + +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 +} diff --git a/checks/check_extra79 b/checks/check_extra79 index e69de29b..ff24bd2e 100644 --- a/checks/check_extra79 +++ b/checks/check_extra79 @@ -0,0 +1,26 @@ +CHECK_ID_check_extra79="" +CHECK_TITLE_check_extra79="" +CHECK_SCORED_check_extra79="" +CHECK_TYPE_check_extra79="" +CHECK_ALTERNATE_check_extra79="check_extra79" + +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 +} diff --git a/checks/check b/checks/check_sample similarity index 100% rename from checks/check rename to checks/check_sample diff --git a/checks/lista b/checks/lista index c31d8bc7..4640ead0 100644 --- a/checks/lista +++ b/checks/lista @@ -1,6 +1,3 @@ -check1 -check11 -check12 check13 check14 check15 @@ -23,7 +20,6 @@ check121 check122 check123 check124 -check2 check21 check22 check23 @@ -32,7 +28,6 @@ check25 check26 check27 check28 -check3 check31 check32 check33 @@ -48,32 +43,31 @@ check312 check313 check314 check315 -check4 check41 check42 check43 check44 check45 -check7 -check71 -check72 -check73 -check74 -check75 -check76 -check77 -check78 -check79 -check710 -check711 -check712 -check713 -check714 -check715 -check716 -check717 -check718 -check719 -check720 -check721 -check722 +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/groups/group1 b/groups/group1 deleted file mode 100644 index c82504e0..00000000 --- a/groups/group1 +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -GROUP_ID[1]="group1" -GROUP_NUMBER[1]="1.0" -GROUP_TITLE[1]="Identity and Access Management ****************************************" -GROUP_RUN_BY_DEFAULT[1]="Y" # run it when execute_all is called -GROUP_CHECKS[1]="check11,check12" diff --git a/groups/group1_iam b/groups/group1_iam new file mode 100644 index 00000000..73a4e649 --- /dev/null +++ b/groups/group1_iam @@ -0,0 +1,5 @@ +GROUP_ID[1]="group1" +GROUP_NUMBER[1]="1.0" +GROUP_TITLE[1]="Identity and Access Management ****************************************" +GROUP_RUN_BY_DEFAULT[1]="Y" # run it when execute_all is called +GROUP_CHECKS[1]="check11,check12,check13,check14,check15,check16,check17,check18,check19,check110,check111,check112,check113,check114,check115,check116,check117,check118,check119,check120,check121,check122,check123,check124" diff --git a/groups/group2 b/groups/group2 deleted file mode 100644 index e69de29b..00000000 diff --git a/groups/group2_logging b/groups/group2_logging new file mode 100644 index 00000000..c9225cac --- /dev/null +++ b/groups/group2_logging @@ -0,0 +1,5 @@ +GROUP_ID[2]="group2" +GROUP_NUMBER[2]="2.0" +GROUP_TITLE[2]="Logging ***************************************************************" +GROUP_RUN_BY_DEFAULT[2]="Y" # run it when execute_all is called +GROUP_CHECKS[2]="check21,check22,check23,check24,check25,check26,check27,check28" diff --git a/groups/group3 b/groups/group3 deleted file mode 100644 index e69de29b..00000000 diff --git a/groups/group3_monitoring b/groups/group3_monitoring new file mode 100644 index 00000000..c74d4cf1 --- /dev/null +++ b/groups/group3_monitoring @@ -0,0 +1,5 @@ +GROUP_ID[3]="group3" +GROUP_NUMBER[3]="3.0" +GROUP_TITLE[3]="Monitoring ************************************************************" +GROUP_RUN_BY_DEFAULT[3]="Y" # run it when execute_all is called +GROUP_CHECKS[3]="check31,check32,check33,check34,check35,check36,check37,check38,check39,check310,check311,check312,check313,check314,check315" diff --git a/groups/group4 b/groups/group4 deleted file mode 100644 index e69de29b..00000000 diff --git a/groups/group4_networking b/groups/group4_networking new file mode 100644 index 00000000..b5be3299 --- /dev/null +++ b/groups/group4_networking @@ -0,0 +1,5 @@ +GROUP_ID[4]="group4" +GROUP_NUMBER[4]="4.0" +GROUP_TITLE[4]="Networking ************************************************************" +GROUP_RUN_BY_DEFAULT[4]="Y" # run it when execute_all is called +GROUP_CHECKS[4]="check41,check42,check43,check44,check45" diff --git a/groups/group5_cislevel1 b/groups/group5_cislevel1 new file mode 100644 index 00000000..99656963 --- /dev/null +++ b/groups/group5_cislevel1 @@ -0,0 +1,5 @@ +GROUP_ID[5]="level1" +GROUP_NUMBER[5]="5.0" +GROUP_TITLE[5]="CIS Level 1 **********************************************************" +GROUP_RUN_BY_DEFAULT[5]="N" # run it when execute_all is called +GROUP_CHECKS[5]="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" diff --git a/groups/group6_cislevel2 b/groups/group6_cislevel2 new file mode 100644 index 00000000..698b926b --- /dev/null +++ b/groups/group6_cislevel2 @@ -0,0 +1,5 @@ +GROUP_ID[6]="level2" +GROUP_NUMBER[6]="6.0" +GROUP_TITLE[6]="CIS Level 2 **********************************************************" +GROUP_RUN_BY_DEFAULT[6]="N" # run it when execute_all is called +GROUP_CHECKS[6]="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" diff --git a/groups/group7_extras b/groups/group7_extras new file mode 100644 index 00000000..1cdd6ebe --- /dev/null +++ b/groups/group7_extras @@ -0,0 +1,5 @@ +GROUP_ID[7]="extras" +GROUP_NUMBER[7]="7.0" +GROUP_TITLE[7]="Extras ****************************************************************" +GROUP_RUN_BY_DEFAULT[7]="Y" # run it when execute_all is called +GROUP_CHECKS[7]="extra71,extra72,extra73,extra74,extra75,extra76,extra77,extra78,extra79,extra710,extra711,extra712,extra713,extra714,extra715,extra716,extra717,extra718,extra719,extra720,extra721,extra722,extra723" diff --git a/groups/group8_forensics b/groups/group8_forensics new file mode 100644 index 00000000..c199de70 --- /dev/null +++ b/groups/group8_forensics @@ -0,0 +1,5 @@ +GROUP_ID[8]="forensics-ready" +GROUP_NUMBER[8]="8.0" +GROUP_TITLE[8]="Forensics Readiness ***************************************************" +GROUP_RUN_BY_DEFAULT[8]="N" # run it when execute_all is called +GROUP_CHECKS[8]="check21,check22,check23,check24,check25,check26,check27,check43,extra712,extra713,extra714,extra715,extra717,extra718,extra719,extra720,extra721,extra722" diff --git a/groups/groupN_sample b/groups/groupN_sample new file mode 100644 index 00000000..27678c86 --- /dev/null +++ b/groups/groupN_sample @@ -0,0 +1,5 @@ +GROUP_ID[9]="my-custom-group" +GROUP_NUMBER[9]="9.0" +GROUP_TITLE[9]="My Custom Group **********************************************" +GROUP_RUN_BY_DEFAULT[9]="N" # run it when execute_all is called +GROUP_CHECKS[9]="checkNN,checkMM" diff --git a/groups/group_cislevel1 b/groups/group_cislevel1 deleted file mode 100644 index e69de29b..00000000 diff --git a/groups/group_cislevel2 b/groups/group_cislevel2 deleted file mode 100644 index e69de29b..00000000 diff --git a/groups/group_extras b/groups/group_extras deleted file mode 100644 index e69de29b..00000000 diff --git a/groups/group_forensics b/groups/group_forensics deleted file mode 100644 index e69de29b..00000000 diff --git a/include/banner b/include/banner index 19c16386..2dcfbc99 100644 --- a/include/banner +++ b/include/banner @@ -3,8 +3,8 @@ prowlerBanner() { echo -e " _ __ _ __ _____ _| | ___ _ __" echo -e " | '_ \| '__/ _ \ \ /\ / / |/ _ \ '__|" echo -e " | |_) | | | (_) \ V V /| | __/ |" - echo -e " | .__/|_| \___/ \_/\_/ |_|\___|_|" - echo -e " |_|$NORMAL$BLUE CIS based AWS Account Hardening Tool$NORMAL\n" + echo -e " | .__/|_| \___/ \_/\_/ |_|\___|_|v2.0" + echo -e " |_|$NORMAL$BLUE the handy cloud security tool$NORMAL\n" echo -e "$YELLOW Date: $(date)" } diff --git a/include/colors b/include/colors index 31fc2cd9..cd738b86 100644 --- a/include/colors +++ b/include/colors @@ -53,6 +53,6 @@ fi printColorsCode(){ if [[ $MONOCHROME -eq 0 ]]; then - echo -e "\n$NORMAL Colors Code for results: $NOTICE INFORMATIVE$NORMAL,$OK OK (RECOMMENDED VALUE)$NORMAL, $BAD WARNING (FIX REQUIRED)$NORMAL" + echo -e "\n$NORMAL Colors code for results: $NOTICE INFORMATIVE$NORMAL,$OK OK (RECOMMENDED VALUE)$NORMAL, $BAD WARNING (FIX REQUIRED)$NORMAL" fi } diff --git a/lll b/lll new file mode 100644 index 00000000..66f5ad2d --- /dev/null +++ b/lll @@ -0,0 +1,2 @@ +check11 +check12 diff --git a/prowler2 b/prowler2 index cbd90241..082c2935 100755 --- a/prowler2 +++ b/prowler2 @@ -97,7 +97,7 @@ while getopts ":hlkp:r:c:f:m:M:enb" OPTION; do NUMERAL=1 ;; b ) - NOBANNER="true" + BANNER=1 ;; e ) EXTRAS=1 @@ -136,18 +136,19 @@ REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \ --region $REGION \ --region-names $FILTERREGION) -callCheck(){ - if [[ $CHECKNUMBER ]];then - case "$CHECKNUMBER" in - check11|check101 ) execute_check check11;; - check12|check102 ) execute_check check12;; - * ) - textWarn "ERROR! Use a valid check name (i.e. check41 or extra71)\n"; - esac - cleanTemp - exit $EXITCODE - fi -} + callCheck(){ + if [[ $CHECKNUMBER ]];then + execute_check $CHECKNUMBER + # case "$CHECKNUMBER" in + # check11|check101 ) execute_check check11;; + # check12|check102 ) execute_check check12;; + # * ) + # textWarn "ERROR! Use a valid check name (i.e. check41 or extra71)\n"; + # esac + cleanTemp + exit $EXITCODE + fi + } # List only check tittles if [[ $PRINTCHECKSONLY == "1" ]]; then @@ -168,9 +169,14 @@ for checks in $(ls checks/check*); do done # Function to show the title of the check +# using this way instead of arrays to keep bash3 (osx) and bash4(linux) compatibility show_check_title() { # This would just call textTitle - textTitle "${CHECK_ID[$1]}" "${CHECK_TITLE[$1]}" "${CHECK_SCORED[$1]}" "${CHECK_TYPE[$1]}" + local check_id=CHECK_ID_$1 + local check_title=CHECK_TITLE_$1 + local check_scored=CHECK_SCORED_$1 + local check_type=CHECK_TYPE_$1 + textTitle "${!check_id}" "${!check_title}" "${!check_scored}" "${!check_type}" } # Function to show the title of a group, by numeric id @@ -185,12 +191,22 @@ show_group_title() { execute_check() { # See if this is an alternate name for a check # for example, we might have been passed 1.01 which is another name for 1.1 - if [ ${CHECK_ALTERNATE[$1]} ];then - show_check_title ${CHECK_ALTERNATE[$1]} - ${CHECK_ALTERNATE[$1]} + local alternate_name_var=CHECK_ALTERNATE_$1 + local alternate_name=${!alternate_name_var} + + if [ ${alternate_name} ];then + show_check_title ${alternate_name} + ${alternate_name} else - show_check_title $1 - $1 + # Check to see if this is a real check + local check_id_var=CHECK_ID_$1 + local check_id=${!check_id_var} + if [ ${check_id} ]; then + show_check_title $1 + $1 + else + textWarn "ERROR! Use a valid check name (i.e. check41 or extra71)\n"; + fi fi } @@ -199,8 +215,8 @@ execute_group() { show_group_title $1 # run the checks in the group IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$1]} - for i in "${CHECKS[@]}"; do - execute_check $i + for i in ${CHECKS[@]}; do + execute_check $i done } @@ -235,8 +251,7 @@ show_all_titles() { } ### All functions defined above ... run the workflow - -if [[ $MODE != "csv" || $NOBANNER != "true" ]]; then +if [[ $MODE != "csv" ]]; then prowlerBanner printColorsCode fi