From 1087d604570ec8a66532f55dabecc62e57285869 Mon Sep 17 00:00:00 2001 From: Nimrod Kor Date: Wed, 18 Dec 2019 13:23:51 +0200 Subject: [PATCH 01/25] Small check fixes (cherry picked from commit 70879ba1e03ee7d5e5d59f94fd049620e08e4847) --- checks/check_extra716 | 3 ++- checks/check_extra731 | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/checks/check_extra716 b/checks/check_extra716 index b0b51b85bf..ea45909862 100644 --- a/checks/check_extra716 +++ b/checks/check_extra716 @@ -37,8 +37,9 @@ extra716(){ textPass "$regx: $domain is in a VPC" "$regx" fi done + else + textInfo "$regx: No Elasticsearch Service domain found" "$regx" fi - textInfo "$regx: No Elasticsearch Service domain found" "$regx" rm -fr $TEMP_POLICY_FILE done } diff --git a/checks/check_extra731 b/checks/check_extra731 index 911108ab29..0baa1b6e25 100644 --- a/checks/check_extra731 +++ b/checks/check_extra731 @@ -32,9 +32,9 @@ extra731(){ if [[ $SNS_POLICY_ALLOW_ALL_WITHOUT_CONDITION ]]; then SNS_POLICY_ALLOW_ALL_WITHOUT_CONDITION_DETAILS=$(echo $SNS_POLICY_ALLOW_ALL_WITHOUT_CONDITION \ | jq '"[Principal: " + (.Principal|tostring) + " Action: " + (.Action|tostring) + "]"' ) - textFail "$regx: SNS topic policy with public access: $SNS_POLICY_ALLOW_ALL_WITHOUT_CONDITION_DETAILS" "$SHORT_TOPIC" "$regx" + textFail "$regx: SNS topic $SHORT_TOPIC's policy with public access: $SNS_POLICY_ALLOW_ALL_WITHOUT_CONDITION_DETAILS" "$SHORT_TOPIC" "$regx" else - textPass "$regx: SNS topic policy with public access but has a Condition" "$SHORT_TOPIC" "$regx" + textPass "$regx: SNS topic $SHORT_TOPIC's policy with public access but has a Condition" "$SHORT_TOPIC" "$regx" fi else textPass "$regx: SNS topic without public access" "$SHORT_TOPIC" "$regx" From f979c7334f1f61d1b9c2ddf09fb5d32937cc60a3 Mon Sep 17 00:00:00 2001 From: Dom Bellizzi Date: Wed, 18 Dec 2019 22:06:44 +0000 Subject: [PATCH 02/25] Add quiet mode that only logs failures --- README.md | 1 + include/outputs | 8 ++++++++ prowler | 7 ++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ce7773e25..bc4091af83 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX -b do not print Prowler banner -V show version number & exit -s show scoring report + -q suppress info messages and passing test output -h this help ``` diff --git a/include/outputs b/include/outputs index 6e379d0f82..a33b1dca2d 100644 --- a/include/outputs +++ b/include/outputs @@ -13,6 +13,10 @@ # Output formatting functions textPass(){ + if [[ "$QUIET" == 1 ]]; then + return + fi + PASS_COUNTER=$((PASS_COUNTER+1)) if [[ "$MODE" == "csv" ]]; then if [[ $2 ]]; then @@ -55,6 +59,10 @@ textPass(){ } textInfo(){ + if [[ "$QUIET" == 1 ]]; then + return + fi + if [[ "$MODE" == "csv" ]]; then if [[ $2 ]]; then REPREGION=$2 diff --git a/prowler b/prowler index 437b72941c..fa8fc1e1cf 100755 --- a/prowler +++ b/prowler @@ -40,6 +40,7 @@ FILTERREGION="" MAXITEMS=100 MONOCHROME=0 MODE="text" +QUIET=0 SEP=',' KEEPCREDREPORT=0 EXITCODE=0 @@ -75,12 +76,13 @@ USAGE: -V show version number & exit -s show scoring report -x specify external directory with custom checks (i.e. /my/own/checks, files must start by "check") + -q suppress info messages and passing test output -h this help " exit } -while getopts ":hlLkp:r:c:g:f:m:M:E:enbVsx:" OPTION; do +while getopts ":hlLkqp:r:c:g:f:m:M:E:enbVsx:" OPTION; do case $OPTION in h ) usage @@ -140,6 +142,9 @@ while getopts ":hlLkp:r:c:g:f:m:M:E:enbVsx:" OPTION; do x ) EXTERNAL_CHECKS_PATH=$OPTARG ;; + q ) + QUIET=1 + ;; : ) echo "" echo "$OPTRED ERROR!$OPTNORMAL -$OPTARG requires an argument" From cc5da4279709c571aebc73d4fa1419e54163fdd9 Mon Sep 17 00:00:00 2001 From: Dominick Bellizzi Date: Wed, 18 Dec 2019 14:53:09 -0800 Subject: [PATCH 03/25] add lambda:get* to prowler-additions-policy The check: 7.60 [extra760] Find secrets in Lambda functions code (Not Scored) (Not part of CIS benchmark) errors by default, with the following: An error occurred (AccessDeniedException) when calling the GetFunction operation: User: user/prowler is not authorized to perform: lambda:GetFunction on resource: arn:aws:lambda:eu-west-2:347708466071:function:ApiSimpleDelayDDMonitor Adding this policy to be successfully run that check. --- iam/prowler-additions-policy.json | 1 + 1 file changed, 1 insertion(+) diff --git a/iam/prowler-additions-policy.json b/iam/prowler-additions-policy.json index c95b05f202..0f4b24f8c1 100644 --- a/iam/prowler-additions-policy.json +++ b/iam/prowler-additions-policy.json @@ -50,6 +50,7 @@ "gamelift:list*", "glacier:list*", "importexport:listjobs", + "lambda:get*", "lex:getbotaliases", "lex:getbotchannelassociations", "lex:getbots", From 20b127f516827da19371e3351e450c6e443f49b5 Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Thu, 26 Dec 2019 16:34:24 +0100 Subject: [PATCH 04/25] Added DS IAM actions --- iam/prowler-additions-policy.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iam/prowler-additions-policy.json b/iam/prowler-additions-policy.json index c95b05f202..dc70ac56bb 100644 --- a/iam/prowler-additions-policy.json +++ b/iam/prowler-additions-policy.json @@ -30,10 +30,12 @@ "dax:describeparameters", "dax:describesubnetgroups", "dax:describetable", - "dax:listtables", + "dax:listtables", "devicefarm:list*", "discovery:list*", "dms:list*", + "ds:ListAuthorizedApplications", + "ds:DescribeRoles", "dynamodb:describebackup", "dynamodb:describeglobaltablesettings", "dynamodb:describelimits", From 23be47a9b60896d9031048d8fdefb09ddcaef1ae Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Fri, 27 Dec 2019 12:09:35 +0100 Subject: [PATCH 05/25] Enhanced title for check extra723 --- checks/check_extra723 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/check_extra723 b/checks/check_extra723 index 589df54884..96039eb993 100644 --- a/checks/check_extra723 +++ b/checks/check_extra723 @@ -11,7 +11,7 @@ # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. CHECK_ID_extra723="7.23" -CHECK_TITLE_extra723="[extra723] Check if RDS Snapshots are public (Not Scored) (Not part of CIS benchmark)" +CHECK_TITLE_extra723="[extra723] Check if RDS Snapshots and Cluster Snapshots are public (Not Scored) (Not part of CIS benchmark)" CHECK_SCORED_extra723="NOT_SCORED" CHECK_TYPE_extra723="EXTRA" CHECK_ALTERNATE_check723="extra723" From c84190c3d9eb60e84ea51c3f7829a79b75032e96 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Dec 2019 14:48:41 -0500 Subject: [PATCH 06/25] Add error checking to checks extra77 and extra765 --- checks/check_extra765 | 40 ++++++++++++++++++++++++-------------- checks/check_extra77 | 45 +++++++++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/checks/check_extra765 b/checks/check_extra765 index 3792db18ac..9cbb0930fd 100644 --- a/checks/check_extra765 +++ b/checks/check_extra765 @@ -19,7 +19,6 @@ # --region \ # --repository-name \ # --image-scanning-configuration scanOnPush=true - CHECK_ID_extra765="7.65" CHECK_TITLE_extra765="[extra765] Check if ECR image scan on push is enabled (Not Scored) (Not part of CIS benchmark)" @@ -28,19 +27,32 @@ CHECK_TYPE_extra765="EXTRA" CHECK_ALTERNATE_check765="extra765" extra765(){ - for regx in $REGIONS; do - LIST_ECR_REPOS=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $regx --query "repositories[*].[repositoryName]" --output text 2>&1) - if [[ $LIST_ECR_REPOS ]]; then + for region in $REGIONS; do + LIST_ECR_REPOS=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $region --query "repositories[*].[repositoryName]" --output text 2>&1) + if [[ $(echo "$LIST_ECR_REPOS" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to describe ECR repositories" + continue + fi + if [[ ! -z "$LIST_ECR_REPOS" ]]; then for repo in $LIST_ECR_REPOS; do - SCAN_ENABLED=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $regx --query "repositories[?repositoryName==\`$repo\`].[imageScanningConfiguration.scanOnPush]" --output text|grep True) - if [[ $SCAN_ENABLED ]];then - textPass "$regx: ECR repository $repo has scan on push enabled" "$regx" - else - textFail "$regx: ECR repository $repo has scan on push disabled!" "$regx" - fi - done + SCAN_ENABLED=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $region --query "repositories[?repositoryName==\`$repo\`].[imageScanningConfiguration.scanOnPush]" --output text 2>&1) + case "$SCAN_ENABLED" in + "True") + textPass "$region: ECR repository $repo has scan on push enabled" "$region" + ;; + "False") + textFail "$region: ECR repository $repo has scan on push disabled!" "$region" + ;; + "None") + textInfo "$region: ECR repository $repo hs no scanOnPush status, newer awscli needed" "$region" + ;; + "*") + textInfo "$region: ECR repository $repo has unknown scanOnPush status \"$SCAN_ENABLED\"" "$region" + ;; + esac + done else - textInfo "$regx: No ECR repositories found" "$regx" - fi - done + textInfo "$region: No ECR repositories found" "$region" + fi + done } diff --git a/checks/check_extra77 b/checks/check_extra77 index 8e0b9b4103..886184c833 100644 --- a/checks/check_extra77 +++ b/checks/check_extra77 @@ -10,6 +10,7 @@ # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. + CHECK_ID_extra77="7.7,7.07" CHECK_TITLE_extra77="[extra77] Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark)" CHECK_SCORED_extra77="NOT_SCORED" @@ -20,20 +21,36 @@ CHECK_ALTERNATE_check707="extra77" extra77(){ # "Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark)" - textInfo "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 + for region in $REGIONS; do + LIST_ECR_REPOS=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $region --query "repositories[*].[repositoryName]" --output text 2>&1) + if [[ $(echo "$LIST_ECR_REPOS" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to describe ECR repositories" + continue + fi + if [[ ! -z "$LIST_ECR_REPOS" ]]; then 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 - textFail "$regx: $ecr_repo policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx" - else - textPass "$regx: $ecr_repo is not open" "$regx" - fi - done - rm -fr $TEMP_POLICY_FILE + for repo in $LIST_ECR_REPOS; do + $AWSCLI ecr get-repository-policy $PROFILE_OPT --region $region --repository-name $repo --query "policyText" --output text > $TEMP_POLICY_FILE 2>&1 + if [[ $(grep AccessDenied $TEMP_POLICY_FILE) ]]; then + textFail "$region: $repo Access Denied for get-repository-policy" + continue + fi + # https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html - "By default, only the repository owner has access to a repository." + if [[ $(grep RepositoryPolicyNotFoundException $TEMP_POLICY_FILE) ]]; then + textPass "$region: $repo is not open" "$region" + continue + fi + # check if the policy has Principal as * + CHECK_ECR_REPO_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | jq '.Statement[]|select(.Effect=="Allow" and (((.Principal|type == "object") and .Principal.AWS == "*") or ((.Principal|type == "string") and .Principal == "*")))') + if [[ $CHECK_ECR_REPO_ALLUSERS_POLICY ]]; then + textFail "$region: $repo policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$region" + else + textPass "$region: $repo is not open" "$region" + fi + done + rm -f $TEMP_POLICY_FILE + else + textInfo "$region: No ECR repositories found" "$region" + fi done } From 688f02869800eb58f149e09733b7530d49277b72 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 30 Dec 2019 11:33:12 -0500 Subject: [PATCH 07/25] Add additional error checkings to check extra769 --- checks/check_extra769 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/checks/check_extra769 b/checks/check_extra769 index 8ec529e23a..99835ba0e4 100644 --- a/checks/check_extra769 +++ b/checks/check_extra769 @@ -10,6 +10,7 @@ # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. + CHECK_ID_extra769="7.69" CHECK_TITLE_extra769="[extra769] Check if IAM Access Analyzer is enabled and its findings (Not Scored) (Not part of CIS benchmark)" CHECK_SCORED_extra769="NOT_SCORED" @@ -18,10 +19,18 @@ CHECK_ALTERNATE_check769="extra769" extra769(){ for regx in $REGIONS; do - LIST_OF_ACCESS_ANALYZERS=$($AWSCLI accessanalyzer list-analyzers $PROFILE_OPT --region $regx --query analyzers[*].arn --output text) + LIST_OF_ACCESS_ANALYZERS=$($AWSCLI accessanalyzer list-analyzers $PROFILE_OPT --region $regx --query analyzers[*].arn --output text 2>&1) + if [[ $(echo "$LIST_OF_ACCESS_ANALYZERS" | grep -i "argument command: Invalid choice") ]]; then + textInfo "$regx: list-analyzers not supported, newer awscli needed" "$regx" + continue + fi + if [[ $(echo "$LIST_OF_ACCESS_ANALYZERS" | grep -i "AccessDeniedException") ]]; then + textFail "$regx: Access Denied trying to list-analyzers" "$regx" + continue + fi if [[ $LIST_OF_ACCESS_ANALYZERS ]]; then for accessAnalyzerArn in $LIST_OF_ACCESS_ANALYZERS;do - ANALYZER_ACTIVE_FINDINGS_COUNT=$($AWSCLI accessanalyzer list-findings $PROFILE_OPT --region $regx --analyzer-arn $accessAnalyzerArn --query 'findings[?status == `ACTIVE`].[id,status]' --output text | wc -l | tr -d ' ') + ANALYZER_ACTIVE_FINDINGS_COUNT=$($AWSCLI accessanalyzer list-findings $PROFILE_OPT --region $regx --analyzer-arn $accessAnalyzerArn --query 'findings[?status == `ACTIVE`].[id,status]' --output text | wc -l | tr -d ' ') if [[ $ANALYZER_ACTIVE_FINDINGS_COUNT -eq 0 ]];then textPass "$regx: IAM Access Analyzer $accessAnalyzerArn has no active findings" "$regx" else From 53ea126065e9b7d90eb2f6d38c098a23e9a7cc31 Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Mon, 30 Dec 2019 18:30:25 +0100 Subject: [PATCH 08/25] Add native support for AssumeRole issue #445 --- README.md | 41 ++++++++++++++++++++++++++++++++++ include/assume_role | 54 +++++++++++++++++++++++++++++++++++++++++++++ prowler | 18 ++++++++++++++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 include/assume_role diff --git a/README.md b/README.md index bc4091af83..8a77917516 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ - [Features](#features) - [Requirements and Installation](#requirements-and-installation) - [Usage](#usage) +- [Advanced Usage](#advanced-usage) - [Fix](#fix) - [Screenshots](#screenshots) - [Troubleshooting](#troubleshooting) @@ -222,10 +223,50 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX -b do not print Prowler banner -V show version number & exit -s show scoring report + -x specify external directory with custom checks (i.e. /my/own/checks, files must start by check) -q suppress info messages and passing test output + -A account id for the account where to assume a role, requires -R and -T + (i.e.: 123456789012) + -R role name to assume in the account, requires -A and -T + (i.e.: ProwlerRole) + -T session durantion given to that role credentials in seconds, default 1h (3600) recommended 12h, requires -R and -T + (i.e.: 43200) -h this help ``` +## Advanced Usage + +### Assume Role: + +Prowler uses the AWS CLI underneath so it uses the same authentication methods. However, there are few ways to run Prowler against multiple accounts using IAM Assume Role feature depending on eachg use case. You can just set up your custom profile inside `~/.aws/config` with all needed information about the role to assume then call it with `./prowler -p your-custom-profile`. Additionally you can use `-A 123456789012` and `-R RemoteRoleToAssume` and Prowler will get those temporary credentials using `aws sts assume-role`, set them up as environment variables and run against that given account. + +``` +./prowler -A 123456789012 -R ProwlerRole +``` + +> *NOTE 1 about Session Duration*: By default it gets credentials valid for 1 hour (3600 seconds). Depending on the mount of checks you run and the size of your infrastructure, Prowler may require more than 1 hour to finish. Use option `-T ` to allow up to 12h (43200 seconds). To allow more than 1h you need to modify *"Maximum CLI/API session duration"* for that particular role, read more [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html#id_roles_use_view-role-max-session). + +> *NOTE 2 about Session Duration*: Bear in mind that if you are using roles assumed by role chaining there is a hard limit of 1 hour so consider not using role chaining if possible, read more about that, in foot note 1 below the table [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html). + +For example, if you want to get only the fails in CSV format from all checks regarding RDS without banner from the AWS Account 123456789012 assuming the role RemoteRoleToAssume and set a fixed session duration of 1h: + +``` +./prowler -A 123456789012 -R RemoteRoleToAssume -T 3600 -b -M cvs -q -g rds +``` + +### Custom folder for custom checks + +Flag `-x /my/own/checks` will include any check in that particular directory. To see how to write checks see [Add Custom Checks](#add-custom-checks) section. + +### Show or log only FAILs + +In order to remove noise and get only FAIL findings there is a `-q` flag that makes Prowler to show and log only FAILs. It can be combined with any other option. + +``` +./prowler -q -M csv -b +``` + + ## How to fix every FAIL Check your report and fix the issues following all specific guidelines per check in diff --git a/include/assume_role b/include/assume_role new file mode 100644 index 0000000000..3bf1da7746 --- /dev/null +++ b/include/assume_role @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +# both variables are mandatory to be set together +if [[ $ACCOUNT_TO_ASSUME ]]; then + if [[ -z $ROLE_TO_ASSUME ]]; then + echo "$OPTRED ERROR!$OPTNORMAL - Both Account ID (-A) and IAM Role to assume (-R) must be set" + exit 1 + fi + # if not session duration set with -T, then will be 1h. + # In some cases you will need more than 1h. + if [[ -z $SESSION_DURATION_TO_ASSUME ]]; then + SESSION_DURATION_TO_ASSUME="3600" + fi + + # temporary file where to store credentials + TEMP_STS_ASSUMED_FILE=$(mktemp -t prowler.sts_assumed-XXXXXX) + + # assume role command + $AWSCLI sts assume-role --role-arn arn:aws:iam::$ACCOUNT_TO_ASSUME:role/$ROLE_TO_ASSUME \ + --role-session-name ProwlerAssessmentSession \ + --duration-seconds $SESSION_DURATION_TO_ASSUME > $TEMP_STS_ASSUMED_FILE + + # if previous command fails exit with the given error from aws-cli + # this is likely to be due to session duration limit of 1h in case + # of assume role chaining: + # "The requested DurationSeconds exceeds the 1 hour session limit + # for roles assumed by role chaining." + # https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html + if [[ $? != 0 ]];then + exit 1 + fi + + cat $TEMP_STS_ASSUMED_FILE + + # set env variables with assumed role credentials + AWS_ACCESS_KEY_ID=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.AccessKeyId') + AWS_SECRET_ACCESS_KEY=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.SecretAccessKey') + AWS_SESSION_TOKEN=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.SessionToken') + + aws sts get-caller-identity + + rm -fr $TEMP_STS_ASSUMED_FILE +fi diff --git a/prowler b/prowler index fa8fc1e1cf..8677e0c768 100755 --- a/prowler +++ b/prowler @@ -77,12 +77,18 @@ USAGE: -s show scoring report -x specify external directory with custom checks (i.e. /my/own/checks, files must start by "check") -q suppress info messages and passing test output + -A account id for the account where to assume a role, requires -R and -T + (i.e.: 123456789012) + -R role name to assume in the account, requires -A and -T + (i.e.: ProwlerRole) + -T session durantion given to that role credentials in seconds, default 1h (3600) recommended 12h, requires -R and -T + (i.e.: 43200) -h this help " exit } -while getopts ":hlLkqp:r:c:g:f:m:M:E:enbVsx:" OPTION; do +while getopts ":hlLkqp:r:c:g:f:m:M:E:enbVsx:A:R:T:" OPTION; do case $OPTION in h ) usage @@ -145,6 +151,15 @@ while getopts ":hlLkqp:r:c:g:f:m:M:E:enbVsx:" OPTION; do q ) QUIET=1 ;; + A ) + ACCOUNT_TO_ASSUME=$OPTARG + ;; + R ) + ROLE_TO_ASSUME=$OPTARG + ;; + T ) + SESSION_DURATION_TO_ASSUME=$OPTARG + ;; : ) echo "" echo "$OPTRED ERROR!$OPTNORMAL -$OPTARG requires an argument" @@ -175,6 +190,7 @@ done . $PROWLER_DIR/include/python_detector . $PROWLER_DIR/include/secrets_detector . $PROWLER_DIR/include/check3x +. $PROWLER_DIR/include/assume_role # Get a list of all available AWS Regions REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \ From f3bfe90587a534d40b2f92420a93f972d1ee98cd Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Mon, 30 Dec 2019 18:32:00 +0100 Subject: [PATCH 09/25] Add native support for AssumeRole clean up issue #445 --- include/assume_role | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/assume_role b/include/assume_role index 3bf1da7746..0ff9761051 100644 --- a/include/assume_role +++ b/include/assume_role @@ -41,14 +41,9 @@ if [[ $ACCOUNT_TO_ASSUME ]]; then exit 1 fi - cat $TEMP_STS_ASSUMED_FILE - # set env variables with assumed role credentials AWS_ACCESS_KEY_ID=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.AccessKeyId') AWS_SECRET_ACCESS_KEY=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.SecretAccessKey') AWS_SESSION_TOKEN=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.SessionToken') - - aws sts get-caller-identity - rm -fr $TEMP_STS_ASSUMED_FILE fi From 4cc5cd1ab10fa6461d86130e1d063a5239848032 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 30 Dec 2019 12:06:11 -0500 Subject: [PATCH 10/25] Try to make sure prowler cleans up its temporary files --- checks/check_extra716 | 2 +- checks/check_extra73 | 3 ++- checks/check_extra734 | 10 +++++----- checks/check_extra764 | 2 ++ checks/check_extra77 | 6 ++++-- prowler | 13 ++++++++----- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/checks/check_extra716 b/checks/check_extra716 index ea45909862..404462b1b2 100644 --- a/checks/check_extra716 +++ b/checks/check_extra716 @@ -36,10 +36,10 @@ extra716(){ else textPass "$regx: $domain is in a VPC" "$regx" fi + rm -f $TEMP_POLICY_FILE done else textInfo "$regx: No Elasticsearch Service domain found" "$regx" fi - rm -fr $TEMP_POLICY_FILE done } diff --git a/checks/check_extra73 b/checks/check_extra73 index 8e203b7985..0499675476 100644 --- a/checks/check_extra73 +++ b/checks/check_extra73 @@ -10,6 +10,7 @@ # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. + CHECK_ID_extra73="7.3,7.03" CHECK_TITLE_extra73="[extra73] Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark)" CHECK_SCORED_extra73="NOT_SCORED" @@ -176,5 +177,5 @@ extra73(){ # else # textOK "$BUCKET_LOCATION: $bucket bucket is not open" "$BUCKET_LOCATION" # fi -# rm -fr $TEMP_POLICY_FILE +# rm -f $TEMP_POLICY_FILE # } diff --git a/checks/check_extra734 b/checks/check_extra734 index 26c5fbcf2e..bebd2bfcaa 100644 --- a/checks/check_extra734 +++ b/checks/check_extra734 @@ -42,15 +42,15 @@ extra734(){ TEMP_SSE_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-${bucket}.policy.XXXXXXXXXX) # get bucket policy - $AWSCLI s3api get-bucket-policy $PROFILE_OPT --bucket $bucket --output text --query Policy > $TEMP_SSE_POLICY_FILE 2> /dev/null + $AWSCLI s3api get-bucket-policy $PROFILE_OPT --bucket $bucket --output text --query Policy > $TEMP_SSE_POLICY_FILE 2>&1 if [[ $(grep AccessDenied $TEMP_SSE_POLICY_FILE) ]]; then textFail "Access Denied Trying to Get Bucket Policy for $bucket" - rm -fr $TEMP_SSE_POLICY_FILE + rm -f $TEMP_SSE_POLICY_FILE continue fi if [[ $(grep NoSuchBucketPolicy $TEMP_SSE_POLICY_FILE) ]]; then textFail "No bucket policy for $bucket" - rm -fr $TEMP_SSE_POLICY_FILE + rm -f $TEMP_SSE_POLICY_FILE continue fi @@ -58,14 +58,14 @@ extra734(){ CHECK_BUCKET_SSE_POLICY_PRESENT=$(cat $TEMP_SSE_POLICY_FILE | jq --arg arn "arn:aws:s3:::${bucket}/*" '.Statement[]|select(.Effect=="Deny" and ((.Principal|type == "object") and .Principal.AWS == "*") or ((.Principal|type == "string") and .Principal == "*") and .Action=="s3:PutObject" and .Resource==$arn and .Condition.StringEquals."s3:x-amz-server-side-encryption" != null)') if [[ $CHECK_BUCKET_SSE_POLICY_PRESENT == "" ]]; then textFail "Bucket $bucket does not enforce encryption!" - rm -fr $TEMP_SSE_POLICY_FILE + rm -f $TEMP_SSE_POLICY_FILE continue fi CHECK_BUCKET_SSE_POLICY_VALUE=$(echo "$CHECK_BUCKET_SSE_POLICY_PRESENT" | jq -r '.Condition.StringNotEquals."s3:x-amz-server-side-encryption"') textPass "Bucket $bucket has S3 bucket policy to enforce encryption with $CHECK_BUCKET_SSE_POLICY_VALUE" - rm -fr $TEMP_SSE_POLICY_FILE + rm -f $TEMP_SSE_POLICY_FILE done else diff --git a/checks/check_extra764 b/checks/check_extra764 index a479531e87..62a398ffac 100644 --- a/checks/check_extra764 +++ b/checks/check_extra764 @@ -26,10 +26,12 @@ extra764(){ $AWSCLI s3api get-bucket-policy $PROFILE_OPT --bucket $bucket --output text --query Policy > $TEMP_STP_POLICY_FILE 2>&1 if [[ $(grep AccessDenied $TEMP_STP_POLICY_FILE) ]]; then textFail "Access Denied Trying to Get Bucket Policy for $bucket" + rm -f $TEMP_STP_POLICY_FILE continue fi if [[ $(grep NoSuchBucketPolicy $TEMP_STP_POLICY_FILE) ]]; then textFail "No bucket policy for $bucket" + rm -f $TEMP_STP_POLICY_FILE continue fi diff --git a/checks/check_extra77 b/checks/check_extra77 index 886184c833..cfd1078a15 100644 --- a/checks/check_extra77 +++ b/checks/check_extra77 @@ -28,16 +28,18 @@ extra77(){ continue fi if [[ ! -z "$LIST_ECR_REPOS" ]]; then - TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-ecr-repo.policy.XXXXXXXXXX) for repo in $LIST_ECR_REPOS; do + TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-ecr-repo.policy.XXXXXXXXXX) $AWSCLI ecr get-repository-policy $PROFILE_OPT --region $region --repository-name $repo --query "policyText" --output text > $TEMP_POLICY_FILE 2>&1 if [[ $(grep AccessDenied $TEMP_POLICY_FILE) ]]; then textFail "$region: $repo Access Denied for get-repository-policy" + rm -f $TEMP_POLICY_FILE continue fi # https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html - "By default, only the repository owner has access to a repository." if [[ $(grep RepositoryPolicyNotFoundException $TEMP_POLICY_FILE) ]]; then textPass "$region: $repo is not open" "$region" + rm -f $TEMP_POLICY_FILE continue fi # check if the policy has Principal as * @@ -47,8 +49,8 @@ extra77(){ else textPass "$region: $repo is not open" "$region" fi + rm -f $TEMP_POLICY_FILE done - rm -f $TEMP_POLICY_FILE else textInfo "$region: No ECR repositories found" "$region" fi diff --git a/prowler b/prowler index fa8fc1e1cf..397055ba27 100755 --- a/prowler +++ b/prowler @@ -162,6 +162,9 @@ while getopts ":hlLkqp:r:c:g:f:m:M:E:enbVsx:" OPTION; do esac done +# Clean up any temp files when prowler quits unexpectedly +trap "{ rm -f /tmp/prowler*.policy.*; }" EXIT + . $PROWLER_DIR/include/colors . $PROWLER_DIR/include/os_detector . $PROWLER_DIR/include/aws_profile_loader @@ -194,12 +197,12 @@ for checks in $(ls $PROWLER_DIR/checks/check*|grep -v check_sample); do . "$checks" done -# include checks if external folder is specified -if [[ $EXTERNAL_CHECKS_PATH ]]; then +# include checks if external folder is specified +if [[ $EXTERNAL_CHECKS_PATH ]]; then for checks in $(ls $EXTERNAL_CHECKS_PATH/check*); do . "$checks" done -fi +fi # Function to show the title of the check # using this way instead of arrays to keep bash3 (osx) and bash4(linux) compatibility @@ -292,7 +295,7 @@ execute_group_by_id() { done } -# Function to execute all checks in all groups except extras if -e is invoked +# Function to execute all checks in all groups except extras if -e is invoked execute_all() { for i in "${!GROUP_TITLE[@]}"; do if [[ $EXTRAS ]]; then @@ -309,7 +312,7 @@ show_all_titles() { MAIN_GROUPS=(1 2 3 4 7) for i in "${MAIN_GROUPS[@]}"; do show_group_title $i - # Display the title of the checks in groups 1,2,3,4 and 7 + # Display the title of the checks in groups 1,2,3,4 and 7 # Any other group has checks in these groups IFS=',' read -ra CHECKS <<< ${GROUP_CHECKS[$i]} for j in ${CHECKS[@]}; do From b22b0af2cebdf1250586b64b3db7107efe664a98 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 30 Dec 2019 14:20:50 -0500 Subject: [PATCH 11/25] Misc fixes to check extra764 --- checks/check_extra764 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/check_extra764 b/checks/check_extra764 index a479531e87..86d1e47f3e 100644 --- a/checks/check_extra764 +++ b/checks/check_extra764 @@ -34,7 +34,7 @@ extra764(){ fi # https://aws.amazon.com/premiumsupport/knowledge-center/s3-bucket-policy-for-config-rule/ - CHECK_BUCKET_STP_POLICY_PRESENT=$(cat $TEMP_STP_POLICY_FILE | jq --arg arn "arn:aws:s3:::${bucket}/*" '.Statement[]|select((((.Principal|type == "object") and .Principal.AWS == "*") or ((.Principal|type == "string") and .Principal == "*")) and .Action=="s3:*" and (.Resource|type == "array") and (.Resource|map({(.):0})[]|has($arn)) and (.Resource|map({(.):0})[]|has($arn+"/*")) and .Condition.Bool."aws:SecureTransport" == "false")') + CHECK_BUCKET_STP_POLICY_PRESENT=$(cat $TEMP_STP_POLICY_FILE | jq --arg arn "arn:aws:s3:::${bucket}" '.Statement[]|select(((.Principal|type == "string") and .Principal == "*") and .Action=="s3:*" and (.Resource|type == "array") and (.Resource|map({(.):0})[]|has($arn)) and (.Resource|map({(.):0})[]|has($arn+"/*")) and .Condition.Bool."aws:SecureTransport" == "false")') if [[ $CHECK_BUCKET_STP_POLICY_PRESENT ]]; then textPass "Bucket $bucket has S3 bucket policy to deny requests over insecure transport" else From 7d324bed6568789213b99f6b96a7904fa5eeff3c Mon Sep 17 00:00:00 2001 From: root Date: Mon, 30 Dec 2019 14:43:51 -0500 Subject: [PATCH 12/25] Resolve issue with not_available state in results --- checks/check_extra726 | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/checks/check_extra726 b/checks/check_extra726 index 388d01a16e..b2eee1a401 100644 --- a/checks/check_extra726 +++ b/checks/check_extra726 @@ -29,12 +29,23 @@ extra726(){ for checkid in $TA_CHECKS_ID; do TA_CHECKS_NAME=$($AWSCLI support describe-trusted-advisor-checks --language en $PROFILE_OPT --region us-east-1 --query "checks[?id==\`$checkid\`].{name:name}[*]" --output text) QUERY_TA_CHECK_RESULT=$($AWSCLI support describe-trusted-advisor-check-result --check-id $checkid --language en $PROFILE_OPT --region us-east-1 --query 'result.status' --output text) - if [[ $(echo $QUERY_TA_CHECK_RESULT | grep ok) ]]; then - textPass "Trusted Advisor check $TA_CHECKS_NAME is in state $QUERY_TA_CHECK_RESULT" - elif [[ $(echo $QUERY_TA_CHECK_RESULT | grep warning) ]]; then - textInfo "Trusted Advisor check $TA_CHECKS_NAME is in state $QUERY_TA_CHECK_RESULT" - else - textFail "Trusted Advisor check $TA_CHECKS_NAME is in state $QUERY_TA_CHECK_RESULT" - fi + # Possible results - https://docs.aws.amazon.com/cli/latest/reference/support/describe-trusted-advisor-check-result.html + case "$QUERY_TA_CHECK_RESULT" in + "ok") + textPass "Trusted Advisor check $TA_CHECKS_NAME is in ok state $QUERY_TA_CHECK_RESULT" + ;; + "error") + textFail "Trusted Advisor check $TA_CHECKS_NAME is in error state $QUERY_TA_CHECK_RESULT" + ;; + "warning") + textInfo "Trusted Advisor check $TA_CHECKS_NAME is in warning state $QUERY_TA_CHECK_RESULT" + ;; + "not_available") + textInfo "Trusted Advisor check $TA_CHECKS_NAME is in not_available state $QUERY_TA_CHECK_RESULT" + ;; + "*") + textFail "Trusted Advisor check $TA_CHECKS_NAME is in unknown state $QUERY_TA_CHECK_RESULT" + ;; + esac done } From c2f541134b5aa65b5064209389f395420836a049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=E1=BB=8D=20Anh=20=C4=90=E1=BB=A9c?= Date: Wed, 8 Jan 2020 11:13:25 +0700 Subject: [PATCH 13/25] Update README.md Add jq package in requirements --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 8a77917516..61bb9089f0 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,11 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX AWS-CLI can be also installed it using "brew", "apt", "yum" or manually from , but `ansi2html` and `detect-secrets` has to be installed using `pip`. You will need to install `jq` to get more accuracy in some checks. +- Make sure jq is installed: + ```sh + sudo apt install jq + ``` + - Previous steps, from your workstation: ```sh From 49ec898b9e413815d4de0d148da10c1173b34131 Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Wed, 8 Jan 2020 09:14:21 +0100 Subject: [PATCH 14/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61bb9089f0..2cc37a20d4 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX AWS-CLI can be also installed it using "brew", "apt", "yum" or manually from , but `ansi2html` and `detect-secrets` has to be installed using `pip`. You will need to install `jq` to get more accuracy in some checks. -- Make sure jq is installed: +- Make sure jq is installed (example below with "apt" but use a valid package manager for your OS): ```sh sudo apt install jq ``` From cea0cfb47d46450bc0638b0f25487260c386bf00 Mon Sep 17 00:00:00 2001 From: bgeesaman Date: Wed, 8 Jan 2020 20:21:18 -0500 Subject: [PATCH 15/25] Prevent colorization on Failed and Info --- include/outputs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/outputs b/include/outputs index a33b1dca2d..b18ac68352 100644 --- a/include/outputs +++ b/include/outputs @@ -76,7 +76,7 @@ textInfo(){ else REPREGION=$REGION fi - jq -c \ + jq -M -c \ --arg PROFILE "$PROFILE" \ --arg ACCOUNT_NUM "$ACCOUNT_NUM" \ --arg TITLE_TEXT "$TITLE_TEXT" \ @@ -119,7 +119,7 @@ textFail(){ else REPREGION=$REGION fi - jq -c \ + jq -M -c \ --arg PROFILE "$PROFILE" \ --arg ACCOUNT_NUM "$ACCOUNT_NUM" \ --arg TITLE_TEXT "$TITLE_TEXT" \ From 4c1d1887e4e0fd85747fdb068ab8682e88715f8b Mon Sep 17 00:00:00 2001 From: root Date: Fri, 10 Jan 2020 15:47:15 -0500 Subject: [PATCH 16/25] Add Prowler check for unused elastic IP addresses --- checks/check_extra772 | 36 ++++++++++++++++++++++++++++++++++++ groups/group7_extras | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 checks/check_extra772 diff --git a/checks/check_extra772 b/checks/check_extra772 new file mode 100644 index 0000000000..83f3cdd5f2 --- /dev/null +++ b/checks/check_extra772 @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +CHECK_ID_extra772="7.72" +CHECK_TITLE_extra772="[extra772] Check if elastic IPs are unused (Not Scored) (Not part of CIS benchmark)" +CHECK_SCORED_extra772="NOT_SCORED" +CHECK_TYPE_extra772="EXTRA" +CHECK_ALTERNATE_check772="extra772" + +extra772(){ + for region in $REGIONS; do + EIP_DUMP=$($AWSCLI ec2 describe-addresses ${PROFILE_OPT} --region $region) + EIP_LIST=$(echo $EIP_DUMP | jq -r '.Addresses[].AllocationId') + if [[ $EIP_LIST ]]; then + for eip in $EIP_LIST; do + ASSOCIATION_ID=$(echo $EIP_DUMP | jq -r --arg i "$eip" '.Addresses[]|select(.AllocationId==$i)|.AssociationId') + if [[ "$ASSOCIATION_ID" == "null" ]]; then + textFail "$region: EIP $eip is unused" $region + else + textPass "$region: EIP $eip is used" $region + fi + done + else + textInfo "$region: No Elastic IPs found" $region + fi + done +} diff --git a/groups/group7_extras b/groups/group7_extras index 12afb988ee..b452cecdc1 100644 --- a/groups/group7_extras +++ b/groups/group7_extras @@ -15,7 +15,7 @@ GROUP_ID[7]='extras' GROUP_NUMBER[7]='7.0' GROUP_TITLE[7]='Extras - [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,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra737,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra756,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra766,extra767,extra768,extra769,extra770,extra771' +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,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra737,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra756,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra766,extra767,extra768,extra769,extra770,extra771,extra772' # Extras 759 and 760 (lambda variables and code secrets finder are not included) # to run detect-secrets use `./prowler -g secrets` From f006c81e6a888fd6bd3cc7bcaf62fefea7401acb Mon Sep 17 00:00:00 2001 From: Fayez Barbari Date: Mon, 20 Jan 2020 14:36:01 -0600 Subject: [PATCH 17/25] Use custom aws profile with Role to assume --- include/assume_role | 12 ++++++++---- prowler | 7 +++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/include/assume_role b/include/assume_role index 0ff9761051..4fedfb3dc7 100644 --- a/include/assume_role +++ b/include/assume_role @@ -27,7 +27,7 @@ if [[ $ACCOUNT_TO_ASSUME ]]; then TEMP_STS_ASSUMED_FILE=$(mktemp -t prowler.sts_assumed-XXXXXX) # assume role command - $AWSCLI sts assume-role --role-arn arn:aws:iam::$ACCOUNT_TO_ASSUME:role/$ROLE_TO_ASSUME \ + $AWSCLI $PROFILE_OPT sts assume-role --role-arn arn:aws:iam::$ACCOUNT_TO_ASSUME:role/$ROLE_TO_ASSUME \ --role-session-name ProwlerAssessmentSession \ --duration-seconds $SESSION_DURATION_TO_ASSUME > $TEMP_STS_ASSUMED_FILE @@ -41,9 +41,13 @@ if [[ $ACCOUNT_TO_ASSUME ]]; then exit 1 fi + # The profile shouldn't be used for CLI + PROFILE="" + PROFILE_OPT="" + # set env variables with assumed role credentials - AWS_ACCESS_KEY_ID=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.AccessKeyId') - AWS_SECRET_ACCESS_KEY=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.SecretAccessKey') - AWS_SESSION_TOKEN=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.SessionToken') + export AWS_ACCESS_KEY_ID=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.AccessKeyId') + export AWS_SECRET_ACCESS_KEY=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.SecretAccessKey') + export AWS_SESSION_TOKEN=$(cat $TEMP_STS_ASSUMED_FILE | jq -r '.Credentials.SessionToken') rm -fr $TEMP_STS_ASSUMED_FILE fi diff --git a/prowler b/prowler index 57315da3ba..e4e096d000 100755 --- a/prowler +++ b/prowler @@ -439,6 +439,13 @@ if [[ $CHECK_ID ]];then exit $EXITCODE fi +if [[ $ACCOUNT_TO_ASSUME ]]; then + # unset env variables with assumed role credentials + unset AWS_ACCESS_KEY_ID + unset AWS_SECRET_ACCESS_KEY + unset AWS_SESSION_TOKEN +fi + execute_all scoring From 2f17cfbc302ebedd2a1f0156cc7272fb6ad8026f Mon Sep 17 00:00:00 2001 From: Fayez Barbari Date: Mon, 20 Jan 2020 17:14:52 -0600 Subject: [PATCH 18/25] Check if CloudFront is using a WAF --- checks/check_extra772 | 34 ++++++++++++++++++++++++++++++++++ groups/group7_extras | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 checks/check_extra772 diff --git a/checks/check_extra772 b/checks/check_extra772 new file mode 100644 index 0000000000..d08515a195 --- /dev/null +++ b/checks/check_extra772 @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +CHECK_ID_extra772="7.72" +CHECK_TITLE_extra772="[extra772] Check if CloudFront distributions are using WAF (Not Scored) (Not part of CIS benchmark)" +CHECK_SCORED_extra772="NOT_SCORED" +CHECK_TYPE_extra772="EXTRA" +CHECK_ALTERNATE_check772="extra772" + +extra772(){ + # "Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark)" + LIST_OF_DISTRIBUTIONS=$($AWSCLI cloudfront list-distributions $PROFILE_OPT --query 'DistributionList.Items[].Id' --output text | grep -v "^None") + if [[ $LIST_OF_DISTRIBUTIONS ]]; then + for dist in $LIST_OF_DISTRIBUTIONS; do + WEB_ACL_ID=$($AWSCLI cloudfront get-distribution $PROFILE_OPT --id "$dist" --query 'Distribution.DistributionConfig.WebACLId' --output text) + if [[ $WEB_ACL_ID ]]; then + textPass "CloudFront distribution $dist is using AWS WAF web ACL $WEB_ACL_ID" + else + textFail "CloudFront distribution $dist is not using AWS WAF web ACL" + fi + done + else + textInfo "No CloudFront distributions found" + fi +} diff --git a/groups/group7_extras b/groups/group7_extras index 12afb988ee..b452cecdc1 100644 --- a/groups/group7_extras +++ b/groups/group7_extras @@ -15,7 +15,7 @@ GROUP_ID[7]='extras' GROUP_NUMBER[7]='7.0' GROUP_TITLE[7]='Extras - [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,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra737,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra756,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra766,extra767,extra768,extra769,extra770,extra771' +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,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra737,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra756,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra766,extra767,extra768,extra769,extra770,extra771,extra772' # Extras 759 and 760 (lambda variables and code secrets finder are not included) # to run detect-secrets use `./prowler -g secrets` From 41ccd4517b49c8978446d0d589ddb1fd958ed6ed Mon Sep 17 00:00:00 2001 From: "C.J" <31103058+zfLQ2qx2@users.noreply.github.com> Date: Sat, 25 Jan 2020 15:22:39 -0500 Subject: [PATCH 19/25] Add additional error checking to address issue 459 --- checks/check23 | 10 +++++++--- checks/check26 | 15 +++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/checks/check23 b/checks/check23 index 63ccd4d75e..53d1b6f6ba 100644 --- a/checks/check23 +++ b/checks/check23 @@ -17,10 +17,14 @@ CHECK_ALTERNATE_check203="check23" check23(){ # "Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)" CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text $PROFILE_OPT --region $REGION) - if [[ $CLOUDTRAILBUCKET ]];then + 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 + 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 2>&1) + if [[ $(echo "$CLOUDTRAILBUCKET_HASALLPERMISIONS" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to Get Bucket Acl for $bucket" + continue + fi + if [[ $CLOUDTRAILBUCKET_HASALLPERMISIONS ]]; then textFail "check your $bucket CloudTrail bucket ACL and Policy!" else textPass "Bucket $bucket is set correctly" diff --git a/checks/check26 b/checks/check26 index 0c6dfa0c3b..83395527a8 100644 --- a/checks/check26 +++ b/checks/check26 @@ -19,16 +19,20 @@ check26(){ CLOUDTRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region "$REGION" --query 'trailList[*].Name' --output text| tr '\011' '\012' | awk -F: '{print $1}') - if [[ $CLOUDTRAILS ]];then + if [[ $CLOUDTRAILS ]]; then for trail in $CLOUDTRAILS; do CLOUDTRAIL_ACCOUNT_ID=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region "$REGION" --query 'trailList[*].TrailARN' --output text | tr '\011' '\012' | grep "$trail" | awk -F: '{ print $5 }' | head -n 1) CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].[Name, S3BucketName]' --output text | tr '\011' ':' | grep "$trail" | awk -F: '{ print $2 }' ) - if [[ $CLOUDTRAILBUCKET ]];then + if [[ $CLOUDTRAILBUCKET ]]; then bucket=$CLOUDTRAILBUCKET - if [ "$CLOUDTRAIL_ACCOUNT_ID" == "$ACCOUNT_NUM" ];then - 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 + if [ "$CLOUDTRAIL_ACCOUNT_ID" == "$ACCOUNT_NUM" ]; then + CLOUDTRAILBUCKET_LOGENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket $PROFILE_OPT --region $REGION --query 'LoggingEnabled.TargetBucket' --output text 2>&1) + if [[ $(echo "$CLOUDTRAILBUCKET_LOGENABLED" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to Get Bucket Logging for $bucket" + continue + fi + if [[ $CLOUDTRAILBUCKET_LOGENABLED != "null" ]]; then textPass "Bucket access logging enabled in CloudTrail S3 bucket $bucket for $trail" else textFail "Bucket access logging is not enabled in CloudTrail S3 bucket $bucket for $trail" @@ -36,7 +40,6 @@ check26(){ else textInfo "CloudTrail S3 bucket $bucket for trail $trail is not in current account" fi - else textFail "CloudTrail bucket not found!" fi From 9fc0f6c61c585684a2eb675f42cc4b24cb85a1d2 Mon Sep 17 00:00:00 2001 From: "C.J" <31103058+zfLQ2qx2@users.noreply.github.com> Date: Sat, 25 Jan 2020 15:29:05 -0500 Subject: [PATCH 20/25] Remove check 766, dupe of check 765 --- checks/check_extra766 | 46 ------------------------------------------- groups/group7_extras | 2 +- 2 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 checks/check_extra766 diff --git a/checks/check_extra766 b/checks/check_extra766 deleted file mode 100644 index 2382f4ace8..0000000000 --- a/checks/check_extra766 +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -# Remediation: -# -# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html#configuring-instance-metadata-service -# -# aws ecr put-image-scanning-configuration \ -# --region \ -# --repository-name \ -# --image-scanning-configuration scanOnPush=true - - -CHECK_ID_extra766="7.66" -CHECK_TITLE_extra766="[extra766] Check if ECR image scan on push is enabled (Not Scored) (Not part of CIS benchmark)" -CHECK_SCORED_extra766="NOT_SCORED" -CHECK_TYPE_extra766="EXTRA" -CHECK_ALTERNATE_check766="extra766" - -extra766(){ - for regx in $REGIONS; do - LIST_ECR_REPOS=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $regx --query "repositories[*].[repositoryName]" --output text 2>&1) - if [[ $LIST_ECR_REPOS ]]; then - for repo in $LIST_ECR_REPOS; do - SCAN_ENABLED=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $regx --query "repositories[?repositoryName==\`$repo\`].[imageScanningConfiguration.scanOnPush]" --output text|grep True) - if [[ $SCAN_ENABLED ]];then - textPass "$regx: ECR repository $repo has scan on push enabled" "$regx" - else - textFail "$regx: ECR repository $repo has scan on push disabled!" "$regx" - fi - done - else - textInfo "$regx: No ECR repositories found" "$regx" - fi - done -} diff --git a/groups/group7_extras b/groups/group7_extras index 12afb988ee..30bd2e77f6 100644 --- a/groups/group7_extras +++ b/groups/group7_extras @@ -15,7 +15,7 @@ GROUP_ID[7]='extras' GROUP_NUMBER[7]='7.0' GROUP_TITLE[7]='Extras - [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,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra737,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra756,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra766,extra767,extra768,extra769,extra770,extra771' +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,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra737,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra756,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra767,extra768,extra769,extra770,extra771' # Extras 759 and 760 (lambda variables and code secrets finder are not included) # to run detect-secrets use `./prowler -g secrets` From f735de8836020a84bcaa8d57afed78646e8a31ab Mon Sep 17 00:00:00 2001 From: "C.J" <31103058+zfLQ2qx2@users.noreply.github.com> Date: Sun, 26 Jan 2020 03:00:45 -0500 Subject: [PATCH 21/25] Rewrite of check extra73 --- checks/check_extra73 | 245 +++++++++++++++++++------------------------ 1 file changed, 109 insertions(+), 136 deletions(-) diff --git a/checks/check_extra73 b/checks/check_extra73 index 0499675476..a587a60f6a 100644 --- a/checks/check_extra73 +++ b/checks/check_extra73 @@ -19,46 +19,65 @@ CHECK_ALTERNATE_extra703="extra73" CHECK_ALTERNATE_check73="extra73" CHECK_ALTERNATE_check703="extra73" -# Improved and simplified check on Nov 18th 2018 due to a new bucket attribute -# called PolicyStatus, not available in all regions yet. - -# extra73(){ -# 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 2>&1) -# if [[ $(echo "$BUCKET_LOCATION" | grep AccessDenied) ]]; then -# textFail "Access Denied Trying to Get Bucket Location for $bucket" -# continue -# fi -# if [[ "None" == $BUCKET_LOCATION ]]; then -# BUCKET_LOCATION="us-east-1" -# fi -# if [[ "EU" == $BUCKET_LOCATION ]]; then -# BUCKET_LOCATION="eu-west-1" -# fi +# Verified with AWS support that if get-bucket-acl doesn't return a grant +# for All and get-bucket-policy-status returns IsPublic false or bad request +# (no policy) then the bucket can be considered not public - though +# individual objects may still be. If in addition put-public-access-block is +# used to set IgnorePublicAcls and RestrictPublicBuckets to true then that +# causes Amazon S3 to ignore all public ACLs on a bucket and any objects that +# it contains. # -# BUCKET_POLICY_STATUS=$($AWSCLI s3api get-bucket-policy-status --bucket $bucket --query PolicyStatus.IsPublic --output text | grep False) -# if [[ $BUCKET_POLICY_STATUS ]];then -# textFail "$BUCKET_LOCATION: $bucket bucket is Public!" "$BUCKET_LOCATION" -# else -# textPass "$BUCKET_LOCATION: $bucket bucket is not Public" "$BUCKET_LOCATION" -# fi -# done -# } - +# This check does not address legacy ACLs or policies that would give +# public access if not blocked at account or bucket level, instead it tries +# to reward the use of more broadly restrictive controls with quicker and less +# computational intensive checks. +# +# If we are assembling an inventory then maybe that is not what we want but +# for day to day usage that is probably desirable. extra73(){ textInfo "Looking for open S3 Buckets (ACLs and Policies) in all regions... " - ALL_BUCKETS_LIST=$($AWSCLI s3api list-buckets --query 'Buckets[*].{Name:Name}' $PROFILE_OPT --output text) + + # + # If public ACLs disabled at account level then look no further + # + ACCOUNT_PUBLIC_ACCESS_BLOCK=$($AWSCLI s3control get-public-access-block $PROFILE_OPT --region $REGION --account-id $ACCOUNT_NUM --output json 2>&1) + if [[ $(echo "$ACCOUNT_PUBLIC_ACCESS_BLOCK" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to Get Public Access Block for $bucket" + return + fi + if [[ $(echo "$ACCOUNT_PUBLIC_ACCESS_BLOCK" | grep NoSuchPublicAccessBlockConfiguration) ]]; then + ACCOUNTIGNOREPUBLICACLS="" + ACCOUNTRESTRICTPUBLICBUCKETS="" + else + ACCOUNTIGNOREPUBLICACLS=$(echo "$ACCOUNT_PUBLIC_ACCESS_BLOCK" | jq -r '.PublicAccessBlockConfiguration.IgnorePublicAcls') + ACCOUNTRESTRICTPUBLICBUCKETS=$(echo "$ACCOUNT_PUBLIC_ACCESS_BLOCK" | jq -r '.PublicAccessBlockConfiguration.RestrictPublicBuckets') + fi + if [[ $ACCOUNTIGNOREPUBLICACLS == "true" && $ACCOUNTRESTRICTPUBLICBUCKETS == "true" ]]; then + textPass "All S3 public access blocked at account level" + return + fi + + # + # Otherwise start to iterate bucket + # + ALL_BUCKETS_LIST=$($AWSCLI s3api list-buckets --query 'Buckets[*].{Name:Name}' $PROFILE_OPT --output text 2>&1) + if [[ $(echo "$ALL_BUCKETS_LIST" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to List Buckets" + return + fi + if [[ "$ALL_BUCKETS_LIST" == "" ]]; then + textInfo "No buckets found" + return + fi for bucket in $ALL_BUCKETS_LIST; do - # 3 Different problems, let's show only 1 finding all together - S3_FINDING_ALLUSERS_ACL="Ok" - S3_FINDING_AUTHUSERS_ACL="Ok" - S3_FINDING_POLICY="Ok" - - # LOCATION + # + # LOCATION - requests referencing buckets created after March 20, 2019 + # must be made to S3 endpoints in the same region as the bucket was + # created. + # BUCKET_LOCATION=$($AWSCLI s3api get-bucket-location --bucket $bucket $PROFILE_OPT --output text 2>&1) if [[ $(echo "$BUCKET_LOCATION" | grep AccessDenied) ]]; then textFail "Access Denied Trying to Get Bucket Location for $bucket" @@ -71,111 +90,65 @@ extra73(){ BUCKET_LOCATION="eu-west-1" fi - # EXPLICIT DENY - CHEK_FOR_EXPLICIT_DENY=$($AWSCLI s3api get-bucket-acl $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --output text 2>&1) - if [[ $(echo "$CHEK_FOR_EXPLICIT_DENY" | grep AccessDenied) ]] ; then - textInfo "$BUCKET_LOCATION: $bucket have an explicit Deny. Not possible to get ACL." "$bucket" "$BUCKET_LOCATION" + # + # If public ACLs disabled at bucket level then look no further + # + BUCKET_PUBLIC_ACCESS_BLOCK=$($AWSCLI s3api get-public-access-block $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --output json 2>&1) + if [[ $(echo "$BUCKET_PUBLIC_ACCESS_BLOCK" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to Get Public Access Block for $bucket" + continue + fi + if [[ $(echo "$BUCKET_PUBLIC_ACCESS_BLOCK" | grep NoSuchPublicAccessBlockConfiguration) ]]; then + BUCKETIGNOREPUBLICACLS="" + BUCKETRESTRICTPUBLICBUCKETS="" else - # PUBLIC BLOCK - # https://docs.aws.amazon.com/cli/latest/reference/s3api/get-public-access-block.html - BUCKET_PUBLIC_BLOCK=$($AWSCLI s3api get-public-access-block --bucket $bucket $PROFILE_OPT --region $BUCKET_LOCATION 2>/dev/null) - BUCKET_PUBLIC_BLOCK_IGNOREPUBLICACL=$(echo $BUCKET_PUBLIC_BLOCK | jq .PublicAccessBlockConfiguration.BlockPublicAcls 2>/dev/null) - BUCKET_PUBLIC_BLOCK_BLOCKPUBLICPOLICY=$(echo $BUCKET_PUBLIC_BLOCK | jq .PublicAccessBlockConfiguration.BlockPublicPolicy 2>/dev/null) - BUCKET_PUBLIC_BLOCK_BLOCKPUBLICACLS=$(echo $BUCKET_PUBLIC_BLOCK | jq .PublicAccessBlockConfiguration.BlockPublicAcls 2>/dev/null) - BUCKET_PUBLIC_BLOCK_RESTRICPUBLICBUCKET=$(echo $BUCKET_PUBLIC_BLOCK | jq .PublicAccessBlockConfiguration.RestrictPublicBuckets 2>/dev/null) - if [[ $BUCKET_PUBLIC_BLOCK_IGNOREPUBLICACL == "true" ]] && [[ $BUCKET_PUBLIC_BLOCK_BLOCKPUBLICPOLICY == "true" ]] && [[ $BUCKET_PUBLIC_BLOCK_BLOCKPUBLICACLS == "true" ]] && [[ $BUCKET_PUBLIC_BLOCK_RESTRICPUBLICBUCKET == "true" ]]; then - textPass "$BUCKET_LOCATION: $bucket bucket is public blocked (public-access-block)" "$BUCKET_LOCATION" - else - ## ACL - # 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) - ## POLICY - BUCKET_POLICY_STATUS=$($AWSCLI s3api get-bucket-policy-status $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --query PolicyStatus.IsPublic --output text 2>/dev/null) - if [[ $CHECK_BUCKET_ALLUSERS_ACL || $CHECK_BUCKET_AUTHUSERS_ACL || $CHECK_BUCKET_ALLUSERS_POLICY == "True" ]]; then - if [[ $CHECK_BUCKET_ALLUSERS_ACL || $CHECK_BUCKET_AUTHUSERS_ACL ]] && [[ $BUCKET_PUBLIC_BLOCK_IGNOREPUBLICACL != "true" ]];then - if [[ $CHECK_BUCKET_ALLUSERS_ACL ]];then - S3_FINDING_ALLUSERS_ACL="bucket ACL is open to the Internet (Everyone) with permissions: $CHECK_BUCKET_ALLUSERS_ACL_SINGLE_LINE" - fi - if [[ $CHECK_BUCKET_AUTHUSERS_ACL ]];then - S3_FINDING_AUTHUSERS_ACL="bucket ACL is open to Authenticated users (Any AWS user) with permissions: $CHECK_BUCKET_AUTHUSERS_ACL_SINGLE_LINE" - fi - fi - if [[ $BUCKET_PUBLIC_BLOCK_RESTRICPUBLICBUCKET != "true" ]] && [[ $BUCKET_POLICY_STATUS == "True" ]]; then - # Here comes the magic: Find Statement Allow, Principal * and No Condition - BUCKET_POLICY_ALLOW_ALL_WITHOUT_CONDITION=$($AWSCLI s3api get-bucket-policy $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket \ - | jq '.Policy | fromjson' | jq '.Statement[] | select(.Effect=="Allow") | select(.Principal=="*" or .Principal.AWS=="*" or .Principal.CanonicalUser=="*") | select(has("Condition") | not)') - if [[ $BUCKET_POLICY_ALLOW_ALL_WITHOUT_CONDITION ]]; then - # Let's do more magic and identify who can do what - BUCKET_POLICY_ALLOW_ALL_WITHOUT_CONDITION_DETAILS=$(echo $BUCKET_POLICY_ALLOW_ALL_WITHOUT_CONDITION \ - | jq '"[Principal: " + (.Principal|tostring) + " Action: " + (.Action|tostring) + "]"' ) - S3_FINDING_POLICY="bucket policy allow perform actions: $BUCKET_POLICY_ALLOW_ALL_WITHOUT_CONDITION_DETAILS" - else - textPass "$BUCKET_LOCATION: $bucket bucket policy with conditions" "$BUCKET_LOCATION" - fi - fi - else - textPass "$BUCKET_LOCATION: $bucket bucket is not open" "$bucket" "$BUCKET_LOCATION" - fi - fi + BUCKETIGNOREPUBLICACLS=$(echo "$BUCKET_PUBLIC_ACCESS_BLOCK" | jq -r '.PublicAccessBlockConfiguration.IgnorePublicAcls') + BUCKETRESTRICTPUBLICBUCKETS=$(echo "$BUCKET_PUBLIC_ACCESS_BLOCK" | jq -r '.PublicAccessBlockConfiguration.RestrictPublicBuckets') fi - if [[ $S3_FINDING_ALLUSERS_ACL != "Ok" ]] || [[ $S3_FINDING_AUTHUSERS_ACL != "Ok" ]] || [[ $S3_FINDING_POLICY != "Ok" ]] ; then - textFail "$BUCKET_LOCATION: (bucket: $bucket) ALLUSERS_ACL: $S3_FINDING_ALLUSERS_ACL | AUTHUSERS_ACL: $S3_FINDING_AUTHUSERS_ACL | BUCKET_POLICY: $S3_FINDING_POLICY" "$BUCKET_LOCATION" + if [[ $BUCKETIGNOREPUBLICACLS == "true" && $BUCKETRESTRICTPUBLICBUCKETS == "true" ]]; then + textPass "$BUCKET_LOCATION: $bucket bucket is not Public" "$BUCKET_LOCATION" + continue fi + + # + # Check for public ACL grants + # + BUCKET_ACL=$($AWSCLI s3api get-bucket-acl $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --output json 2>&1) + if [[ $(echo "$BUCKET_ACL" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to Get Bucket Acl for $bucket" + continue + fi + + ALLUSERS_ACL=$(echo "$BUCKET_ACL" | jq '.Grants[]|select(.Grantee.URI != null)|select(.Grantee.URI | endswith("/AllUsers"))') + if [[ $ALLUSERS_ACL != "" ]]; then + textFail "$BUCKET_LOCATION: $bucket bucket is Public!" "$BUCKET_LOCATION" + continue + fi + + AUTHENTICATEDUSERS_ACL=$(echo "$BUCKET_ACL" | jq '.Grants[]|select(.Grantee.URI != null)|select(.Grantee.URI | endswith("/AuthenticatedUsers"))') + if [[ $AUTHENTICATEDUSERS_ACL != "" ]]; then + textFail "$BUCKET_LOCATION: $bucket bucket is Public!" "$BUCKET_LOCATION" + continue + fi + + # + # Check for public access in policy + # + BUCKET_POLICY_STATUS=$($AWSCLI s3api get-bucket-policy-status $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --query PolicyStatus.IsPublic --output text 2>&1) + if [[ $(echo "$BUCKET_POLICY_STATUS" | grep AccessDenied) ]]; then + textFail "Access Denied Trying to Get Bucket Policy Status for $bucket" + continue + fi + if [[ $(echo "$BUCKET_POLICY_STATUS" | grep NoSuchBucketPolicy) ]]; then + BUCKET_POLICY_STATUS="False" + fi + + if [[ $BUCKET_POLICY_STATUS != "" && $BUCKET_POLICY_STATUS != "False" ]]; then + textFail "$BUCKET_LOCATION: $bucket bucket is Public!" "$BUCKET_LOCATION" + continue + fi + + textPass "$BUCKET_LOCATION: $bucket bucket is not Public" "$BUCKET_LOCATION" + done } - -# Then implementation below makes pararel checks but can reach AWS API limits -# and eventually doesn't work as expected - -# extra73(){ -# 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 $PROFILE --region $REGION --output text) -# for bucket in $ALL_BUCKETS_LIST; do -# extra73Thread $bucket & -# done -# wait -# } -# extra73Thread(){ -# bucket=$1 -# BUCKET_LOCATION=$($AWSCLI s3api get-bucket-location --bucket $bucket --profile $PROFILE --region $REGION --output text 2>&1) -# if [[ $(echo "$BUCKET_LOCATION" | grep AccessDenied) ]]; then -# textFail "Access Denied Trying to Get Bucket Location for $bucket" -# return -# fi -# 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 $PROFILE --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 $PROFILE --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 $PROFILE --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" "$BUCKET_LOCATION" -# 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" "$BUCKET_LOCATION" -# fi -# if [[ $CHECK_BUCKET_ALLUSERS_POLICY ]];then -# textWarn "$BUCKET_LOCATION: $bucket bucket policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$BUCKET_LOCATION" -# fi -# else -# textOK "$BUCKET_LOCATION: $bucket bucket is not open" "$BUCKET_LOCATION" -# fi -# rm -f $TEMP_POLICY_FILE -# } From 425fe16752e9c3743bdfb107d9979a51c1907160 Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Mon, 27 Jan 2020 17:57:06 -0500 Subject: [PATCH 22/25] Update and rename check_extra772 to check_extra773 --- checks/{check_extra772 => check_extra773} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename checks/{check_extra772 => check_extra773} (87%) diff --git a/checks/check_extra772 b/checks/check_extra773 similarity index 87% rename from checks/check_extra772 rename to checks/check_extra773 index d08515a195..ecd2638529 100644 --- a/checks/check_extra772 +++ b/checks/check_extra773 @@ -10,13 +10,13 @@ # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. -CHECK_ID_extra772="7.72" -CHECK_TITLE_extra772="[extra772] Check if CloudFront distributions are using WAF (Not Scored) (Not part of CIS benchmark)" -CHECK_SCORED_extra772="NOT_SCORED" -CHECK_TYPE_extra772="EXTRA" -CHECK_ALTERNATE_check772="extra772" +CHECK_ID_extra773="7.73" +CHECK_TITLE_extra773="[extra773] Check if CloudFront distributions are using WAF (Not Scored) (Not part of CIS benchmark)" +CHECK_SCORED_extra773="NOT_SCORED" +CHECK_TYPE_extra773="EXTRA" +CHECK_ALTERNATE_check773="extra773" -extra772(){ +extra773(){ # "Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark)" LIST_OF_DISTRIBUTIONS=$($AWSCLI cloudfront list-distributions $PROFILE_OPT --query 'DistributionList.Items[].Id' --output text | grep -v "^None") if [[ $LIST_OF_DISTRIBUTIONS ]]; then From 278e382f9ac402515cd5dd5c1f57c9533052455f Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Mon, 27 Jan 2020 17:58:04 -0500 Subject: [PATCH 23/25] Update group7_extras --- groups/group7_extras | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/groups/group7_extras b/groups/group7_extras index b452cecdc1..5ab2c55273 100644 --- a/groups/group7_extras +++ b/groups/group7_extras @@ -15,7 +15,7 @@ GROUP_ID[7]='extras' GROUP_NUMBER[7]='7.0' GROUP_TITLE[7]='Extras - [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,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra737,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra756,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra766,extra767,extra768,extra769,extra770,extra771,extra772' +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,extra724,extra725,extra726,extra727,extra728,extra729,extra730,extra731,extra732,extra733,extra734,extra735,extra736,extra737,extra738,extra739,extra740,extra741,extra742,extra743,extra744,extra745,extra746,extra747,extra748,extra749,extra750,extra751,extra752,extra753,extra754,extra755,extra756,extra757,extra758,extra761,extra762,extra763,extra764,extra765,extra766,extra767,extra768,extra769,extra770,extra771,extra772,extra773' # Extras 759 and 760 (lambda variables and code secrets finder are not included) # to run detect-secrets use `./prowler -g secrets` From f038074e0c0cdaf2c83cf71bc860f7099036d4fe Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Mon, 27 Jan 2020 18:06:43 -0500 Subject: [PATCH 24/25] Update prowler-additions-policy.json --- iam/prowler-additions-policy.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/iam/prowler-additions-policy.json b/iam/prowler-additions-policy.json index 0f4b24f8c1..af68b2d979 100644 --- a/iam/prowler-additions-policy.json +++ b/iam/prowler-additions-policy.json @@ -50,7 +50,11 @@ "gamelift:list*", "glacier:list*", "importexport:listjobs", - "lambda:get*", + "lambda:GetAccountSettings", + "lambda:GetFunctionConfiguration", + "lambda:GetLayerVersionPolicy", + "lambda:GetPolicy", + "lambda:List*", "lex:getbotaliases", "lex:getbotchannelassociations", "lex:getbots", From 24780b4caab8c3a063320f785a5d657ec2b92d07 Mon Sep 17 00:00:00 2001 From: Toni de la Fuente Date: Thu, 30 Jan 2020 22:23:53 +0000 Subject: [PATCH 25/25] Improve documentation with prowler-additions-policy.json, issue #468 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2cc37a20d4..ac703a8137 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX cd prowler ``` -- Make sure you have properly configured your AWS-CLI with a valid Access Key and Region or declare AWS variables properly: +- Make sure you have properly configured your AWS-CLI with a valid Access Key and Region or declare AWS variables properly (or intance profile): ```sh aws configure @@ -94,7 +94,7 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX arn:aws:iam::aws:policy/SecurityAudit ``` - > In some cases you may need more list or get permissions in some services, look at the Troubleshooting section for a more comprehensive policy if you find issues with the default SecurityAudit policy. + > Additional permissions needed: to make sure Prowler can scan all services included in the group *Extras*, make sure you attach also the custom policy [prowler-additions-policy.json](https://github.com/toniblyx/prowler/blob/master/iam/prowler-additions-policy.json) to the role you are using. ## Usage