mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-18 18:11:57 +00:00
@@ -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 <https://aws.amazon.com/cli/>, 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 <https://aws.amazon.com/cli/>, 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
|
||||
|
||||
+17
-10
@@ -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
|
||||
|
||||
+57
-25
@@ -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
|
||||
}
|
||||
|
||||
+14
-11
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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`
|
||||
# to run detect-secrets use `./prowler -g secrets`
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
-4
@@ -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}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user