mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 06:45:08 +00:00
feat(aws): Get organizations metadata if delegated admin (#3435)
This commit is contained in:
@@ -1,23 +1,28 @@
|
||||
# AWS Organizations
|
||||
|
||||
## Get AWS Account details from your AWS Organization
|
||||
|
||||
Prowler allows you to 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.
|
||||
Prowler allows you to get additional information of the scanned account from AWS Organizations.
|
||||
|
||||
If you have AWS Organizations 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.
|
||||
If you have AWS Organizations enabled, Prowler can get your account details like account name, email, ARN, organization id and tags and you will have them next to every finding's output.
|
||||
|
||||
In order to do that you can use the option `-O`/`--organizations-role <organizations_role_arn>`. See the following sample command:
|
||||
In order to do that you can use the argument `-O`/`--organizations-role <organizations_role_arn>`. If this argument is not present Prowler will try to fetch that information automatically if the AWS account is a delegated administrator for the AWS Organization.
|
||||
|
||||
???+ note
|
||||
Refer [here](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_delegate_policies.html) for more information about AWS Organizations delegated administrator.
|
||||
|
||||
See the following sample command:
|
||||
|
||||
```shell
|
||||
prowler aws \
|
||||
-O arn:aws:iam::<management_organizations_account_id>:role/<role_name>
|
||||
```
|
||||
|
||||
???+ note
|
||||
Make sure the role in your AWS Organizations management account has the permissions `organizations:ListAccounts*` and `organizations:ListTagsForResource`.
|
||||
Make sure the role in your AWS Organizations management account has the permissions `organizations:DescribeAccount` and `organizations:ListTagsForResource`.
|
||||
|
||||
In that command Prowler will scan the account and getting the account details from the AWS Organizations management account assuming a role and creating two reports with those details in JSON and CSV.
|
||||
Prowler will scan the AWS account and get the account details from AWS Organizations.
|
||||
|
||||
In the JSON output below (redacted) you can see tags coded in base64 to prevent breaking CSV or JSON due to its format:
|
||||
In the JSON output below 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",
|
||||
@@ -27,13 +32,15 @@ In the JSON output below (redacted) you can see tags coded in base64 to prevent
|
||||
"Account tags": "\"eyJUYWdzIjpasf0=\""
|
||||
```
|
||||
|
||||
The additional fields in CSV header output are as follow:
|
||||
The additional fields in CSV header output are as follows:
|
||||
|
||||
```csv
|
||||
ACCOUNT_DETAILS_EMAIL,ACCOUNT_DETAILS_NAME,ACCOUNT_DETAILS_ARN,ACCOUNT_DETAILS_ORG,ACCOUNT_DETAILS_TAGS
|
||||
```
|
||||
- ACCOUNT_DETAILS_EMAIL
|
||||
- ACCOUNT_DETAILS_NAME
|
||||
- ACCOUNT_DETAILS_ARN
|
||||
- ACCOUNT_DETAILS_ORG
|
||||
- ACCOUNT_DETAILS_TAGS
|
||||
|
||||
## Extra: run Prowler across all accounts in AWS Organizations by assuming roles
|
||||
## Extra: Run Prowler across all accounts in AWS Organizations by assuming roles
|
||||
|
||||
If you want to run Prowler across all accounts of AWS Organizations you can do this:
|
||||
|
||||
|
||||
@@ -1,40 +1,61 @@
|
||||
import sys
|
||||
|
||||
from boto3 import client
|
||||
from boto3 import client, session
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.lib.audit_info.models import AWS_Organizations_Info
|
||||
|
||||
|
||||
def get_organizations_metadata(
|
||||
metadata_account: str, assumed_credentials: dict
|
||||
) -> AWS_Organizations_Info:
|
||||
aws_account_id: str,
|
||||
assumed_credentials: dict = None,
|
||||
session: session = None,
|
||||
) -> tuple[dict, dict]:
|
||||
try:
|
||||
organizations_client = client(
|
||||
"organizations",
|
||||
aws_access_key_id=assumed_credentials["Credentials"]["AccessKeyId"],
|
||||
aws_secret_access_key=assumed_credentials["Credentials"]["SecretAccessKey"],
|
||||
aws_session_token=assumed_credentials["Credentials"]["SessionToken"],
|
||||
)
|
||||
if assumed_credentials:
|
||||
organizations_client = client(
|
||||
"organizations",
|
||||
aws_access_key_id=assumed_credentials["Credentials"]["AccessKeyId"],
|
||||
aws_secret_access_key=assumed_credentials["Credentials"][
|
||||
"SecretAccessKey"
|
||||
],
|
||||
aws_session_token=assumed_credentials["Credentials"]["SessionToken"],
|
||||
)
|
||||
if session:
|
||||
organizations_client = session.client("organizations")
|
||||
else:
|
||||
organizations_client = client("organizations")
|
||||
|
||||
organizations_metadata = organizations_client.describe_account(
|
||||
AccountId=metadata_account
|
||||
AccountId=aws_account_id
|
||||
)
|
||||
list_tags_for_resource = organizations_client.list_tags_for_resource(
|
||||
ResourceId=metadata_account
|
||||
ResourceId=aws_account_id
|
||||
)
|
||||
|
||||
return organizations_metadata, list_tags_for_resource
|
||||
except Exception as error:
|
||||
logger.critical(f"{error.__class__.__name__} -- {error}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
logger.warning(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return {}, {}
|
||||
|
||||
|
||||
def parse_organizations_metadata(metadata: dict, tags: dict) -> AWS_Organizations_Info:
|
||||
try:
|
||||
# Convert Tags dictionary to String
|
||||
account_details_tags = ""
|
||||
for tag in list_tags_for_resource["Tags"]:
|
||||
for tag in tags.get("Tags", {}):
|
||||
account_details_tags += tag["Key"] + ":" + tag["Value"] + ","
|
||||
|
||||
account_details = metadata.get("Account", {})
|
||||
organizations_info = AWS_Organizations_Info(
|
||||
account_details_email=organizations_metadata["Account"]["Email"],
|
||||
account_details_name=organizations_metadata["Account"]["Name"],
|
||||
account_details_arn=organizations_metadata["Account"]["Arn"],
|
||||
account_details_org=organizations_metadata["Account"]["Arn"].split("/")[1],
|
||||
account_details_tags=account_details_tags,
|
||||
account_details_email=account_details.get("Email", ""),
|
||||
account_details_name=account_details.get("Name", ""),
|
||||
account_details_arn=account_details.get("Arn", ""),
|
||||
account_details_org=account_details.get("Arn", "").split("/")[1],
|
||||
account_details_tags=account_details_tags.rstrip(","),
|
||||
)
|
||||
return organizations_info
|
||||
except Exception as error:
|
||||
logger.warning(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ from prowler.providers.aws.lib.credentials.credentials import (
|
||||
)
|
||||
from prowler.providers.aws.lib.organizations.organizations import (
|
||||
get_organizations_metadata,
|
||||
parse_organizations_metadata,
|
||||
)
|
||||
from prowler.providers.aws.lib.resource_api_tagging.resource_api_tagging import (
|
||||
get_tagged_resources,
|
||||
@@ -228,17 +229,53 @@ Azure Identity Type: {Fore.YELLOW}[{audit_info.identity.identity_type}]{Style.RE
|
||||
|
||||
else:
|
||||
logger.info(
|
||||
f"Getting organizations metadata for account {organizations_role_arn}"
|
||||
f"Getting organizations metadata for account with IAM Role ARN {organizations_role_arn}"
|
||||
)
|
||||
assumed_credentials = assume_role(
|
||||
aws_provider.aws_session,
|
||||
aws_provider.role_info,
|
||||
sts_endpoint_region,
|
||||
)
|
||||
current_audit_info.organizations_metadata = get_organizations_metadata(
|
||||
current_audit_info.audited_account, assumed_credentials
|
||||
organizations_metadata, list_tags_for_resource = (
|
||||
get_organizations_metadata(
|
||||
current_audit_info.audited_account, assumed_credentials
|
||||
)
|
||||
)
|
||||
current_audit_info.organizations_metadata = (
|
||||
parse_organizations_metadata(
|
||||
organizations_metadata, list_tags_for_resource
|
||||
)
|
||||
)
|
||||
logger.info(
|
||||
f"Organizations metadata retrieved with IAM Role ARN {organizations_role_arn}"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
logger.info(
|
||||
"Getting organizations metadata for account if it is a delegated administrator"
|
||||
)
|
||||
organizations_metadata, list_tags_for_resource = (
|
||||
get_organizations_metadata(
|
||||
aws_account_id=current_audit_info.audited_account,
|
||||
session=current_audit_info.audit_session,
|
||||
)
|
||||
)
|
||||
if organizations_metadata:
|
||||
current_audit_info.organizations_metadata = (
|
||||
parse_organizations_metadata(
|
||||
organizations_metadata, list_tags_for_resource
|
||||
)
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Organizations metadata retrieved as a delegated administrator"
|
||||
)
|
||||
except Exception as error:
|
||||
# If the account is not a delegated administrator for AWS Organizations a credentials error will be thrown
|
||||
# Since it is a permission issue for an optional we'll raise a warning
|
||||
logger.warning(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
logger.info("Organizations metadata retrieved")
|
||||
|
||||
# Setting default region of session
|
||||
if current_audit_info.audit_session.region_name:
|
||||
|
||||
@@ -3,19 +3,23 @@ import json
|
||||
import boto3
|
||||
from moto import mock_aws
|
||||
|
||||
from prowler.providers.aws.lib.audit_info.models import AWS_Organizations_Info
|
||||
from prowler.providers.aws.lib.organizations.organizations import (
|
||||
get_organizations_metadata,
|
||||
parse_organizations_metadata,
|
||||
)
|
||||
from tests.providers.aws.audit_info_utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
)
|
||||
|
||||
AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
|
||||
|
||||
class Test_AWS_Organizations:
|
||||
@mock_aws
|
||||
def test_organizations(self):
|
||||
client = boto3.client("organizations", region_name="us-east-1")
|
||||
iam_client = boto3.client("iam", region_name="us-east-1")
|
||||
sts_client = boto3.client("sts", region_name="us-east-1")
|
||||
client = boto3.client("organizations", region_name=AWS_REGION_US_EAST_1)
|
||||
iam_client = boto3.client("iam", region_name=AWS_REGION_US_EAST_1)
|
||||
sts_client = boto3.client("sts", region_name=AWS_REGION_US_EAST_1)
|
||||
|
||||
mockname = "mock-account"
|
||||
mockdomain = "moto-example.org"
|
||||
@@ -47,7 +51,8 @@ class Test_AWS_Organizations:
|
||||
RoleArn=iam_role_arn, RoleSessionName=session_name
|
||||
)
|
||||
|
||||
org = get_organizations_metadata(account_id, assumed_role)
|
||||
metadata, tags = get_organizations_metadata(account_id, assumed_role)
|
||||
org = parse_organizations_metadata(metadata, tags)
|
||||
|
||||
assert org.account_details_email == mockemail
|
||||
assert org.account_details_name == mockname
|
||||
@@ -56,4 +61,26 @@ class Test_AWS_Organizations:
|
||||
== f"arn:aws:organizations::{AWS_ACCOUNT_NUMBER}:account/{org_id}/{account_id}"
|
||||
)
|
||||
assert org.account_details_org == org_id
|
||||
assert org.account_details_tags == "key:value,"
|
||||
assert org.account_details_tags == "key:value"
|
||||
|
||||
def test_parse_organizations_metadata(self):
|
||||
tags = {"Tags": [{"Key": "test-key", "Value": "test-value"}]}
|
||||
name = "test-name"
|
||||
email = "test-email"
|
||||
organization_name = "test-org"
|
||||
arn = f"arn:aws:organizations::{AWS_ACCOUNT_NUMBER}:organization/{organization_name}"
|
||||
metadata = {
|
||||
"Account": {
|
||||
"Name": name,
|
||||
"Email": email,
|
||||
"Arn": arn,
|
||||
}
|
||||
}
|
||||
org = parse_organizations_metadata(metadata, tags)
|
||||
|
||||
assert isinstance(org, AWS_Organizations_Info)
|
||||
assert org.account_details_email == email
|
||||
assert org.account_details_name == name
|
||||
assert org.account_details_arn == arn
|
||||
assert org.account_details_org == organization_name
|
||||
assert org.account_details_tags == "test-key:test-value"
|
||||
|
||||
Reference in New Issue
Block a user