diff --git a/README.md b/README.md
index 70e8771655..3405705be3 100644
--- a/README.md
+++ b/README.md
@@ -55,13 +55,13 @@ With Prowler you can:
This script has been written in bash using AWS-CLI and it works in Linux and OSX.
-- Make sure your AWS-CLI is installed on your workstation, and other components needed, with Python pip already installed:
+- Make sure the latest version of AWS-CLI is installed on your workstation, and other components needed, with Python pip already installed:
```sh
pip install awscli ansi2html detect-secrets
```
- 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`
+ 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.
- Previous steps, from your workstation:
@@ -70,13 +70,19 @@ 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:
+- Make sure you have properly configured your AWS-CLI with a valid Access Key and Region or declare AWS variables properly:
```sh
aws configure
```
+ or
+ ```sh
+ export AWS_ACCESS_KEY_ID="ASXXXXXXX"
+ export AWS_SECRET_ACCESS_KEY="XXXXXXXXX"
+ export AWS_SESSION_TOKEN="XXXXXXXXX"
+ ```
-- Make sure your Secret and Access Keys are associated to a user with proper permissions to do all checks. To make sure add SecurityAuditor default policy to your user. Policy ARN is
+- Those credentials must be associated to a user or role with proper permissions to do all checks. To make sure add SecurityAuditor default policy to your user. Policy ARN is
```sh
arn:aws:iam::aws:policy/SecurityAudit
@@ -94,6 +100,12 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX
Use `-l` to list all available checks and group of checks (sections)
+ If you want to avoid installing dependences run it using Docker:
+
+ ```sh
+ docker run -ti --rm --name prowler --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --env AWS_SESSION_TOKEN toniblyx/prowler:latest
+ ```
+
1. For custom AWS-CLI profile and region, use the following: (it will use your custom profile and run checks over all regions when needed):
```sh
@@ -105,6 +117,11 @@ This script has been written in bash using AWS-CLI and it works in Linux and OSX
```sh
./prowler -c check310
```
+ With Docker:
+ ```sh
+ docker run -ti --rm --name prowler --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY --env AWS_SESSION_TOKEN toniblyx/prowler:latest "-c check310"
+ ```
+
or multiple checks separated by comma:
```sh
./prowler -c check310,check722
diff --git a/checks/check_extra727 b/checks/check_extra727
index 0be47f2c25..5e14e2b5c3 100644
--- a/checks/check_extra727
+++ b/checks/check_extra727
@@ -22,18 +22,25 @@ extra727(){
LIST_SQS=$($AWSCLI sqs list-queues $PROFILE_OPT --region $regx --query QueueUrls --output text |grep -v ^None)
if [[ $LIST_SQS ]]; then
for queue in $LIST_SQS; do
- # check if the policy has Principal as *
- SQS_TO_CHECK=$($AWSCLI sqs get-queue-attributes --queue-url $queue $PROFILE_OPT --region $regx --attribute-names All --query Attributes.Policy --output text | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | awk '/Principal/ || /Condition/ && !skip { print } { skip = /Deny/} ')
- PUBLIC_SQS_WCONDITION=$(echo $SQS_TO_CHECK|grep Condition)
- if [[ $PUBLIC_SQS_WCONDITION ]]; then
- textPass "$regx: SQS queue $queue has a Condition" "$regx"
- else
- PUBLIC_SQS=$(echo $SQS_TO_CHECK|grep \"Principal|grep \*)
- if [[ $PUBLIC_SQS ]]; then
- textFail "$regx: SQS queue $queue seems to be public (Principal: \"*\")" "$regx"
+ SQS_POLICY=$($AWSCLI sqs get-queue-attributes --queue-url $queue $PROFILE_OPT --region $regx --attribute-names All --query Attributes.Policy)
+ if [[ "$SQS_POLICY" != "null" ]]; then
+ SQS_POLICY_ALLOW_ALL=$(echo $SQS_POLICY \
+ | jq '. | fromjson' | jq '.Statement[] | select(.Effect=="Allow") | select(.Principal=="*" or .Principal.AWS=="*" or .Principal.CanonicalUser=="*")')
+ if [[ $SQS_POLICY_ALLOW_ALL ]]; then
+ SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION=$(echo $SQS_POLICY \
+ | jq '. | fromjson' | jq '.Statement[] | select(.Effect=="Allow") | select(.Principal=="*" or .Principal.AWS=="*" or .Principal.CanonicalUser=="*") | select(has("Condition") | not)')
+ if [[ $SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION ]]; then
+ SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION_DETAILS=$(echo $SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION \
+ | jq '"[Principal: " + (.Principal|tostring) + " Action: " + (.Action|tostring) + "]"' )
+ textFail "$regx: SQS $queue queue policy with public access: $SQS_POLICY_ALLOW_ALL_WITHOUT_CONDITION_DETAILS" "$regx"
+ else
+ textInfo "$regx: SQS $queue queue policy with public access but has a Condition" "$regx"
+ fi
else
- textPass "$regx: SQS queue $queue seems correct" "$regx"
+ textPass "$regx: SQS $queue queue without public access" "$regx"
fi
+ else
+ textPass "$regx: SQS $queue queue without policy" "$regx"
fi
done
else
diff --git a/checks/check_extra73 b/checks/check_extra73
index 7ca96c7f77..3b0efc1abf 100644
--- a/checks/check_extra73
+++ b/checks/check_extra73
@@ -45,7 +45,15 @@ CHECK_ALTERNATE_check703="extra73"
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)
+
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
BUCKET_LOCATION=$($AWSCLI s3api get-bucket-location --bucket $bucket $PROFILE_OPT --output text)
if [[ "None" == $BUCKET_LOCATION ]]; then
BUCKET_LOCATION="us-east-1"
@@ -53,36 +61,60 @@ extra73(){
if [[ "EU" == $BUCKET_LOCATION ]]; then
BUCKET_LOCATION="eu-west-1"
fi
- # Check Explicit Deny and Avoid Error
+
+ # 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
- textPass "$BUCKET_LOCATION: bucket have an explicit Deny. Not possible to get ACL." "$BUCKET_LOCATION"
+ textInfo "$BUCKET_LOCATION: $bucket have an explicit Deny. Not possible to get ACL." "$bucket" "$BUCKET_LOCATION"
else
- # 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
- textFail "$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
- textFail "$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
- textFail "$BUCKET_LOCATION: $bucket bucket policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$BUCKET_LOCATION"
- fi
+ # 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
- textPass "$BUCKET_LOCATION: $bucket bucket is not open" "$BUCKET_LOCATION"
+ ## 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
- rm -fr $TEMP_POLICY_FILE
+ 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"
fi
done
}
diff --git a/checks/check_extra731 b/checks/check_extra731
index 5fa5544404..911108ab29 100644
--- a/checks/check_extra731
+++ b/checks/check_extra731
@@ -22,23 +22,26 @@ extra731(){
LIST_SNS=$($AWSCLI sns list-topics $PROFILE_OPT --region $regx --query Topics --output text |grep -v ^None)
if [[ $LIST_SNS ]]; then
for topic in $LIST_SNS; do
- # check if the policy has Principal as *
- SNS_TO_CHECK=$($AWSCLI sns get-topic-attributes --topic-arn $topic $PROFILE_OPT --region $regx --query Attributes.Policy --output text | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | awk '/Principal/ || /Condition/ && !skip { print } { skip = /Deny/}')
- PUBLIC_SNS_WCONDITION=$(echo $SNS_TO_CHECK|grep Condition)
SHORT_TOPIC=$(echo $topic| cut -d: -f6)
- if [[ $PUBLIC_SNS_WCONDITION ]]; then
- textPass "$regx: SNS topic $SHORT_TOPIC has a Condition" "$regx"
- else
- PUBLIC_SNS=$(echo $SNS_TO_CHECK|grep \"Principal|grep \*)
- if [[ $PUBLIC_SNS ]]; then
- textFail "$regx: SNS topic $SHORT_TOPIC seems to be public (Principal: \"*\")" "$regx"
+ SNS_POLICY=$($AWSCLI sns get-topic-attributes --topic-arn $topic $PROFILE_OPT --region $regx --query Attributes.Policy 2>/dev/null)
+ SNS_POLICY_ALLOW_ALL=$(echo $SNS_POLICY \
+ | jq '. | fromjson' | jq '.Statement[] | select(.Effect=="Allow") | select(.Principal=="*" or .Principal.AWS=="*" or .Principal.CanonicalUser=="*")')
+ if [[ $SNS_POLICY_ALLOW_ALL ]]; then
+ SNS_POLICY_ALLOW_ALL_WITHOUT_CONDITION=$(echo $SNS_POLICY \
+ | jq '. | fromjson' | jq '.Statement[] | select(.Effect=="Allow") | select(.Principal=="*" or .Principal.AWS=="*" or .Principal.CanonicalUser=="*") | select(has("Condition") | not)')
+ 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"
else
- textPass "$regx: SNS topic $SHORT_TOPIC seems correct" "$regx"
+ textPass "$regx: SNS topic policy with public access but has a Condition" "$SHORT_TOPIC" "$regx"
fi
+ else
+ textPass "$regx: SNS topic without public access" "$SHORT_TOPIC" "$regx"
fi
done
else
- textInfo "$regx: No SNS topics found" "$regx"
+ textInfo "$regx: No SNS topic found" "$SHORT_TOPIC" "$regx"
fi
done
}
diff --git a/checks/check_extra757 b/checks/check_extra757
index 162c7fc579..0320081a9a 100644
--- a/checks/check_extra757
+++ b/checks/check_extra757
@@ -28,13 +28,13 @@ extra757(){
do
EC2_ID=$(echo "$ec2_instace" | awk '{print $1}')
LAUNCH_DATE=$(echo "$ec2_instace" | awk '{print $2}')
- textFail "$regx: EC2 Instance $EC2_ID running before than $OLDAGE"
+ textFail "$regx: EC2 Instance $EC2_ID running before than $OLDAGE" "$regx"
done <<< "$INSTACES_OLD_THAN_AGE"
else
- textPass "All Instances newer than 6 months"
+ textPass "$regx: All Instances newer than 6 months" "$regx"
fi
else
- textInfo "No EC2 Instances Found"
+ textInfo "$regx: No EC2 Instances Found" "$regx"
fi
done
}
diff --git a/checks/check_extra758 b/checks/check_extra758
index 93f1d16e75..1c402aa563 100644
--- a/checks/check_extra758
+++ b/checks/check_extra758
@@ -28,13 +28,13 @@ extra758(){
do
EC2_ID=$(echo "$ec2_instace" | awk '{print $1}')
LAUNCH_DATE=$(echo "$ec2_instace" | awk '{print $2}')
- textFail "$regx: EC2 Instance $EC2_ID running before than $OLDAGE"
+ textFail "$regx: EC2 Instance $EC2_ID running before than $OLDAGE" "$regx"
done <<< "$INSTACES_OLD_THAN_AGE"
else
- textPass "All Instances newer than 12 months"
+ textPass "$regx: All Instances newer than 12 months" "$regx"
fi
else
- textInfo "No EC2 Instances Found"
+ textInfo "$regx: No EC2 Instances Found" "$regx"
fi
done
}
diff --git a/checks/check_extra761 b/checks/check_extra761
new file mode 100644
index 0000000000..a74f76de3e
--- /dev/null
+++ b/checks/check_extra761
@@ -0,0 +1,29 @@
+#!/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.
+CHECK_ID_extra761="7.61"
+CHECK_TITLE_extra761="[extra761] Check if EBS Default Encryption is activated (Not Scored) (Not part of CIS benchmark)"
+CHECK_SCORED_extra761="NOT_SCORED"
+CHECK_TYPE_extra761="EXTRA"
+CHECK_ALTERNATE_check761="extra761"
+
+extra761(){
+ textInfo "Looking for EBS Default Encryption activation in all regions... "
+ for regx in $REGIONS; do
+ EBS_DEFAULT_ENCRYPTION=$($AWSCLI ec2 get-ebs-encryption-by-default $PROFILE_OPT --region $regx --query 'EbsEncryptionByDefault')
+ if [[ $EBS_DEFAULT_ENCRYPTION == "true" ]];then
+ textPass "$regx: EBS Default Encryption is activated" "$regx"
+ else
+ textFail "$regx: EBS Default Encryption is not activated" "$regx"
+ fi
+ done
+}
diff --git a/checks/check_extra762 b/checks/check_extra762
new file mode 100644
index 0000000000..b54cd5ac52
--- /dev/null
+++ b/checks/check_extra762
@@ -0,0 +1,41 @@
+#!/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.
+CHECK_ID_extra762="7.62"
+CHECK_TITLE_extra762="[extra762] Find obsolete Lambda runtimes (Not Scored) (Not part of CIS benchmark)"
+CHECK_SCORED_extra762="NOT_SCORED"
+CHECK_TYPE_extra762="EXTRA"
+CHECK_ALTERNATE_check762="extra762"
+
+extra762(){
+
+ # regex to match OBSOLETE runtimes in string functionName%runtime
+ # https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html
+ OBSOLETE='%(nodejs4.3|nodejs4.3-edge|nodejs6.10|nodejs8.10|dotnetcore1.0|dotnetcore2.0)'
+
+ for regx in $REGIONS; do
+ LIST_OF_FUNCTIONS=$($AWSCLI lambda list-functions $PROFILE_OPT --region $regx --output text --query 'Functions[*].{R:Runtime,N:FunctionName}' | tr "\t" "%")
+ if [[ $LIST_OF_FUNCTIONS ]]; then
+ for lambdafunction in $LIST_OF_FUNCTIONS;do
+ fname=$(echo "$lambdafunction" | cut -d'%' -f1)
+ runtime=$(echo "$lambdafunction" | cut -d'%' -f2)
+ if echo "$lambdafunction" | grep -Eq $OBSOLETE ; then
+ textFail "$regx: Obsolete runtime: ${runtime} used by: ${fname}" "$regx"
+ else
+ textPass "$regx: Supported runtime: ${runtime} used by: ${fname}" "$regx"
+ fi
+ done
+ else
+ textInfo "$regx: No Lambda functions found" "$regx"
+ fi
+ done
+}
diff --git a/groups/group7_extras b/groups/group7_extras
index 2d51b26034..b186e032b1 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'
+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'
# Extras 759 and 760 (lambda variables and code secrets finder are not included)
-# to run detect-secrets use `./prowler -g secrets`
\ No newline at end of file
+# to run detect-secrets use `./prowler -g secrets`
diff --git a/groups/group9_gdpr b/groups/group9_gdpr
index 7df4c6ecaf..858e4ab647 100644
--- a/groups/group9_gdpr
+++ b/groups/group9_gdpr
@@ -15,7 +15,7 @@ GROUP_ID[9]='gdpr'
GROUP_NUMBER[9]='9.0'
GROUP_TITLE[9]='GDPR Readiness - ONLY AS REFERENCE - [gdpr] ********************'
GROUP_RUN_BY_DEFAULT[9]='N' # run it when execute_all is called
-GROUP_CHECKS[9]='extra718,extra725,extra727,check12,check113,check114,extra71,extra731,extra732,extra733,check25,check39,check21,check22,check23,check24,check26,check27,check35,extra726,extra714,extra715,extra717,extra719,extra720,extra721,extra722,check43,check25,extra714,extra729,extra734,extra735,extra736,extra738,extra740'
+GROUP_CHECKS[9]='extra718,extra725,extra727,check12,check113,check114,extra71,extra731,extra732,extra733,check25,check39,check21,check22,check23,check24,check26,check27,check35,extra726,extra714,extra715,extra717,extra719,extra720,extra721,extra722,check43,check25,extra714,extra729,extra734,extra735,extra736,extra738,extra740,extra761'
# Resources:
# https://d1.awsstatic.com/whitepapers/compliance/GDPR_Compliance_on_AWS.pdf
diff --git a/prowler b/prowler
index 7f92e7b1e2..fb60a32f41 100755
--- a/prowler
+++ b/prowler
@@ -252,12 +252,10 @@ execute_group() {
NEW_CHECKS=()
IFS=',' read -ra EXCLUDED_CHECKS <<< "${2},"
for exc in ${EXCLUDED_CHECKS[@]} ; do
- for i in ${CHECKS[@]} ; do
- [[ ${i} != ${exc} ]] && NEW_CHECKS+=(${i})
+ for i in ${!CHECKS[@]} ; do
+ [[ ${CHECKS[i]} = ${exc} ]] && unset CHECKS[i]
done
done
- CHECKS=("${NEW_CHECKS[@]}")
- unset NEW_CHECKS
unset EXCLUDED_CHECKS
fi
for i in ${CHECKS[@]}; do
diff --git a/util/Dockerfile b/util/Dockerfile
index 116bc1f4f4..ae0cb0ccc0 100644
--- a/util/Dockerfile
+++ b/util/Dockerfile
@@ -5,11 +5,13 @@ ARG USERID=34000
RUN addgroup -g ${USERID} ${USERNAME} && \
adduser -s /bin/sh -G ${USERNAME} -D -u ${USERID} ${USERNAME} && \
- apk --update --no-cache add python3 bash curl git jq file && \
+ apk --update --no-cache add python3 bash curl jq file && \
pip3 install --upgrade pip && \
- pip install awscli ansi2html boto3 detect-secrets &&\
- git clone https://github.com/toniblyx/prowler/ &&\
- chown -R prowler /prowler/
+ pip install awscli ansi2html boto3 detect-secrets
+
+ADD . /prowler
+
+RUN chown -R prowler /prowler/
USER ${USERNAME}
diff --git a/util/multi-account/Audit_Exec_Role.yaml b/util/multi-account/Audit_Exec_Role.yaml
new file mode 100644
index 0000000000..673defaa2e
--- /dev/null
+++ b/util/multi-account/Audit_Exec_Role.yaml
@@ -0,0 +1,73 @@
+---
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Prowler Auditing Role - in Control Tower pick AWSControlTowerStackSetRole for IAM role and AWSControlTowerExecution for execution
+
+Parameters:
+
+ AuditorAccountId:
+ Default: 987600001234
+ Description: AWS Account ID where the audit tooling executes
+ Type: Number
+ AuditRolePathName:
+ Default: '/audit/prowler/XA_AuditRole_Prowler'
+ Description: Path for role name in audit tooling account
+ Type: String
+
+Resources:
+ XAAuditRole:
+ Type: "AWS::IAM::Role"
+ Properties: # /audit/prowler/XA_AuditRole_Prowler
+ RoleName: XA_AuditRole_Prowler
+ Path: "/audit/prowler/"
+ ManagedPolicyArns:
+ - arn:aws:iam::aws:policy/SecurityAudit
+ - arn:aws:iam::aws:policy/AWSOrganizationsReadOnlyAccess
+ - arn:aws:iam::aws:policy/IAMReadOnlyAccess
+ AssumeRolePolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Effect: "Allow"
+ Principal:
+ AWS: # TODO: review permissions to see if this can be narrowed down - code build only perhaps
+ - !Sub "arn:aws:iam::${AuditorAccountId}:root"
+ Action:
+ - "sts:AssumeRole"
+ - Effect: "Allow"
+ Principal:
+ Service:
+ - "codebuild.amazonaws.com"
+ Action:
+ - "sts:AssumeRole"
+ # TODO: restrict to only AuditorAccount only
+ Policies:
+ - PolicyName: "ProwlerPolicyAdditions"
+ PolicyDocument:
+ Version: "2012-10-17"
+ Statement:
+ - Sid: "ProwlerPolicyAdditions"
+ Effect: "Allow"
+ Resource: "*"
+ Action:
+ - "acm:describecertificate"
+ - "acm:listcertificates"
+ - "es:describeelasticsearchdomainconfig"
+ - "logs:DescribeLogGroups"
+ - "logs:DescribeMetricFilters"
+ - "ses:getidentityverificationattributes"
+ - "sns:listsubscriptionsbytopic"
+ - "guardduty:ListDetectors"
+ - "guardduty:GetDetector"
+ - "S3:GetEncryptionConfiguration"
+ - "trustedadvisor:Describe*"
+ - "cloudtrail:GetEventSelectors"
+ - "apigateway:GET"
+ - "support:*"
+ Metadata:
+ cfn_nag:
+ rules_to_suppress:
+ - id: W28
+ reason: "the role name is intentionally static"
+ - id: W11
+ reason: "the policy grants read/view/audit access only, to all resources, by design"
+ - id: F3
+ reason: "Support does not allow or deny access to individual actions"
diff --git a/util/multi-account/Audit_Pipeline.yaml b/util/multi-account/Audit_Pipeline.yaml
new file mode 100644
index 0000000000..acd0c21652
--- /dev/null
+++ b/util/multi-account/Audit_Pipeline.yaml
@@ -0,0 +1,414 @@
+---
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Prowler Auditing Tools Stack
+
+Metadata:
+ AWS::CloudFormation::Interface:
+ ParameterGroups:
+ - Label:
+ default: "Organizations and Accounts"
+ Parameters:
+ - pOrgMasterAccounts
+ - pOrgExcludedAccounts
+ - pStandAloneAccounts
+ - Label:
+ default: "Check Group and Execution"
+ Parameters:
+ - pProwlerCheckGroup
+ - pAuditEveryXHours
+ - Label:
+ default: "Advanced"
+ Parameters:
+ - pTimeoutMinutes
+ - pAuditRolePathName
+ - pCustomProwlerRepo
+ - pCustomProwlerCloneArgs
+ ParameterLabels:
+ pOrgMasterAccounts:
+ default: "Organization Master Accounts"
+ pOrgExcludedAccounts:
+ default: "Excluded Organiztion Members"
+ pStandAloneAccounts:
+ default: "Stand-alone Accounts"
+ pProwlerCheckGroup:
+ default: "Prowler Check Group"
+ pAuditEveryXHours:
+ default: "Perform Audit every X hours"
+ pTimeoutMinutes:
+ default: "Permit Audit to run for X minutes"
+ pAuditRolePathName:
+ default: "Custom audit role path"
+ pCustomProwlerRepo:
+ default: "Custom git repo location for prowler"
+ pCustomProwlerCloneArgs:
+ default: "Custom arguments to git clone --depth 1"
+
+Parameters:
+ pAuditEveryXHours:
+ Default: 24
+ Type: Number
+ Description: Number of hours between prowler audit runs.
+ MinValue: 2
+ MaxValue: 168
+ pTimeoutMinutes:
+ Default: 30
+ Type: Number
+ Description: Timeout for running prowler across the fleet
+ MinValue: 5
+ MaxValue: 480
+ pAuditRolePathName:
+ Default: '/audit/prowler/XA_AuditRole_Prowler'
+ Type: String
+ Description: Role path and name which prowler will assume in the target accounts (Audit_Exec_Role.yaml)
+ # TODO: Validation: begins with "/" and does NOT end with "/"
+ pOrgMasterAccounts:
+ Description: Comma Separated list of Organization Master Accounts, or 'none'
+ Default: 'none'
+ Type: String
+ MinLength: 4
+ AllowedPattern: ^(none|([0-9]{12}(,[0-9]{12})*))$
+ ConstraintDescription: comma separated list 12-digit account numbers, or 'none'
+ pOrgExcludedAccounts: # Comma Separated list of Org Member Accounts to EXCLUDE
+ Description: Comma Separated list of Skipped Organization Member Accounts, or 'none'
+ Default: 'none'
+ Type: String
+ MinLength: 4
+ AllowedPattern: ^(none|([0-9]{12}(,[0-9]{12})*))$
+ ConstraintDescription: comma separated list 12-digit account numbers, or 'none'
+ pStandAloneAccounts: # Comma Separated list of Stand-Alone Accounts
+ Description: Comma Separated list of Stand-alone Accounts, or 'none'
+ Default: 'none'
+ Type: String
+ MinLength: 4
+ AllowedPattern: ^(none|([0-9]{12}(,[0-9]{12})*))$
+ ConstraintDescription: comma separated list 12-digit account numbers, or 'none'
+ pProwlerCheckGroup:
+ Default: 'cislevel1'
+ Type: String
+ Description: Which group of checks should prowler run
+ AllowedValues:
+ - 'group1'
+ - 'group2'
+ - 'group3'
+ - 'group4'
+ - 'cislevel1'
+ - 'cislevel2'
+ - 'extras'
+ - 'forensics-ready'
+ - 'gdpr'
+ - 'hipaa'
+ - 'secrets'
+ - 'apigateway'
+ - 'rds'
+ pCustomProwlerRepo:
+ Type: String
+ Default: 'https://github.com/toniblyx/prowler.git'
+ MinLength: 10
+ pCustomProwlerCloneArgs:
+ Type: String
+ Default: '--branch master'
+ MinLength: 0
+ ##### TODO
+ # pResultsBucket: # if specified, use an existing bucket for the data
+ # pEnableAthena:
+ # Default: false
+ # Type: Boolean
+ # Description: Set to true to enable creation of Athena/QuickSight resources
+
+#### TODO
+# Conditions:
+# cUseAthena: False
+
+Resources:
+
+ # S3 Bucket for Results, Config
+ ProwlerResults:
+ Type: "AWS::S3::Bucket"
+ Properties:
+ # BucketName: !Sub "audit-results-${AWS::AccountId}"
+ Tags:
+ - Key: "data-type"
+ Value: "it-audit:sensitive"
+ - Key: "data-public"
+ Value: "NO"
+ AccessControl: Private
+ BucketEncryption:
+ ServerSideEncryptionConfiguration:
+ - ServerSideEncryptionByDefault:
+ SSEAlgorithm: AES256
+ PublicAccessBlockConfiguration:
+ BlockPublicAcls: True
+ BlockPublicPolicy: True
+ IgnorePublicAcls: True
+ RestrictPublicBuckets: True
+ # LoggingConfiguration:
+ # TODO: Enable BucketLogging - requires more parameters
+ DeletionPolicy: "Retain"
+ Metadata:
+ cfn_nag:
+ rules_to_suppress:
+ - id: W35
+ reason: "Bucket logging requires additional configuration not yet supported by this template"
+
+ # Policy to allow assuming the XA_AuditRole_Prowler in target accounts
+ ProwlerAuditManagerRole:
+ Type: AWS::IAM::Role
+ Properties:
+ RoleName: AuditManagerRole_Prowler
+ AssumeRolePolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service: codebuild.amazonaws.com
+ Action:
+ - sts:AssumeRole
+ Path: /
+ Policies:
+ - PolicyName: AssumeRole-XA_AuditRole_Prowler
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - sts:AssumeRole
+ Resource:
+ - !Sub "arn:aws:iam::*:role${pAuditRolePathName}"
+ - Effect: Allow
+ Action:
+ - s3:PutObject
+ - s3:GetObject
+ - s3:GetObjectVersion
+ Resource:
+ - !Sub "${ProwlerResults.Arn}/*"
+ - Effect: Allow
+ Action:
+ - s3:ListBucket
+ - s3:HeadBucket
+ - s3:GetBucketLocation
+ - s3:GetBucketAcl
+ Resource:
+ - !Sub "${ProwlerResults.Arn}"
+ - Effect: Allow
+ Action:
+ - logs:CreateLogGroup
+ - logs:CreateLogStream
+ - logs:PutLogEvents
+ Resource:
+ - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*"
+ - !Sub "${ProwlerResults.Arn}"
+ - Effect: Allow
+ Action:
+ - ssm:GetParameters
+ Resource:
+ - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/audit/prowler/config/*"
+ Metadata:
+ cfn_nag:
+ rules_to_suppress:
+ - id: W28
+ reason: "the role name is intentionally static"
+ - id: W11
+ reason: "not sure where the violation of w11 is"
+
+ ## Code Build Job
+ ProwlerBuildProject:
+ Type: "AWS::CodeBuild::Project"
+ Properties:
+ Name: PerformProwlerAudit
+ Description: "Run Prowler audit on accounts in targeted organizations"
+ QueuedTimeoutInMinutes: 480
+ TimeoutInMinutes: !Ref pTimeoutMinutes
+ ServiceRole: !Ref ProwlerAuditManagerRole
+ EncryptionKey: !Sub "arn:aws:kms:us-east-1:${AWS::AccountId}:alias/aws/s3"
+ Environment:
+ Type: "LINUX_CONTAINER"
+ ComputeType: "BUILD_GENERAL1_MEDIUM"
+ PrivilegedMode: False
+ Image: "aws/codebuild/standard:2.0-1.12.0"
+ ImagePullCredentialsType: "CODEBUILD"
+ Artifacts: # s3://stack-prowlerresults-randomness/prowler/results/...
+ Name: "results"
+ Type: "S3"
+ Location: !Ref ProwlerResults
+ Path: "prowler"
+ NamespaceType: NONE
+ Packaging: NONE
+ OverrideArtifactName: False
+ EncryptionDisabled: False
+ LogsConfig: # S3/logs/pipeline/
+ CloudWatchLogs:
+ Status: ENABLED
+ GroupName: "audit/prowler"
+ StreamName: "codebuild_runs"
+ S3Logs:
+ Status: DISABLED
+ # Location: !Sub "${ProwlerResults.Arn}/codebuild_run_logs"
+ EncryptionDisabled: False
+ BadgeEnabled: False
+ Tags:
+ - Key: "data-type"
+ Value: "it-audit:sensitive"
+ - Key: "data-public"
+ Value: "NO"
+ Cache:
+ Type: "NO_CACHE"
+ Source:
+ Type: NO_SOURCE
+ BuildSpec: |
+ version: 0.2
+ env:
+ parameter-store:
+ PROWL_CHECK_GROUP: /audit/prowler/config/check_group
+ PROWL_MASTER_ACCOUNTS: /audit/prowler/config/orgmaster_accounts
+ PROWL_STANDALONE_ACCOUNTS: /audit/prowler/config/standalone_accounts
+ PROWL_SKIP_ACCOUNTS: /audit/prowler/config/skip_accounts
+ PROWL_AUDIT_ROLE: /audit/prowler/config/audit_role
+ PROWLER_REPO: /audit/prowler/config/gitrepo
+ PROWLER_CLONE_ARGS: /audit/prowler/config/gitcloneargs
+ phases:
+ install:
+ runtime-versions:
+ python: 3.7
+ commands:
+ - aws --version
+ - git clone --depth 1 $PROWLER_REPO $PROWLER_CLONE_ARGS
+ pre_build:
+ commands:
+ - env | grep PROWL_
+ - export OUTBASE=$(date -u +"out/diagnostics/%Y/%m/%d")
+ - export STAMP=$(date -u +"%Y%m%dT%H%M%SZ")
+ - mkdir -p $OUTBASE || true
+ - prowler/prowler -V
+ - aws sts get-caller-identity > ${OUTBASE}/${STAMP}-caller-id.json
+ build:
+ commands:
+ #### Run Prowler against this account, but don't fail the build
+ # - export PROWLER_ACCOUNT_ID=$(aws sts get-caller-identity | jq -r '.Account')
+ # - /bin/bash prowler/prowler -g cislevel1 -M csv -n -k > ${OUTBASE}/${STAMP}.${PROWLER_ACCOUNT_ID}.prowler.cislevel1.csv || /bin/true
+ # - /bin/bash prowler/prowler -g forensics-ready -M csv -n -k > ${OUTBASE}/${STAMP}.${PROWLER_ACCOUNT_ID}.prowler.forensics-ready.csv || /bin/true
+ #### Run Prowler targeting all accounts in the configured organizations
+ - test -f prowler/util/multi-account/config
+ - /bin/bash prowler/util/multi-account/megaprowler.sh out
+ finally:
+ - ps axuwww | grep -E 'parallel|sem|prowler'
+ post_build:
+ commands:
+ - echo "attempting to collect any prowler credential reports ..."
+ - find /tmp/ -name prowler\* | xargs -I % cp % ${OUTDIAG} || true
+ artifacts:
+ files:
+ - '**/*'
+ discard-paths: no
+ base-directory: out
+
+
+
+
+ ProwlerAuditTriggerRole:
+ Type: AWS::IAM::Role
+ Properties:
+ # RoleName: Let cloudformation create this
+ AssumeRolePolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service: events.amazonaws.com
+ Action:
+ - sts:AssumeRole
+ Path: /
+ Policies:
+ - PolicyName: AssumeRole-XA_AuditRole_Prowler
+ PolicyDocument:
+ Version: 2012-10-17
+ Statement:
+ - Effect: Allow
+ Action:
+ - codebuild:StartBuild
+ Resource:
+ - !GetAtt ProwlerBuildProject.Arn
+
+ ProwlerAuditTrigger:
+ Type: AWS::Events::Rule
+ Properties:
+ Description: !Sub "Execute Prowler audit every ${pAuditEveryXHours} hours"
+ Name: "ScheduledProwler"
+ RoleArn: !GetAtt ProwlerAuditTriggerRole.Arn
+ ## Other ways to define scheduling
+ # ScheduleExpression: "cron(MM HH ? * * *)"
+ # ScheduleExpression: "cron(45 15 ? * * *)"
+ # ScheduleExpression: !Sub "rate( ${pAuditEveryXHours} hours)"
+ ScheduleExpression: !Sub "rate(${pAuditEveryXHours} hours)"
+ State: ENABLED
+ Targets:
+ - Arn: !GetAtt ProwlerBuildProject.Arn
+ Id: 'ScheduledProwler'
+ RoleArn: !GetAtt ProwlerAuditTriggerRole.Arn
+
+ ProwlerConfigCheckGroup:
+ Type: AWS::SSM::Parameter
+ Properties:
+ Description: "Name of the prowler check group to use"
+ Name: "/audit/prowler/config/check_group"
+ Type: "String"
+ Value: !Ref pProwlerCheckGroup
+
+ ProwlerConfigMasterAccounts:
+ Type: AWS::SSM::Parameter
+ Properties:
+ Description: "List of organization master accounts"
+ Name: "/audit/prowler/config/orgmaster_accounts"
+ Type: "String"
+ Value: !Ref pOrgMasterAccounts
+
+ ProwlerConfigStandAloneAccounts:
+ Type: AWS::SSM::Parameter
+ Properties:
+ Description: "List of stand-alone accounts"
+ Name: "/audit/prowler/config/standalone_accounts"
+ Type: "String"
+ Value: !Ref pStandAloneAccounts
+
+ ProwlerConfigSkipAccounts:
+ Type: AWS::SSM::Parameter
+ Properties:
+ Description: "List of skipped organization member accounts"
+ Name: "/audit/prowler/config/skip_accounts"
+ Type: "String"
+ Value: !Ref pOrgExcludedAccounts
+
+ ProwlerConfigAuditRole:
+ Type: AWS::SSM::Parameter
+ Properties:
+ Description: "Role used to audit target accounts"
+ Name: "/audit/prowler/config/audit_role"
+ Type: "String"
+ Value: !Ref pAuditRolePathName
+
+ ProwlerConfigGitRepo:
+ Type: AWS::SSM::Parameter
+ Properties:
+ Description: "Git repository where prowler is gathered"
+ Name: "/audit/prowler/config/gitrepo"
+ Type: "String"
+ Value: !Ref pCustomProwlerRepo
+
+ ProwlerConfigGitCloneArgs:
+ Type: AWS::SSM::Parameter
+ Properties:
+ Description: "Git clone arguments"
+ Name: "/audit/prowler/config/gitcloneargs"
+ Type: "String"
+ Value: !Ref pCustomProwlerCloneArgs
+
+
+ # -- Conditional "cUseAthena"
+ # Athena
+ # QuickSight
+ # ???
+
+
+Outputs:
+ ResultsBucket:
+ Description: S3 Bucket with Prowler Results, Logs, Configs
+ Value: !Ref ProwlerResults
diff --git a/util/multi-account/config b/util/multi-account/config
new file mode 100644
index 0000000000..12c354927b
--- /dev/null
+++ b/util/multi-account/config
@@ -0,0 +1,33 @@
+#!/bin/bash
+########### CODEBUILD CONFIGURATION ##################
+# shellcheck disable=SC2034
+## Collect environment parameters set by buildspec
+CHECKGROUP=${PROWL_CHECK_GROUP}
+
+if [ "none" == "${PROWL_MASTER_ACCOUNTS}" ]; then
+ ORG_MASTERS=""
+else
+ ORG_MASTERS=$(echo "${PROWL_MASTER_ACCOUNTS}" | tr "," " ")
+fi
+
+if [ "none" == "${PROWL_STANDALONE_ACCOUNTS}" ]; then
+ STANDALONE_ACCOUNTS=""
+else
+ STANDALONE_ACCOUNTS=$(echo "${PROWL_STANDALONE_ACCOUNTS}" | tr "," " ")
+fi
+
+if [ "none" == "${PROWL_SKIP_ACCOUNTS}" ]; then
+ SKIP_ACCOUNTS_REGEX='^$'
+else
+ skip_inside=$(echo "${PROWL_SKIP_ACCOUNTS}" | tr "," "|")
+ # shellcheck disable=SC2116
+ SKIP_ACCOUNTS_REGEX=$(echo "(${skip_inside})" )
+fi
+
+AUDIT_ROLE=${PROWL_AUDIT_ROLE}
+
+# Adjust if you clone prowler from somewhere other than the default location
+PROWLER='prowler/prowler'
+
+# Change this if you want to ensure it breaks in code build
+CREDSOURCE='EcsContainer'
diff --git a/util/multi-account/megaprowler.sh b/util/multi-account/megaprowler.sh
new file mode 100644
index 0000000000..623b642e2a
--- /dev/null
+++ b/util/multi-account/megaprowler.sh
@@ -0,0 +1,202 @@
+#!/bin/bash
+
+BASEDIR=$(dirname "${0}")
+# source the configuration data from "config" in this directory
+if [[ -f "${BASEDIR}/config" ]]; then
+ # shellcheck disable=SC1090
+ . "${BASEDIR}/config"
+
+else
+ echo "CONFIG file missing - ${BASEDIR}/config"
+ exit 255
+fi
+
+## Check Environment variables which are set by config
+if [[ "${ORG_MASTERS}X" == "X" && "${STANDALONE_ACCOUNTS}X" == "X" ]]; then
+ echo "No audit targets specified. Failing."
+ exit 15
+fi
+if [[ -z $SKIP_ACCOUNTS_REGEX ]]; then
+ SKIP_ACCOUNTS_REGEX=""
+fi
+
+if [[ -z $CHECKGROUP ]]; then
+ echo "Missing check group from config file"
+ exit 255
+fi
+if [[ -z $AUDIT_ROLE ]]; then
+ echo "Missing audit role from config file"
+ exit 255
+fi
+
+## ========================================================================================
+
+## Check Arguments
+if [ $# -lt 1 ]; then
+ echo "NEED AN OUTPUT DIRECTORY"
+ exit 2
+else
+ if [[ -d $1 && -w $1 ]]; then
+ OUTBASE=$1
+ else
+ echo "Output directory missing or write-protected"
+ exit 1
+ fi
+fi
+
+
+## Check Requirements
+if [[ -x $(command -v aws) ]]; then
+ aws --version
+else
+ echo "AWS CLI is not in PATH ... giving up"
+ exit 4
+fi
+
+if [[ -x $(command -v jq) ]]; then
+ jq --version
+else
+ echo "JQ is not in PATH ... giving up"
+ exit 4
+fi
+
+# Ensure AWS Credentials are present in environment
+if [[ -z $CREDSOURCE ]]; then
+ echo "No source for base credentials ... giving up"
+ exit 5
+fi
+
+if [[ -f ${PROWLER} && -x ${PROWLER} ]]; then
+ ${PROWLER} -V
+else
+ echo "Unable to execute prowler from ${PROWLER}"
+ exit 3
+fi
+
+
+## Preflight checks complete
+
+DAYPATH=$(date -u +%Y/%m/%d)
+STAMP=$(date -u +%Y%m%dT%H%M%SZ)
+## Create output subdirs
+OUTDATA="${OUTBASE}/data/${DAYPATH}"
+OUTLOGS="${OUTBASE}/logs/${DAYPATH}"
+mkdir -p "${OUTDATA}" "${OUTLOGS}"
+
+
+if [[ -x $(command -v parallel) ]]; then
+ # Note: the "standard" codebuild container includes parallel
+ echo "Using GNU sem/parallel, with NCPU+4 jobs"
+ parallel --citation > /dev/null 2> /dev/null
+ PARALLEL_START="parallel --semaphore --fg --id p_${STAMP} --jobs +4 --env AWS_SHARED_CREDENTIALS_FILE"
+ PARALLEL_START_SUFFIX=''
+ PARALLEL_END="parallel --semaphore --wait --id p_${STAMP}"
+else
+ echo "Consider installing GNU Parallel to avoid punishing your system"
+ PARALLEL_START=''
+ PARALLEL_START_SUFFIX=' &'
+ # shellcheck disable=SC2089
+ PARALLEL_END="echo 'WAITING BLINDLY FOR PROCESSES TO COMPLETE'; wait ; sleep 30 ; wait"
+fi
+
+echo "Execution Timestamp: ${STAMP}"
+
+ALL_ACCOUNTS=""
+
+
+# Create a temporary credential file
+AWS_MASTERS_CREDENTIALS_FILE=$(mktemp -t prowler.masters-XXXXXX)
+echo "Preparing Credentials ${AWS_MASTERS_CREDENTIALS_FILE} ( ${CREDSOURCE} )"
+echo "# Master Credentials ${STAMP}" >> "${AWS_MASTERS_CREDENTIALS_FILE}"
+echo "" >> "${AWS_MASTERS_CREDENTIALS_FILE}"
+
+AWS_TARGETS_CREDENTIALS_FILE=$(mktemp -t prowler.targets-XXXXXX)
+echo "Preparing Credentials ${AWS_TARGETS_CREDENTIALS_FILE} ( ${CREDSOURCE} )"
+echo "# Target Credentials ${STAMP}" >> "${AWS_TARGETS_CREDENTIALS_FILE}"
+echo "" >> "${AWS_TARGETS_CREDENTIALS_FILE}"
+
+
+## Visit the Organization Master accounts & build a list of all member accounts
+export AWS_SHARED_CREDENTIALS_FILE=$AWS_MASTERS_CREDENTIALS_FILE
+for org in $ORG_MASTERS ; do
+ echo -n "Preparing organization $org "
+ # create credential profile
+ {
+ echo "[audit_${org}]"
+ echo "role_arn = arn:aws:iam::${org}:role${AUDIT_ROLE}"
+ echo "credential_source = ${CREDSOURCE}"
+ echo ""
+ } >> "${AWS_MASTERS_CREDENTIALS_FILE}"
+
+ # Get the Organization ID to use for output paths, collecting info, etc
+ org_id=$(aws --output json --profile "audit_${org}" organizations describe-organization | jq -r '.Organization.Id' )
+
+ echo "( $org_id )"
+ ORG_ID_LIST="${ORG_ID_LIST} ${org_id}"
+
+
+ # Build the list of all accounts in the organizations
+ aws --output json --profile "audit_${org}" organizations list-accounts > "${OUTLOGS}/${STAMP}-${org_id}-account-list.json"
+ # shellcheck disable=SC2002
+ ORG_ACCOUNTS=$( cat "${OUTLOGS}/${STAMP}-${org_id}-account-list.json" | jq -r '.Accounts[].Id' | tr "\n" " ")
+ ALL_ACCOUNTS="${ALL_ACCOUNTS} ${ORG_ACCOUNTS}"
+
+ # Add the Org's Accounts (including master) to the TARGETS_CREDENTIALS file
+ for target in $ORG_ACCOUNTS ; do
+ if echo "$target" | grep -qE "${SKIP_ACCOUNTS_REGEX}"; then
+ echo " skipping account ${target} ( ${org_id} )"
+ continue
+ fi
+ # echo " ${org_id}_${target}"
+ {
+ echo "[${org_id}_${target}]"
+ echo "role_arn = arn:aws:iam::${target}:role${AUDIT_ROLE}"
+ echo "credential_source = ${CREDSOURCE}"
+ echo ""
+ } >> "${AWS_TARGETS_CREDENTIALS_FILE}"
+ done
+
+done
+
+# Prepare credentials for standalone accounts
+if [[ "" != "${STANDALONE_ACCOUNTS}" ]] ; then
+ # mkdir -p ${OUTBASE}/data/standalone/${DAYPATH} ${OUTBASE}/logs/standalone/${DAYPATH}
+ for target in $STANDALONE_ACCOUNTS ; do
+ echo "Preparing account ${target} ( standalone )"
+ {
+ echo "[standalone_${target}]"
+ echo "role_arn = arn:aws:iam::${target}:role${AUDIT_ROLE}"
+ echo "credential_source = ${CREDSOURCE}"
+ echo ""
+ } >> "${AWS_TARGETS_CREDENTIALS_FILE}"
+ done
+ ALL_ACCOUNTS="${ALL_ACCOUNTS} ${STANDALONE_ACCOUNTS}"
+fi
+
+# grep -E '^\[' $AWS_MASTERS_CREDENTIALS_FILE $AWS_TARGETS_CREDENTIALS_FILE
+
+
+# Switch to Target Credential Set
+export AWS_SHARED_CREDENTIALS_FILE=${AWS_TARGETS_CREDENTIALS_FILE}
+
+## visit each target account
+NUM_ACCOUNTS=$(grep -cE '^\[' "${AWS_TARGETS_CREDENTIALS_FILE}")
+echo "Launching ${CHECKGROUP} audit of ${NUM_ACCOUNTS} accounts"
+for member in $(grep -E '^\[' "${AWS_TARGETS_CREDENTIALS_FILE}" | tr -d '][') ; do
+ ORG_ID=$(echo "$member" | cut -d'_' -f1)
+ ACCOUNT_NUM=$(echo "$member" | cut -d'_' -f2)
+
+ # shellcheck disable=SC2086
+ ${PARALLEL_START} "${PROWLER} -p ${member} -n -M csv -g ${CHECKGROUP} 2> ${OUTLOGS}/${STAMP}-${ORG_ID}-${ACCOUNT_NUM}-prowler-${CHECKGROUP}.log > ${OUTDATA}/${STAMP}-${ORG_ID}-${ACCOUNT_NUM}-prowler-${CHECKGROUP}.csv ; echo \"${ORG_ID}-${ACCOUNT_NUM}-prowler-${CHECKGROUP} finished\" " ${PARALLEL_START_SUFFIX}
+done
+
+echo -n "waiting for parallel threads to complete - " ; date
+# shellcheck disable=SC2090
+${PARALLEL_END}
+
+echo "Completed ${CHECKGROUP} audit with stamp ${STAMP}"
+
+# mkdir -p ${OUTBASE}/logs/debug/${DAYPATH}
+# cp "$AWS_MASTERS_CREDENTIALS_FILE" "${OUTLOGS}/${STAMP}-master_creds.txt"
+# cp "$AWS_TARGETS_CREDENTIALS_FILE" "${OUTLOGS}/${STAMP}-target_creds.txt"
+rm "$AWS_MASTERS_CREDENTIALS_FILE" "$AWS_TARGETS_CREDENTIALS_FILE"