fix(aws): get organization's metadata with assumed role (#10894)

This commit is contained in:
Pepe Fagoaga
2026-04-27 23:15:11 +02:00
committed by GitHub
parent 67234210ba
commit 7df2703db1
3 changed files with 82 additions and 2 deletions
+1
View File
@@ -15,6 +15,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🐞 Fixed
- Alibaba Cloud CS service SDK compatibility, harden other services and improve documentation [(#10871)](https://github.com/prowler-cloud/prowler/pull/10871)
- AWS Organizations metadata retrieval for delegated administrator scans by using the assumed role session instead of the pre-assume credentials [(#10894)](https://github.com/prowler-cloud/prowler/pull/10894)
- `admincenter_groups_not_public_visibility` check for M365 provider evaluating Security and Distribution groups, now restricted to Microsoft 365 (Unified) groups per CIS M365 Foundations 1.2.1 [(#10899)](https://github.com/prowler-cloud/prowler/pull/10899)
- Google Workspace check reports now store the actual domain or account resource subject instead of `provider.identity` [(#10901)](https://github.com/prowler-cloud/prowler/pull/10901)
+32 -2
View File
@@ -318,8 +318,16 @@ class AwsProvider(Provider):
########
######## AWS Organizations Metadata
# This is needed in the case we don't assume an AWS Organizations IAM Role
aws_organizations_session = self._session.original_session
# Default to the current (post-assume) session so DescribeAccount runs
# with the same identity that performs the scan. This makes delegated
# administrator scenarios work without extra configuration: when the
# scan role itself sits in the management or delegated admin account,
# it already holds the Organizations permissions needed. The
# management-account -> member-account flow is handled by the
# original-session fallback below. Use `organizations_role_arn` to
# override when Organizations lives in a different account than both
# the scan role and the original credentials.
aws_organizations_session = self._session.current_session
# Get a new session if the organizations_role_arn is set
if organizations_role_arn:
# Validate the input role
@@ -364,6 +372,28 @@ class AwsProvider(Provider):
self._organizations_metadata = self.get_organizations_info(
aws_organizations_session, self._identity.account
)
# Fallback to the original (pre-assume) session when no explicit
# organizations_role_arn is set and the current session could not
# retrieve Organizations metadata. This preserves the
# management-account -> member-account flow, where DescribeAccount is
# only allowed from the management account or a delegated
# administrator and the assumed member-account session has no
# Organizations permissions.
if (
not organizations_role_arn
and self._session.current_session is not self._session.original_session
and (
self._organizations_metadata is None
or not self._organizations_metadata.organization_id
)
):
logger.info(
"Retrying AWS Organizations metadata retrieval with the original session"
)
self._organizations_metadata = self.get_organizations_info(
self._session.original_session, self._identity.account
)
########
# Get Enabled Regions
+49
View File
@@ -455,6 +455,55 @@ class TestAWSProvider:
aws_provider.organizations_metadata.organization_arn == organization["Arn"]
)
@mock_aws
def test_aws_provider_organizations_uses_assumed_role_session_by_default(self):
# Regression test for issue #10215.
# When only `role_arn` is provided (no `organizations_role_arn`),
# the FIRST attempt to fetch Organizations metadata must use the
# assumed role session (current_session), not the pre-assume
# credentials. This mirrors the CLI: `aws sts assume-role` followed
# by `aws organizations describe-account` uses the assumed identity.
role_arn = create_role(AWS_REGION_EU_WEST_1)
captured_sessions = []
original_get_organizations_info = AwsProvider.get_organizations_info
def capture(self, organizations_session, aws_account_id):
captured_sessions.append(organizations_session)
return original_get_organizations_info(
self, organizations_session, aws_account_id
)
with patch.object(AwsProvider, "get_organizations_info", capture):
aws_provider = AwsProvider(role_arn=role_arn, session_duration=900)
assert captured_sessions[0] is aws_provider.session.current_session
assert captured_sessions[0] is not aws_provider.session.original_session
@mock_aws
def test_aws_provider_organizations_falls_back_to_original_session(self):
# When `role_arn` is provided and the assumed role session cannot
# retrieve Organizations metadata (e.g. management-account ->
# member-account flow where the member account has no Organizations
# permissions), retry with the original (pre-assume) session.
role_arn = create_role(AWS_REGION_EU_WEST_1)
captured_sessions = []
original_get_organizations_info = AwsProvider.get_organizations_info
def capture(self, organizations_session, aws_account_id):
captured_sessions.append(organizations_session)
return original_get_organizations_info(
self, organizations_session, aws_account_id
)
with patch.object(AwsProvider, "get_organizations_info", capture):
aws_provider = AwsProvider(role_arn=role_arn, session_duration=900)
assert len(captured_sessions) == 2
assert captured_sessions[0] is aws_provider.session.current_session
assert captured_sessions[1] is aws_provider.session.original_session
@mock_aws
def test_aws_provider_session_with_mfa(self):
mfa = True