Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion ScoutSuite/providers/aws/facade/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ async def get_credential_reports(self):
print_exception(f'Failed to download credential report: {e}')
return []

async def get_organizations_root_credentials_managed(self):
"""
Check if centralized root access management is enabled for the organization.
Returns True if RootCredentialsManagement is among the enabled features.
Returns False if the API call fails (e.g. not in an org, insufficient permissions).
"""
client = AWSFacadeUtils.get_client('iam', self.session)
try:
response = await run_concurrently(client.list_organizations_features)
enabled_features = response.get('EnabledFeatures', [])
return 'RootCredentialsManagement' in enabled_features
except Exception as e:
# Expected to fail when not in an org or lacking iam:ListOrganizationsFeatures permission
print_warning(f'Could not check Organizations root credentials management: {e}')
return False

async def get_groups(self):
groups = await AWSFacadeUtils.get_all_pages('iam', None, self.session, 'list_groups', 'Groups')
await get_and_set_concurrently(
Expand Down Expand Up @@ -270,4 +286,3 @@ def _normalize_single_statement(self, statement):
statement[resource_string] = [statement[resource_string]]
# Result
return statement

16 changes: 16 additions & 0 deletions ScoutSuite/providers/aws/resources/iam/credentialreports.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
class CredentialReports(AWSResources):
async def fetch_all(self):
raw_credential_reports = await self.facade.iam.get_credential_reports()

# Check if centralized root access management is enabled for this org.
# When enabled, root credentials can be removed from member accounts,
# making MFA checks irrelevant for those accounts.
self._root_credentials_managed_centrally = (
await self.facade.iam.get_organizations_root_credentials_managed()
)

for raw_credential_report in raw_credential_reports:
name, resource = await self._parse_credential_reports(raw_credential_report)
self[name] = resource
Expand All @@ -31,6 +39,14 @@ async def _parse_credential_reports(self, raw_credential_report):
raw_credential_report['cert_1_active'] = raw_credential_report['cert_1_active']
raw_credential_report['cert_2_active'] = raw_credential_report['cert_2_active']

# Flag root accounts where credentials are centrally managed via
# AWS Organizations. When root credentials have been removed, MFA
# cannot and need not be configured, so MFA findings should be skipped.
raw_credential_report['root_credentials_managed_centrally'] = (
raw_credential_report['name'] == '<root_account>'
and self._root_credentials_managed_centrally
)

if raw_credential_report['mfa_active'] == 'true':
raw_credential_report['mfa_active_hardware'] = await \
self._user_has_hardware_mfa_devices(raw_credential_report['name'])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,15 @@
"iam.credential_reports.id.partition",
"notEqual",
"aws-us-gov"
],
[
"iam.credential_reports.id.root_credentials_managed_centrally",
"notTrue",
""
]
],
"keys": [
"this"
],
"id_suffix": "mfa_active_hardware"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
"iam.credential_reports.id.partition",
"notEqual",
"aws-us-gov"
],
[
"iam.credential_reports.id.root_credentials_managed_centrally",
"notTrue",
""
]
],
"keys": [
Expand Down
34 changes: 32 additions & 2 deletions tests/data/rule-configs/iam-root.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"password_last_used": null,
"password_next_rotation": "N/A",
"user": "api3",
"user_creation_time": "2018-09-18T14:58:26+00:00"
"user_creation_time": "2018-09-18T14:58:26+00:00",
"root_credentials_managed_centrally": false
},
"68dcc047c3da5bbbc3f3e9d54000b7357f0e507e": {
"access_key_1_active": "false",
Expand All @@ -55,7 +56,36 @@
"password_last_used": "2019-11-26T01:13:39+00:00",
"password_next_rotation": "not_supported",
"user": "<root_account>",
"user_creation_time": "2018-04-03T21:50:27+00:00"
"user_creation_time": "2018-04-03T21:50:27+00:00",
"root_credentials_managed_centrally": false
},
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2": {
"access_key_1_active": "false",
"access_key_1_last_rotated": "N/A",
"access_key_1_last_used_date": null,
"access_key_1_last_used_region": "N/A",
"access_key_1_last_used_service": "N/A",
"access_key_2_active": "false",
"access_key_2_last_rotated": "N/A",
"access_key_2_last_used_date": null,
"access_key_2_last_used_region": "N/A",
"access_key_2_last_used_service": "N/A",
"arn": "arn:aws:iam::999888777666:root",
"cert_1_active": "false",
"cert_1_last_rotated": "N/A",
"cert_2_active": "false",
"cert_2_last_rotated": "N/A",
"id": "<root_account>_centralized",
"last_used": null,
"mfa_active": "false",
"name": "<root_account>",
"password_enabled": "not_supported",
"password_last_changed": "not_supported",
"password_last_used": null,
"password_next_rotation": "not_supported",
"user": "<root_account>",
"user_creation_time": "2020-01-01T00:00:00+00:00",
"root_credentials_managed_centrally": true
}
}
}
Expand Down