diff --git a/README.md b/README.md index bd0e2090..0121f8fa 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ It covers hardening and security best practices for all AWS regions related to: - Logging (8 checks) - Monitoring (15 checks) - Networking (5 checks) -- Extras (19 checks) *see Extras section -- Forensics related checks +- Extras (22 checks) *see Extras section* +- Forensics related group of checks For a comprehesive list and resolution look at the guide on the link above. @@ -128,14 +128,19 @@ USAGE: prowler -p -r [ -h ] Options: -p specify your AWS profile to use (i.e.: default) - -r specify an AWS region to direct API requests to (i.e.: us-east-1), all regions are checked anyway - -c specify a check number or group from the AWS CIS benchmark (i.e.: check11 for check 1.1, check3 for entire section 3 or level1 for CIS Level 1 Profile Definitions) - -f specify an AWS region to run checks against (i.e.: us-west-1) + -r specify an AWS region to direct API requests to + (i.e.: us-east-1), all regions are checked anyway + -c specify a check number or group from the AWS CIS benchmark + (i.e.: "check11" for check 1.1, "check3" for entire section 3, "level1" for CIS Level 1 Profile Definitions or "forensics-ready") + -f specify an AWS region to run checks against + (i.e.: us-west-1) -m specify the maximum number of items to return for long-running requests (default: 100) -M output mode: text (defalut), mono, csv (separator is ","; data is on stdout; progress on stderr) -k keep the credential report - -n show check numbers to sort easier (i.e.: 1.01 instead of 1.1) + -n show check numbers to sort easier + (i.e.: 1.01 instead of 1.1) -l list all available checks only (does not perform any check) + -e exclude extras -h this help ``` @@ -326,7 +331,7 @@ We are adding additional checks to improve the information gather from each acco Note: Some of these checks for publicly facing resources may not actually be fully public due to other layered controls like S3 Bucket Policies, Security Groups or Network ACLs. -At this moment we have 16 extra checks: +At this moment we have 22 extra checks: - 7.1 (`extra71`) Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark) - 7.2 (`extra72`) Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark) @@ -347,6 +352,9 @@ At this moment we have 16 extra checks: - 7.17 (`extra717`) Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark) - 7.18 (`extra718`) Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark) - 7.19 (`extra719`) Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark) +- 7.20 (`extra720`) Check if Lambda functions are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark) +- 7.21 (`extra721`) Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark) +- 7.22 (`extra722`) Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark) To check all extras in one command: @@ -376,6 +384,9 @@ With this group of checks, Prowler looks if each service with logging or audit c - 7.17 Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark) - 7.18 Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark) - 7.19 Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark) +- 7.20 Check if Lambda functions are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark) +- 7.21 Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark) +- 7.22 Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark) The `forensics-ready` group of checks uses existing and extra checks. To get a forensics readiness report, run this command: ``` diff --git a/prowler b/prowler index a1df8214..d49581d6 100755 --- a/prowler +++ b/prowler @@ -45,22 +45,28 @@ usage(){ echo " USAGE: `basename $0` -p -r [ -h ] + Options: -p specify your AWS profile to use (i.e.: default) - -r specify an AWS region to direct API requests to (i.e.: us-east-1), all regions are checked anyway - -c specify a check number or group from the AWS CIS benchmark (i.e.: check11 for check 1.1, extra71, check3 for entire section 3 or level1 for CIS Level 1 Profile Definitions) - -f specify an AWS region to run checks against (i.e.: us-west-1) + -r specify an AWS region to direct API requests to + (i.e.: us-east-1), all regions are checked anyway + -c specify a check number or group from the AWS CIS benchmark + (i.e.: "check11" for check 1.1, "check3" for entire section 3, "level1" for CIS Level 1 Profile Definitions or "forensics-ready") + -f specify an AWS region to run checks against + (i.e.: us-west-1) -m specify the maximum number of items to return for long-running requests (default: 100) - -M output mode: text (default), mono, csv (separator is \"${SEP}\"; data is on stdout; progress on stderr) + -M output mode: text (defalut), mono, csv (separator is ","; data is on stdout; progress on stderr) -k keep the credential report - -n show check numbers to sort easier (i.e.: 1.01 instead of 1.1) + -n show check numbers to sort easier + (i.e.: 1.01 instead of 1.1) -l list all available checks only (does not perform any check) + -e exclude extras -h this help " exit } -while getopts ":hlkp:r:c:f:m:M:n" OPTION; do +while getopts ":hlkp:r:c:f:m:M:en" OPTION; do case $OPTION in h ) usage @@ -94,6 +100,9 @@ while getopts ":hlkp:r:c:f:m:M:n" OPTION; do n ) NUMERAL=1 ;; + e ) + EXTRAS=1 + ;; : ) echo "" echo "$OPTRED ERROR!$OPTNORMAL -$OPTARG requires an argument" @@ -506,6 +515,12 @@ ID718="7.18,7.18" TITLE718="Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark)" ID719="7.19,7.19" TITLE719="Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark)" +ID720="7.20,7.20" +TITLE720="Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark)" +ID721="7.21,7.21" +TITLE721="Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark)" +ID722="7.22,7.22" +TITLE722="Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark)" printCsvHeader() { >&2 echo "" @@ -1192,7 +1207,8 @@ check31(){ group=${group%:*} textOK "CloudWatch group $group found with metric filter $metric and alarms set for Unauthorized Operation and Access Denied" done - else + fi + if [[ $CHECK31WARN ]]; then for group in $CHECK31WARN; do case $group in *:*) metric=${group#*:} @@ -1889,7 +1905,7 @@ 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 2>/dev/null |cut -f2) + 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) @@ -1900,7 +1916,7 @@ extra713(){ fi done <<< "$LIST_OF_GUARDDUTY_DETECTORS" else - textWarn "$regx: GuardDuty detector $detector not configured" "$regx" + textWarn "$regx: GuardDuty detector not configured!" "$regx" fi done } @@ -2049,6 +2065,96 @@ extra719(){ fi } +extra720(){ + # "Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark)" + textTitle "$ID720" "$TITLE720" "NOT_SCORED" "EXTRA" + for regx in $REGIONS; do + LIST_OF_FUNCTIONS=$($AWSCLI lambda list-functions $PROFILE_OPT --region $regx --query Functions[*].FunctionName --output text) + if [[ $LIST_OF_FUNCTIONS ]]; then + for lambdafunction in $LIST_OF_FUNCTIONS;do + LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query trailList[?HomeRegion==\`$regx\`].Name --output text) + if [[ $LIST_OF_TRAILS ]]; then + for trail in $LIST_OF_TRAILS; do + FUNCTION_ENABLED_IN_TRAIL=$($AWSCLI cloudtrail get-event-selectors $PROFILE_OPT --trail-name $trail --region $regx --query "EventSelectors[*].DataResources[?Type == \`AWS::Lambda::Function\`].Values" --output text |xargs -n1| grep -E "^arn:aws:lambda.*function:$lambdafunction$") + if [[ $FUNCTION_ENABLED_IN_TRAIL ]]; then + textOK "$regx: Lambda function $lambdafunction enabled in trail $trail" "$regx" + else + textWarn "$regx: Lambda function $lambdafunction NOT enabled in trail $trail" "$regx" + fi + done + # LIST_OF_MULTIREGION_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query "trailList[?IsMultiRegionTrail == \`true\`].Name" --output text) + # if [[ $LIST_OF_MULTIREGION_TRAILS ]]; then + # for trail in $LIST_OF_MULTIREGION_TRAILS; do + # REGION_OF_TRAIL=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query "trailList[?IsMultiRegionTrail == \`true\` && Name == \`$trail\` ].HomeRegion" --output text) + # FUNCTION_ENABLED_IN_THIS_REGION=$($AWSCLI cloudtrail get-event-selectors $PROFILE_OPT --trail-name $trail --region $REGION_OF_TRAIL --query "EventSelectors[*].DataResources[?Type == \`AWS::Lambda::Function\`].Values" --output text |xargs -n1| grep -E "^arn:aws:lambda.*function:$lambdafunction$") + # if [[ $FUNCTION_ENABLED_IN_THIS_REGION ]]; then + # textOK "$regx: Lambda function $lambdafunction enabled in trail $trail" "$regx" + # else + # textWarn "$regx: Lambda function $lambdafunction NOT enabled in trail $trail" "$regx" + # fi + # done + # else + # textWarn "$regx: Lambda function $lambdafunction is not being recorded!" "$regx" + # fi + else + textWarn "$regx: Lambda function $lambdafunction is not being recorded no CloudTrail found!" "$regx" + fi + done + else + textNotice "$regx: No Lambda functions found" "$regx" + fi + done +} + +extra721(){ + # "Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark)" + textTitle "$ID721" "$TITLE721" "NOT_SCORED" "EXTRA" + for regx in $REGIONS; do + LIST_OF_REDSHIFT_CLUSTERS=$($AWSCLI redshift describe-clusters $PROFILE_OPT --region $regx --query 'Clusters[*].ClusterIdentifier' --output text) + if [[ $LIST_OF_REDSHIFT_CLUSTERS ]]; then + for redshiftcluster in $LIST_OF_REDSHIFT_CLUSTERS;do + REDSHIFT_LOG_ENABLED=$($AWSCLI redshift describe-logging-status $PROFILE_OPT --region $regx --cluster-identifier $redshiftcluster --query LoggingEnabled --output text | grep True) + if [[ $REDSHIFT_LOG_ENABLED ]];then + REDSHIFT_LOG_ENABLED_BUCKET=$($AWSCLI redshift describe-logging-status $PROFILE_OPT --region $regx --cluster-identifier $redshiftcluster --query BucketName --output text) + textOK "$regx: Redshift cluster $redshiftcluster has audit logging enabled to bucket $REDSHIFT_LOG_ENABLED_BUCKET" "$regx" + else + textWarn "$regx: Redshift cluster $redshiftcluster logging disabled!" "$regx" + fi + done + else + textNotice "$regx: No Redshift cluster configured" "$regx" + fi + done +} + +extra722(){ + # "Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark)" + textTitle "$ID722" "$TITLE722" "NOT_SCORED" "EXTRA" + for regx in $REGIONS; do + LIST_OF_API_GW=$($AWSCLI apigateway get-rest-apis $PROFILE_OPT --region $regx --query items[*].id --output text) + if [[ $LIST_OF_API_GW ]];then + for apigwid in $LIST_OF_API_GW;do + API_GW_NAME=$($AWSCLI apigateway get-rest-apis $PROFILE_OPT --region $regx --query "items[?id==\`$apigwid\`].name" --output text) + CHECK_STAGES_NAME=$($AWSCLI apigateway get-stages $PROFILE_OPT --region $regx --rest-api-id $apigwid --query "item[*].stageName" --output text) + if [[ $CHECK_STAGES_NAME ]];then + for stagname in $CHECK_STAGES_NAME;do + CHECK_STAGE_METHOD_LOGGING=$($AWSCLI apigateway get-stages $PROFILE_OPT --region $regx --rest-api-id $apigwid --query "item[?stageName == \`$stagname\` ].methodSettings" --output text|awk '{ print $1" log level "$6}') + if [[ $CHECK_STAGE_METHOD_LOGGING ]];then + textOK "$regx: API Gateway $API_GW_NAME has stage logging enabled for $CHECK_STAGE_METHOD_LOGGING" "$regx" + else + textWarn "$regx: API Gateway $API_GW_NAME logging disabled for stage $stagname!" "$regx" + fi + done + else + textWarn "$regx: No Stage name found for $API_GW_NAME" "$regx" + fi + done + else + textNotice "$regx: No API Gateway found" "$regx" + fi + done +} + callCheck(){ if [[ $CHECKNUMBER ]];then case "$CHECKNUMBER" in @@ -2113,16 +2219,20 @@ callCheck(){ extra77|extra707 ) extra77;; extra78|extra708 ) extra78;; extra79|extra709 ) extra79;; - extra710|extra710 ) extra710;; - extra711|extra711 ) extra711;; - extra712|extra712 ) extra712;; - extra713|extra713 ) extra713;; - extra714|extra714 ) extra714;; - extra715|extra715 ) extra715;; - extra716|extra716 ) extra716;; - extra717|extra717 ) extra717;; - extra718|extra718 ) extra718;; - extra719|extra719 ) extra719;; + extra710 ) extra710;; + extra711 ) extra711;; + extra712 ) extra712;; + extra713 ) extra713;; + extra714 ) extra714;; + extra715 ) extra715;; + extra716 ) extra716;; + extra717 ) extra717;; + extra718 ) extra718;; + extra719 ) extra719;; + extra720 ) extra720;; + extra721 ) extra721;; + extra722 ) extra722;; + ## Groups of Checks check1 ) @@ -2160,12 +2270,13 @@ callCheck(){ extras ) extra71;extra72;extra73;extra74;extra75;extra76;extra77;extra78; extra79;extra710;extra711;extra712;extra713;extra714;extra715;extra716; - extra717;extra718;extra719 + extra717;extra718;extra719;extra720;extra721;extra722 ;; forensics-ready ) check21;check22;check23;check24;check25;check26;check27; check43; - extra712;extra713;extra714;extra715;extra717;extra718;extra719 + extra712;extra713;extra714;extra715;extra717;extra718;extra719; + extra720;extra721;extra722 ;; * ) textWarn "ERROR! Use a valid check name (i.e. check41 or extra71)\n"; @@ -2255,6 +2366,9 @@ if [[ $PRINTCHECKSONLY == "1" ]]; then textTitle "$ID717" "$TITLE717" "NOT_SCORED" "EXTRA" textTitle "$ID718" "$TITLE718" "NOT_SCORED" "EXTRA" textTitle "$ID719" "$TITLE719" "NOT_SCORED" "EXTRA" + textTitle "$ID720" "$TITLE720" "NOT_SCORED" "EXTRA" + textTitle "$ID721" "$TITLE721" "NOT_SCORED" "EXTRA" + textTitle "$ID722" "$TITLE722" "NOT_SCORED" "EXTRA" exit $EXITCODE fi @@ -2330,26 +2444,31 @@ check43 check44 check45 -textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT" -extra71 -extra72 -extra73 -extra74 -extra75 -extra76 -extra77 -extra78 -extra79 -extra710 -extra711 -extra712 -extra713 -extra714 -extra715 -extra716 -extra717 -extra718 -extra719 +if [[ ! $EXTRAS ]]; then + textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT" + extra71 + extra72 + extra73 + extra74 + extra75 + extra76 + extra77 + extra78 + extra79 + extra710 + extra711 + extra712 + extra713 + extra714 + extra715 + extra716 + extra717 + extra718 + extra719 + extra720 + extra721 + extra722 +fi cleanTemp exit $EXITCODE diff --git a/prowler-policy-additions.json b/prowler-policy-additions.json index 9104c6da..66ba909c 100644 --- a/prowler-policy-additions.json +++ b/prowler-policy-additions.json @@ -9,7 +9,8 @@ "cloudwatchlogs:describemetricfilters", "es:describeelasticsearchdomainconfig", "ses:getidentityverificationattributes", - "sns:listsubscriptionsbytopic" + "sns:listsubscriptionsbytopic", + "guardduty:ListDetectors" ], "Effect": "Allow", "Resource": "*"