feat(metadata): Include account metadata in Prowler assessments (#1049)

* Add support for organizations accounts metadata part 1

* Add support for organizations accounts metadata part 2

* Add gathering account metadata from org

* chore(prowler): get accounts metadata

Use assume_role backing up normal assumed credentials to assume management account and then restore it to old ones

* fix(orgs metadata): deleted assume_role_orgs

* refactor(organization_metadata)

Reformulate to extract AWS Organizations metadata

* doc(org_metadata): include required -R in usage

* docs(org-metadata): Update README

Co-authored-by: n4ch04 <nachor1992@gmail.com>
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
This commit is contained in:
Toni de la Fuente
2022-03-16 16:27:19 +01:00
committed by GitHub
parent c526c61d5e
commit 365b396f9a
6 changed files with 115 additions and 10 deletions

View File

@@ -365,6 +365,30 @@ for accountId in $ACCOUNTS_IN_ORGS; do ./prowler -A $accountId -R RemoteRoleToAs
```
Usig the same for loop it can be scanned a list of accounts with a variable like `ACCOUNTS_LIST='11111111111 2222222222 333333333'`
### Get AWS Account details from your AWS Organization:
From Prowler v2.8, you can get additional information of the scanned account in CSV and JSON outputs. When scanning a single account you get the Account ID as part of the output. Now, if you have AWS Organizations and are scanning multiple accounts using the assume role functionality, Prowler can get your account details like Account Name, Email, ARN, Organization ID and Tags and you will have them next to every finding in the CSV and JSON outputs.
In order to do that you can use the new option `-O <management account id>`, requires `-R <role to assume>` and also needs permissions `organizations:ListAccounts*` and `organizations:ListTagsForResource`. See the following sample command:
```
./prowler -R ProwlerScanRole -A 111111111111 -O 222222222222 -M json,csv
```
In that command Prowler will scan the account `111111111111` assuming the role `ProwlerScanRole` and getting the account details from the AWS Organizatiosn management account `222222222222` assuming the same role `ProwlerScanRole` for that and creating two reports with those details in JSON and CSV.
In the JSON output below (redacted) you can see tags coded in base64 to prevent breaking CSV or JSON due to its format:
```json
"Account Email": "my-prod-account@domain.com",
"Account Name": "my-prod-account",
"Account ARN": "arn:aws:organizations::222222222222:account/o-abcde1234/111111111111",
"Account Organization": "o-abcde1234",
"Account tags": "\"eyJUYWdzIjpasf0=\""
```
The additional fields in CSV header output are as follow:
```csv
ACCOUNT_DETAILS_EMAIL,ACCOUNT_DETAILS_NAME,ACCOUNT_DETAILS_ARN,ACCOUNT_DETAILS_ORG,ACCOUNT_DETAILS_TAGS
```
### GovCloud
Prowler runs in GovCloud regions as well. To make sure it points to the right API endpoint use `-r` to either `us-gov-west-1` or `us-gov-east-1`. If not filter region is used it will look for resources in both GovCloud regions by default:

View File

@@ -92,7 +92,6 @@ cleanSTSAssumeFile() {
}
backupInitialAWSCredentials() {
if [[ $(printenv AWS_ACCESS_KEY_ID) && $(printenv AWS_SECRET_ACCESS_KEY) && $(printenv AWS_SESSION_TOKEN) ]]; then
INITIAL_AWS_ACCESS_KEY_ID=$(printenv AWS_ACCESS_KEY_ID)
INITIAL_AWS_SECRET_ACCESS_KEY=$(printenv AWS_SECRET_ACCESS_KEY)

View File

@@ -13,8 +13,5 @@
printCsvHeader() {
# >&2 echo ""
# >&2 echo "Generating \"${SEP}\" delimited report on stdout for profile $PROFILE, account $ACCOUNT_NUM"
echo "PROFILE${SEP}ACCOUNT_NUM${SEP}REGION${SEP}TITLE_ID${SEP}CHECK_RESULT${SEP}ITEM_SCORED${SEP}ITEM_LEVEL${SEP}TITLE_TEXT${SEP}CHECK_RESULT_EXTENDED${SEP}CHECK_ASFF_COMPLIANCE_TYPE${SEP}CHECK_SEVERITY${SEP}CHECK_SERVICENAME${SEP}CHECK_ASFF_RESOURCE_TYPE${SEP}CHECK_ASFF_TYPE${SEP}CHECK_RISK${SEP}CHECK_REMEDIATION${SEP}CHECK_DOC${SEP}CHECK_CAF_EPIC${SEP}CHECK_RESOURCE_ID${SEP}PROWLER_START_TIME" >> ${OUTPUT_FILE_NAME}.$EXTENSION_CSV
# echo "PROFILE${SEP}ACCOUNT_NUM${SEP}REGION${SEP}TITLE_ID${SEP}RESULT${SEP}SCORED${SEP}LEVEL${SEP}TITLE_TEXT${SEP}NOTES${SEP}COMPLIANCE${SEP}SEVERITY${SEP}SERVICENAME" | tee -a $OUTPUT_FILE_NAME.$EXTENSION_CSV
echo "PROFILE${SEP}ACCOUNT_NUM${SEP}REGION${SEP}TITLE_ID${SEP}CHECK_RESULT${SEP}ITEM_SCORED${SEP}ITEM_LEVEL${SEP}TITLE_TEXT${SEP}CHECK_RESULT_EXTENDED${SEP}CHECK_ASFF_COMPLIANCE_TYPE${SEP}CHECK_SEVERITY${SEP}CHECK_SERVICENAME${SEP}CHECK_ASFF_RESOURCE_TYPE${SEP}CHECK_ASFF_TYPE${SEP}CHECK_RISK${SEP}CHECK_REMEDIATION${SEP}CHECK_DOC${SEP}CHECK_CAF_EPIC${SEP}CHECK_RESOURCE_ID${SEP}PROWLER_START_TIME${SEP}ACCOUNT_DETAILS_EMAIL${SEP}ACCOUNT_DETAILS_NAME${SEP}ACCOUNT_DETAILS_ARN${SEP}ACCOUNT_DETAILS_ORG${SEP}ACCOUNT_DETAILS_TAGS" >> ${OUTPUT_FILE_NAME}.$EXTENSION_CSV
}

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
# Gets account details with a given ACCOUNT_ID.
# Prowler requires organizations:ListAccounts* and organizations:ListTagsForResource
# in the management account in order to get that data. SecurityAudit managed policy includes them.
# Account Tags are in json format with comma, however they are converted to Base64
# in order to avoid breaking the CSV or JSON. To use them a post-processor is needed.
get_orgs_account_details(){
echo " Prowler is getting details from the AWS Organizations Management Account: ${MANAGEMENT_ACCOUNT_ID}..."
# Assume role to recover AWS Organizations metadata
assume_role
# The following code requires organizations:ListTagsForResource
ACCOUNTS_DETAILS=$($AWSCLI $PROFILE_OPT --region "${REGION}" organizations list-accounts --output json 2>&1)
if ! grep -q -E 'AccessDenied|UnauthorizedOperation|AuthorizationError' <<< "${ACCOUNTS_DETAILS}"
then
# Prowler gets only ACTIVE accounts details
ACCOUNT_DETAILS_EMAIL=$(jq -r --arg ACCOUNT_ID "${ACCOUNT_NUM}" '.Accounts[] | select(.Status == "ACTIVE") | select(.Id == $ACCOUNT_ID) | "\(.Email)"' <<< "${ACCOUNTS_DETAILS}")
ACCOUNT_DETAILS_NAME=$(jq -r --arg ACCOUNT_ID "${ACCOUNT_NUM}" '.Accounts[] | select(.Status == "ACTIVE") | select(.Id == $ACCOUNT_ID) | "\(.Name)"' <<< "${ACCOUNTS_DETAILS}")
ACCOUNT_DETAILS_ARN=$(jq -r --arg ACCOUNT_ID "${ACCOUNT_NUM}" '.Accounts[] | select(.Status == "ACTIVE") | select(.Id == $ACCOUNT_ID) | "\(.Arn)"' <<< "${ACCOUNTS_DETAILS}")
ACCOUNT_DETAILS_ORG=$(jq -r --arg ACCOUNT_ID "${ACCOUNT_NUM}" '.Accounts[] | select(.Status == "ACTIVE") | select(.Id == $ACCOUNT_ID) | "\(.Arn)"' <<< "${ACCOUNTS_DETAILS}" | awk -F/ '{ print $2 }')
ACCOUNT_DETAILS_TAGS=$($AWSCLI $PROFILE_OPT --region "${REGION}" organizations list-tags-for-resource --resource-id "${MANAGEMENT_ACCOUNT_ID}" --output json | jq -c '. | @base64' 2>&1)
else
# textFail "${regx}: Access Denied trying to list AWS Organization accounts. Prowler requires organizations:List*" "$regx"
textInfo "Access Denied trying to list AWS Organization accounts. Prowler requires organizations:List*"
exit 1
fi
}

View File

@@ -64,6 +64,11 @@ PROWLER_PARAMETERS=$@
# $CHECK_REMEDIATION text about remediation
# $CHECK_DOC link to related documentation
# $CHECK_CAF_EPIC it can be Logging and Monitoring, IAM, Data Protection, Infrastructure Security. Incident Response is not included since CAF has not specific checks on it logs enablement are part of Logging and Monitoring.
# $ACCOUNT_DETAILS_EMAIL
# $ACCOUNT_DETAILS_NAME
# $ACCOUNT_DETAILS_ARN
# $ACCOUNT_DETAILS_ORG
# $ACCOUNT_DETAILS_TAGS
# Ensure that output directory always exists when -M is used
if [[ $MODE ]];then
@@ -102,7 +107,7 @@ textPass(){
REPREGION=$REGION
fi
if [[ "${MODES[@]}" =~ "csv" ]]; then
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}$CHECK_RESULT${SEP}$ITEM_SCORED${SEP}$ITEM_CIS_LEVEL${SEP}$TITLE_TEXT${SEP}$CHECK_RESULT_EXTENDED${SEP}$CHECK_ASFF_COMPLIANCE_TYPE${SEP}$CHECK_SEVERITY${SEP}$CHECK_SERVICENAME${SEP}$CHECK_ASFF_RESOURCE_TYPE${SEP}$CHECK_ASFF_TYPE${SEP}$CHECK_RISK${SEP}$CHECK_REMEDIATION${SEP}$CHECK_DOC${SEP}$CHECK_CAF_EPIC${SEP}$CHECK_RESOURCE_ID${SEP}$PROWLER_START_TIME" >> ${OUTPUT_FILE_NAME}.$EXTENSION_CSV
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}$CHECK_RESULT${SEP}$ITEM_SCORED${SEP}$ITEM_CIS_LEVEL${SEP}$TITLE_TEXT${SEP}$CHECK_RESULT_EXTENDED${SEP}$CHECK_ASFF_COMPLIANCE_TYPE${SEP}$CHECK_SEVERITY${SEP}$CHECK_SERVICENAME${SEP}$CHECK_ASFF_RESOURCE_TYPE${SEP}$CHECK_ASFF_TYPE${SEP}$CHECK_RISK${SEP}$CHECK_REMEDIATION${SEP}$CHECK_DOC${SEP}$CHECK_CAF_EPIC${SEP}$CHECK_RESOURCE_ID${SEP}$PROWLER_START_TIME${SEP}$ACCOUNT_DETAILS_EMAIL${SEP}$ACCOUNT_DETAILS_NAME${SEP}$ACCOUNT_DETAILS_ARN${SEP}$ACCOUNT_DETAILS_ORG${SEP}$ACCOUNT_DETAILS_TAGS" >> ${OUTPUT_FILE_NAME}.$EXTENSION_CSV
fi
if [[ "${MODES[@]}" =~ "json" ]]; then
generateJsonOutput "$1" "Pass" "$CHECK_RESOURCE_ID" >> ${OUTPUT_FILE_NAME}.$EXTENSION_JSON
@@ -144,7 +149,7 @@ textInfo(){
REPREGION=$REGION
fi
if [[ "${MODES[@]}" =~ "csv" ]]; then
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}$CHECK_RESULT${SEP}$ITEM_SCORED${SEP}$ITEM_CIS_LEVEL${SEP}$TITLE_TEXT${SEP}$CHECK_RESULT_EXTENDED${SEP}$CHECK_ASFF_COMPLIANCE_TYPE${SEP}$CHECK_SEVERITY${SEP}$CHECK_SERVICENAME${SEP}$CHECK_ASFF_RESOURCE_TYPE${SEP}$CHECK_ASFF_TYPE${SEP}$CHECK_RISK${SEP}$CHECK_REMEDIATION${SEP}$CHECK_DOC${SEP}$CHECK_CAF_EPIC${SEP}$CHECK_RESOURCE_ID${SEP}$PROWLER_START_TIME" >> ${OUTPUT_FILE_NAME}.$EXTENSION_CSV
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}$CHECK_RESULT${SEP}$ITEM_SCORED${SEP}$ITEM_CIS_LEVEL${SEP}$TITLE_TEXT${SEP}$CHECK_RESULT_EXTENDED${SEP}$CHECK_ASFF_COMPLIANCE_TYPE${SEP}$CHECK_SEVERITY${SEP}$CHECK_SERVICENAME${SEP}$CHECK_ASFF_RESOURCE_TYPE${SEP}$CHECK_ASFF_TYPE${SEP}$CHECK_RISK${SEP}$CHECK_REMEDIATION${SEP}$CHECK_DOC${SEP}$CHECK_CAF_EPIC${SEP}$CHECK_RESOURCE_ID${SEP}$PROWLER_START_TIME${SEP}$ACCOUNT_DETAILS_EMAIL${SEP}$ACCOUNT_DETAILS_NAME${SEP}$ACCOUNT_DETAILS_ARN${SEP}$ACCOUNT_DETAILS_ORG${SEP}$ACCOUNT_DETAILS_TAGS" >> ${OUTPUT_FILE_NAME}.$EXTENSION_CSV
fi
if [[ "${MODES[@]}" =~ "json" ]]; then
generateJsonOutput "$1" "Info" "$CHECK_RESOURCE_ID" >> ${OUTPUT_FILE_NAME}.${EXTENSION_JSON}
@@ -209,7 +214,7 @@ textFail(){
fi
if [[ "${MODES[@]}" =~ "csv" ]]; then
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}$CHECK_RESULT${SEP}$ITEM_SCORED${SEP}$ITEM_CIS_LEVEL${SEP}$TITLE_TEXT${SEP}$CHECK_RESULT_EXTENDED${SEP}$CHECK_ASFF_COMPLIANCE_TYPE${SEP}$CHECK_SEVERITY${SEP}$CHECK_SERVICENAME${SEP}$CHECK_ASFF_RESOURCE_TYPE${SEP}$CHECK_ASFF_TYPE${SEP}$CHECK_RISK${SEP}$CHECK_REMEDIATION${SEP}$CHECK_DOC${SEP}$CHECK_CAF_EPIC${SEP}$CHECK_RESOURCE_ID${SEP}$PROWLER_START_TIME" >> ${OUTPUT_FILE_NAME}.$EXTENSION_CSV
echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}$CHECK_RESULT${SEP}$ITEM_SCORED${SEP}$ITEM_CIS_LEVEL${SEP}$TITLE_TEXT${SEP}$CHECK_RESULT_EXTENDED${SEP}$CHECK_ASFF_COMPLIANCE_TYPE${SEP}$CHECK_SEVERITY${SEP}$CHECK_SERVICENAME${SEP}$CHECK_ASFF_RESOURCE_TYPE${SEP}$CHECK_ASFF_TYPE${SEP}$CHECK_RISK${SEP}$CHECK_REMEDIATION${SEP}$CHECK_DOC${SEP}$CHECK_CAF_EPIC${SEP}$CHECK_RESOURCE_ID${SEP}$PROWLER_START_TIME${SEP}$ACCOUNT_DETAILS_EMAIL${SEP}$ACCOUNT_DETAILS_NAME${SEP}$ACCOUNT_DETAILS_ARN${SEP}$ACCOUNT_DETAILS_ORG${SEP}$ACCOUNT_DETAILS_TAGS" >> ${OUTPUT_FILE_NAME}.$EXTENSION_CSV
fi
if [[ "${MODES[@]}" =~ "json" ]]; then
generateJsonOutput "$1" "${level}" "$CHECK_RESOURCE_ID">> ${OUTPUT_FILE_NAME}.${EXTENSION_JSON}
@@ -301,6 +306,11 @@ generateJsonOutput(){
--arg CHECK_REMEDIATION "$CHECK_REMEDIATION" \
--arg CHECK_DOC "$CHECK_DOC" \
--arg CHECK_RESOURCE_ID "$resource_id" \
--arg ACCOUNT_DETAILS_EMAIL "$ACCOUNT_DETAILS_EMAIL" \
--arg ACCOUNT_DETAILS_NAME "$ACCOUNT_DETAILS_NAME" \
--arg ACCOUNT_DETAILS_ARN "$ACCOUNT_DETAILS_ARN" \
--arg ACCOUNT_DETAILS_ORG "$ACCOUNT_DETAILS_ORG" \
--arg ACCOUNT_DETAILS_TAGS "$ACCOUNT_DETAILS_TAGS" \
-n '{
"Profile": $PROFILE,
"Account Number": $ACCOUNT_NUM,
@@ -319,7 +329,12 @@ generateJsonOutput(){
"Risk": $CHECK_RISK,
"Remediation": $CHECK_REMEDIATION,
"Doc link": $CHECK_DOC,
"Resource ID": $CHECK_RESOURCE_ID
"Resource ID": $CHECK_RESOURCE_ID,
"Account Email": $ACCOUNT_DETAILS_EMAIL,
"Account Name": $ACCOUNT_DETAILS_NAME,
"Account ARN": $ACCOUNT_DETAILS_ARN,
"Account Organization": $ACCOUNT_DETAILS_ORG,
"Account tags": $ACCOUNT_DETAILS_TAGS
}'
}

31
prowler
View File

@@ -110,13 +110,15 @@ USAGE:
-z Failed checks do not trigger exit code 3.
-Z Specify one or multiple check ids separated by commas that will trigger exit code 3 if they fail. Unspecified checks will not trigger exit code 3. This will override "-z".
(i.e.: "-Z check11,check12" will cause check11 and/or check12 to trigger exit code 3)
-O <mgmnt acct ID> Specify AWS Organizations management account ID. Used to get account details, requires -R.
(requires organizations:ListAccounts* and organizations:ListTagsForResource)
-V Show version number & exit.
-h This help.
"
exit
}
while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:" OPTION; do
while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:O:" OPTION; do
case $OPTION in
h )
usage
@@ -223,6 +225,10 @@ while getopts ":hlLkqp:r:c:C:g:f:m:M:E:x:enbVsSI:A:R:T:w:N:o:B:D:F:zZ:" OPTION;
Z )
FAILED_CHECK_FAILED_SCAN_LIST=$OPTARG
;;
O )
MANAGEMENT_ACCOUNT_ID=$OPTARG
;;
: )
echo ""
echo "$OPTRED ERROR!$OPTNORMAL -$OPTARG requires an argument"
@@ -287,6 +293,7 @@ unset AWS_DEFAULT_OUTPUT
. $PROWLER_DIR/include/connection_tests
. $PROWLER_DIR/include/securityhub_integration
. $PROWLER_DIR/include/junit_integration
. $PROWLER_DIR/include/organizations_metadata
# Parses the check file into CHECK_ID's.
if [[ -n "$CHECK_FILE" ]]; then
@@ -644,6 +651,28 @@ if is_junit_output_enabled; then
prepare_junit_output
fi
# First, check AWS Organizations Metadata
if [[ -n "${MANAGEMENT_ACCOUNT_ID}" && -n "${ROLE_TO_ASSUME}" ]]
then
# Backing up initial credentials
backupInitialAWSCredentials
# Backing up initial account to assume
INITIAL_ACCOUNT_TO_ASSUME="${ACCOUNT_TO_ASSUME}"
# Set the new account to assume to recover AWS Organizations Metadata
ACCOUNT_TO_ASSUME="${MANAGEMENT_ACCOUNT_ID}"
# Recover AWS Organizations Metadata
get_orgs_account_details
# Restoring account to assume to -A field after getting account metadata
ACCOUNT_TO_ASSUME="${INITIAL_ACCOUNT_TO_ASSUME}"
# Restoring initial credentials
restoreInitialAWSCredentials
fi
# Gather account data / test aws cli connectivity
getWhoami
if [[ -n "${ACCOUNT_TO_ASSUME}" || -n "${ROLE_TO_ASSUME}" ]]; then