Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aws): add new check iam_root_credentials_management_enabled #5801

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,36 @@
class iam_no_root_access_key(Check):
def execute(self) -> Check_Report_AWS:
findings = []
response = iam_client.credential_report

for user in response:
if user["user"] == "<root_account>":
report = Check_Report_AWS(self.metadata())
report.region = iam_client.region
report.resource_id = user["user"]
report.resource_arn = user["arn"]
if (
user["access_key_1_active"] == "false"
and user["access_key_2_active"] == "false"
):
report.status = "PASS"
report.status_extended = (
f"User {user['user']} does not have access keys."
)
elif (
user["access_key_1_active"] == "true"
and user["access_key_2_active"] == "true"
):
report.status = "FAIL"
report.status_extended = (
f"User {user['user']} has two active access keys."
)
else:
report.status = "FAIL"
report.status_extended = (
f"User {user['user']} has one active access key."
)
findings.append(report)
# Check if the root credentials are managed by AWS Organizations
if "RootCredentialsManagement" not in iam_client.organization_features:
for user in iam_client.credential_report:
if user["user"] == "<root_account>":
report = Check_Report_AWS(self.metadata())
report.region = iam_client.region
report.resource_id = user["user"]
report.resource_arn = user["arn"]
if (
user["access_key_1_active"] == "false"
and user["access_key_2_active"] == "false"
):
report.status = "PASS"
report.status_extended = (
"Root account does not have access keys."
)
elif (
user["access_key_1_active"] == "true"
and user["access_key_2_active"] == "true"
):
report.status = "FAIL"
report.status_extended = (
"Root account has two active access keys."
)
else:
report.status = "FAIL"
report.status_extended = (
"Root account has one active access key."
)
findings.append(report)
break

return findings
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"Provider": "aws",
"CheckID": "iam_root_credentials_management_enabled",
"CheckTitle": "Ensure centralized root credentials management is enabled",
"CheckType": [],
"ServiceName": "iam",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "high",
"ResourceType": "Other",
"Description": "Checks if centralized management of root credentials for member accounts in AWS Organizations is enabled. This ensures that root credentials are managed centrally, reducing the risk of unauthorized access or mismanagement.",
"Risk": "Without centralized root credentials management, member accounts retain full control over their root user credentials, increasing the risk of credential misuse, mismanagement, or compromise.",
"RelatedUrl": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html#id_root-user-access-management",
"Remediation": {
"Code": {
"CLI": "aws iam enable-organizations-root-credentials-management",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable centralized management of root access for member accounts using the CLI or IAM console.",
"Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-enable-root-access.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [
"iam_root_hardware_mfa_enabled",
"iam_root_mfa_enabled"
],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.iam_client import iam_client
from prowler.providers.aws.services.organizations.organizations_client import (
organizations_client,
)


class iam_root_credentials_management_enabled(Check):
def execute(self) -> Check_Report_AWS:
findings = []
if (
organizations_client.organization
and organizations_client.organization.status == "ACTIVE"
):
report = Check_Report_AWS(self.metadata())
report.region = iam_client.region
report.resource_arn = iam_client.audited_account_arn
report.resource_id = iam_client.audited_account
if "RootCredentialsManagement" in iam_client.organization_features:
report.status = "PASS"
report.status_extended = "Root credentials management is enabled."
else:
report.status = "FAIL"
report.status_extended = "Root credentials management is not enabled."
findings.append(report)

return findings
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,36 @@
class iam_root_hardware_mfa_enabled(Check):
def execute(self) -> Check_Report_AWS:
findings = []
# This check is only avaible in Commercial Partition
# This check is only available in Commercial Partition
if iam_client.audited_partition == "aws":
if iam_client.account_summary:
virtual_mfa = False
report = Check_Report_AWS(self.metadata())
report.region = iam_client.region
report.resource_id = "<root_account>"
report.resource_arn = iam_client.mfa_arn_template
# Check if the root credentials are managed by AWS Organizations
if "RootCredentialsManagement" not in iam_client.organization_features:
if iam_client.account_summary:
virtual_mfa = False
report = Check_Report_AWS(self.metadata())
report.region = iam_client.region
report.resource_id = "<root_account>"
report.resource_arn = iam_client.mfa_arn_template

if iam_client.account_summary["SummaryMap"]["AccountMFAEnabled"] > 0:
for mfa in iam_client.virtual_mfa_devices:
# If the ARN of the associated IAM user of the Virtual MFA device is "arn:aws:iam::[aws-account-id]:root", your AWS root account is not using a hardware-based MFA device for MFA protection.
if "root" in mfa.get("User", {}).get("Arn", ""):
virtual_mfa = True
report.status = "FAIL"
report.status_extended = "Root account has a virtual MFA instead of a hardware MFA device enabled."
if not virtual_mfa:
report.status = "PASS"
report.status_extended = (
"Root account has a hardware MFA device enabled."
)
else:
report.status = "FAIL"
report.status_extended = "MFA is not enabled for root account."
if (
iam_client.account_summary["SummaryMap"]["AccountMFAEnabled"]
> 0
):
for mfa in iam_client.virtual_mfa_devices:
# If the ARN of the associated IAM user of the Virtual MFA device is "arn:aws:iam::[aws-account-id]:root", your AWS root account is not using a hardware-based MFA device for MFA protection.
if "root" in mfa.get("User", {}).get("Arn", ""):
virtual_mfa = True
report.status = "FAIL"
report.status_extended = "Root account has a virtual MFA instead of a hardware MFA device enabled."
if not virtual_mfa:
report.status = "PASS"
report.status_extended = (
"Root account has a hardware MFA device enabled."
)
else:
report.status = "FAIL"
report.status_extended = "MFA is not enabled for root account."

Check warning on line 36 in prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.py#L35-L36

Added lines #L35 - L36 were not covered by tests

findings.append(report)
findings.append(report)

return findings
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@
class iam_root_mfa_enabled(Check):
def execute(self) -> Check_Report_AWS:
findings = []
if iam_client.credential_report:
for user in iam_client.credential_report:
if user["user"] == "<root_account>":
report = Check_Report_AWS(self.metadata())
report.region = iam_client.region
report.resource_id = user["user"]
report.resource_arn = user["arn"]
if user["mfa_active"] == "false":
report.status = "FAIL"
report.status_extended = "MFA is not enabled for root account."
else:
report.status = "PASS"
report.status_extended = "MFA is enabled for root account."
findings.append(report)
# Check if the root credentials are managed by AWS Organizations
if "RootCredentialsManagement" not in iam_client.organization_features:
if iam_client.credential_report:
for user in iam_client.credential_report:
if user["user"] == "<root_account>":
report = Check_Report_AWS(self.metadata())
report.region = iam_client.region
report.resource_id = user["user"]
report.resource_arn = user["arn"]
if user["mfa_active"] == "false":
report.status = "FAIL"
report.status_extended = (
"MFA is not enabled for root account."
)
else:
report.status = "PASS"
report.status_extended = "MFA is enabled for root account."
findings.append(report)

return findings
27 changes: 27 additions & 0 deletions prowler/providers/aws/services/iam/iam_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@
self._get_last_accessed_services()
self.user_temporary_credentials_usage = {}
self._get_user_temporary_credentials_usage()
self.organization_features = []
self._list_organizations_features()
# List missing tags
self.__threading_call__(self._list_tags, self.users)
self.__threading_call__(self._list_tags, self.roles)
Expand Down Expand Up @@ -964,6 +966,31 @@
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)

def _list_organizations_features(self):
logger.info("IAM - List Organization Features...")
try:
organization_features = self.client.list_organizations_features()
self.organization_features = organization_features.get(
"EnabledFeatures", []
)
except ClientError as error:
if error.response["Error"]["Code"] == "OrganizationNotFoundException":
logger.warning(

Check warning on line 978 in prowler/providers/aws/services/iam/iam_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/iam/iam_service.py#L978

Added line #L978 was not covered by tests
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
elif error.response["Error"]["Code"] == "ServiceAccessNotEnabledException":
logger.warning(

Check warning on line 982 in prowler/providers/aws/services/iam/iam_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/iam/iam_service.py#L982

Added line #L982 was not covered by tests
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)


class MFADevice(BaseModel):
serial_number: str
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,19 @@
class organizations_account_part_of_organizations(Check):
def execute(self):
findings = []
for org in organizations_client.organizations:
if organizations_client.organization:
report = Check_Report_AWS(self.metadata())
if org.status == "ACTIVE":
if organizations_client.organization.status == "ACTIVE":
report.status = "PASS"
report.status_extended = (
f"AWS Organization {org.id} contains this AWS account."
)
report.status_extended = f"AWS Organization {organizations_client.organization.id} contains this AWS account."
else:
report.status = "FAIL"
report.status_extended = (
"AWS Organizations is not in-use for this AWS Account."
)
report.region = organizations_client.region
report.resource_id = org.id
report.resource_arn = org.arn
report.resource_id = organizations_client.organization.id
report.resource_arn = organizations_client.organization.arn
findings.append(report)

return findings
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,33 @@ def execute(self):
)
)

for org in organizations_client.organizations:
if org.status == "ACTIVE":
report = Check_Report_AWS(self.metadata())
report.resource_id = org.id
report.resource_arn = org.arn
report.region = organizations_client.region
if org.delegated_administrators is None:
# Access Denied to list_policies
continue
if org.delegated_administrators:
for delegated_administrator in org.delegated_administrators:
if (
organizations_client.organization
and organizations_client.organization.status == "ACTIVE"
):
report = Check_Report_AWS(self.metadata())
report.resource_id = organizations_client.organization.id
report.resource_arn = organizations_client.organization.arn
report.region = organizations_client.region
if (
organizations_client.organization.delegated_administrators is not None
): # Check if Access Denied to list_delegated_administrators
if organizations_client.organization.delegated_administrators:
for (
delegated_administrator
) in organizations_client.organization.delegated_administrators:
if (
delegated_administrator.id
not in organizations_trusted_delegated_administrators
):
report.status = "FAIL"
report.status_extended = f"AWS Organization {org.id} has an untrusted Delegated Administrator: {delegated_administrator.id}."
report.status_extended = f"AWS Organization {organizations_client.organization.id} has an untrusted Delegated Administrator: {delegated_administrator.id}."
else:
report.status = "PASS"
report.status_extended = f"AWS Organization {org.id} has a trusted Delegated Administrator: {delegated_administrator.id}."
report.status_extended = f"AWS Organization {organizations_client.organization.id} has a trusted Delegated Administrator: {delegated_administrator.id}."
else:
report.status = "PASS"
report.status_extended = (
f"AWS Organization {org.id} has no Delegated Administrators."
)
report.status_extended = f"AWS Organization {organizations_client.organization.id} has no Delegated Administrators."

findings.append(report)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ class organizations_opt_out_ai_services_policy(Check):
def execute(self):
findings = []

for org in organizations_client.organizations:
if org.policies is not None: # Access Denied to list_policies
if organizations_client.organization:
if (
organizations_client.organization.policies is not None
): # Access Denied to list_policies
report = Check_Report_AWS(self.metadata())
report.resource_id = org.id
report.resource_arn = org.arn
report.resource_id = organizations_client.organization.id
report.resource_arn = organizations_client.organization.arn
report.region = organizations_client.region
report.status = "FAIL"
report.status_extended = (
"AWS Organizations is not in-use for this AWS Account."
)
if org.status == "ACTIVE":
report.status_extended = f"AWS Organization {org.id} has not opted out of all AI services, granting consent for AWS to access its data."
for policy in org.policies.get("AISERVICES_OPT_OUT_POLICY", []):
if organizations_client.organization.status == "ACTIVE":
report.status_extended = f"AWS Organization {organizations_client.organization.id} has not opted out of all AI services, granting consent for AWS to access its data."
for policy in organizations_client.organization.policies.get(
"AISERVICES_OPT_OUT_POLICY", []
):
if (
policy.content.get("services", {})
.get("default", {})
Expand All @@ -29,7 +33,7 @@ def execute(self):
== "optOut"
):
report.status = "PASS"
report.status_extended = f"AWS Organization {org.id} has opted out of all AI services, not granting consent for AWS to access its data."
report.status_extended = f"AWS Organization {organizations_client.organization.id} has opted out of all AI services, not granting consent for AWS to access its data."
break

findings.append(report)
Expand Down
Loading