diff --git a/prowler/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key.metadata.json b/prowler/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key.metadata.json index 933ffee047..0aa6cd663a 100644 --- a/prowler/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key.metadata.json +++ b/prowler/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key.metadata.json @@ -23,7 +23,7 @@ "Terraform": "" }, "Recommendation": { - "Text": "Use the credential report to check the user and ensure the access_key_1_active and access_key_2_active fields are set to FALSE.", + "Text": "Use the credential report to check the user and ensure the access_key_1_active and access_key_2_active fields are set to FALSE. If using AWS Organizations, consider enabling Centralized Root Management and removing individual root credentials.", "Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html" } }, diff --git a/prowler/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key.py b/prowler/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key.py index 155f58d9d6..bf38a91788 100644 --- a/prowler/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key.py +++ b/prowler/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key.py @@ -5,39 +5,65 @@ from prowler.providers.aws.services.iam.iam_client import iam_client class iam_no_root_access_key(Check): def execute(self) -> Check_Report_AWS: findings = [] - # Check if the root credentials are managed by AWS Organizations - if ( - iam_client.organization_features is not None - and "RootCredentialsManagement" not in iam_client.organization_features - ): + + if iam_client.credential_report: for user in iam_client.credential_report: if user["user"] == "": - report = Check_Report_AWS(metadata=self.metadata(), resource=user) - 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." + password_enabled = user["password_enabled"] == "true" + access_key_1_active = user["access_key_1_active"] == "true" + access_key_2_active = user["access_key_2_active"] == "true" + + # Only report if root actually has credentials + if password_enabled or access_key_1_active or access_key_2_active: + report = Check_Report_AWS( + metadata=self.metadata(), resource=user ) - 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." + report.region = iam_client.region + report.resource_id = user["user"] + report.resource_arn = user["arn"] + + # Check if organization manages root credentials + org_managed = ( + iam_client.organization_features is not None + and "RootCredentialsManagement" + in iam_client.organization_features ) - else: - report.status = "FAIL" - report.status_extended = ( - "Root account has one active access key." - ) - findings.append(report) + + if not access_key_1_active and not access_key_2_active: + report.status = "PASS" + if org_managed: + report.status_extended = ( + "Root account has password configured but no access keys. " + "Consider removing individual root credentials since organizational " + "root management is active." + ) + else: + report.status_extended = ( + "Root account does not have access keys." + ) + elif access_key_1_active and access_key_2_active: + report.status = "FAIL" + if org_managed: + report.status_extended = ( + "Root account has two active access keys " + "despite organizational root management being enabled." + ) + else: + report.status_extended = ( + "Root account has two active access keys." + ) + else: + report.status = "FAIL" + if org_managed: + report.status_extended = ( + "Root account has one active access key " + "despite organizational root management being enabled." + ) + else: + report.status_extended = ( + "Root account has one active access key." + ) + findings.append(report) break return findings diff --git a/prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.metadata.json b/prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.metadata.json index 3332283487..06ba3125d0 100644 --- a/prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.metadata.json +++ b/prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.metadata.json @@ -23,7 +23,7 @@ "Terraform": "" }, "Recommendation": { - "Text": "Using IAM console navigate to Dashboard and expand Activate MFA on your root account.", + "Text": "Using IAM console navigate to Dashboard and expand Activate MFA on your root account. If using AWS Organizations, consider enabling Centralized Root Management and removing individual root credentials.", "Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html#id_root-user_manage_mfa" } }, diff --git a/prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.py b/prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.py index 7b3ad5d811..dca0bd7f87 100644 --- a/prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.py +++ b/prowler/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled.py @@ -7,40 +7,77 @@ class iam_root_hardware_mfa_enabled(Check): findings = [] # This check is only available in Commercial Partition if iam_client.audited_partition == "aws": - # Check if the root credentials are managed by AWS Organizations - if ( - iam_client.organization_features is not None - and "RootCredentialsManagement" not in iam_client.organization_features - ): - if iam_client.account_summary: - virtual_mfa = False - report = Check_Report_AWS( - metadata=self.metadata(), - resource=iam_client.account_summary, - ) - report.region = iam_client.region - report.resource_id = "" - report.resource_arn = iam_client.mfa_arn_template + if iam_client.credential_report: + for user in iam_client.credential_report: + if user["user"] == "": + password_enabled = user["password_enabled"] == "true" + access_key_1_active = user["access_key_1_active"] == "true" + access_key_2_active = user["access_key_2_active"] == "true" - 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." + # Only report if root actually has credentials + if ( + password_enabled + or access_key_1_active + or access_key_2_active + ) and iam_client.account_summary: + virtual_mfa = False + report = Check_Report_AWS( + metadata=self.metadata(), + resource=user, ) - else: - report.status = "FAIL" - report.status_extended = "MFA is not enabled for root account." + report.region = iam_client.region + report.resource_id = user["user"] + report.resource_arn = iam_client.mfa_arn_template - findings.append(report) + # Check if organization manages root credentials + org_managed = ( + iam_client.organization_features is not None + and "RootCredentialsManagement" + in iam_client.organization_features + ) + + 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" + if org_managed: + report.status_extended = ( + "Root account has credentials with virtual MFA " + "instead of hardware MFA despite organizational root management being enabled." + ) + else: + report.status_extended = "Root account has a virtual MFA instead of a hardware MFA device enabled." + break + + if not virtual_mfa: + report.status = "PASS" + if org_managed: + report.status_extended = ( + "Root account has credentials with hardware MFA enabled. " + "Consider removing individual root credentials since organizational " + "root management is active." + ) + else: + report.status_extended = "Root account has a hardware MFA device enabled." + else: + report.status = "FAIL" + if org_managed: + report.status_extended = ( + "Root account has credentials without MFA " + "despite organizational root management being enabled." + ) + else: + report.status_extended = ( + "MFA is not enabled for root account." + ) + + findings.append(report) return findings diff --git a/prowler/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled.metadata.json b/prowler/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled.metadata.json index 7a9a5ba263..320bf787a9 100644 --- a/prowler/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled.metadata.json +++ b/prowler/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled.metadata.json @@ -23,7 +23,7 @@ "Terraform": "" }, "Recommendation": { - "Text": "Using IAM console navigate to Dashboard and expand Activate MFA on your root account.", + "Text": "Using IAM console navigate to Dashboard and expand Activate MFA on your root account. If using AWS Organizations, consider enabling Centralized Root Management and removing individual root credentials.", "Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html#id_root-user_manage_mfa" } }, diff --git a/prowler/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled.py b/prowler/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled.py index 8a035f2ede..81ffd7b062 100644 --- a/prowler/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled.py +++ b/prowler/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled.py @@ -5,28 +5,53 @@ from prowler.providers.aws.services.iam.iam_client import iam_client class iam_root_mfa_enabled(Check): def execute(self) -> Check_Report_AWS: findings = [] - # Check if the root credentials are managed by AWS Organizations - if ( - iam_client.organization_features is not None - and "RootCredentialsManagement" not in iam_client.organization_features - ): - if iam_client.credential_report: - for user in iam_client.credential_report: - if user["user"] == "": + + if iam_client.credential_report: + for user in iam_client.credential_report: + if user["user"] == "": + password_enabled = user["password_enabled"] == "true" + access_key_1_active = user["access_key_1_active"] == "true" + access_key_2_active = user["access_key_2_active"] == "true" + + # Only report if root actually has credentials + if password_enabled or access_key_1_active or access_key_2_active: report = Check_Report_AWS( metadata=self.metadata(), resource=user ) report.region = iam_client.region report.resource_id = user["user"] report.resource_arn = user["arn"] + + # Check if organization manages root credentials + org_managed = ( + iam_client.organization_features is not None + and "RootCredentialsManagement" + in iam_client.organization_features + ) + if user["mfa_active"] == "false": report.status = "FAIL" - report.status_extended = ( - "MFA is not enabled for root account." - ) + if org_managed: + report.status_extended = ( + "Root account has credentials without MFA " + "despite organizational root management being enabled." + ) + else: + report.status_extended = ( + "MFA is not enabled for root account." + ) else: report.status = "PASS" - report.status_extended = "MFA is enabled for root account." + if org_managed: + report.status_extended = ( + "Root account has credentials with MFA enabled. " + "Consider removing individual root credentials since organizational " + "root management is active." + ) + else: + report.status_extended = ( + "MFA is enabled for root account." + ) findings.append(report) return findings diff --git a/prowler/providers/aws/services/iam/iam_service.py b/prowler/providers/aws/services/iam/iam_service.py index dafd86017c..7fc821a329 100644 --- a/prowler/providers/aws/services/iam/iam_service.py +++ b/prowler/providers/aws/services/iam/iam_service.py @@ -54,7 +54,7 @@ class IAM(AWSService): self.role_arn_template = f"arn:{self.audited_partition}:iam:{self.region}:{self.audited_account}:role" self.password_policy_arn_template = f"arn:{self.audited_partition}:iam:{self.region}:{self.audited_account}:password-policy" self.mfa_arn_template = ( - f"arn:{self.audited_partition}:iam:{self.region}:{self.audited_account}:mfa" + f"arn:{self.audited_partition}:iam::{self.audited_account}:mfa" ) self.users = self._get_users() self.roles = self._get_roles() diff --git a/tests/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key_test.py b/tests/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key_test.py index b2a9501e4b..ac096ca13b 100644 --- a/tests/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key_test.py +++ b/tests/providers/aws/services/iam/iam_no_root_access_key/iam_no_root_access_key_test.py @@ -33,11 +33,13 @@ class Test_iam_no_root_access_key_test: service_client.credential_report[0][ "arn" ] = "arn:aws:iam::123456789012:user/" + service_client.credential_report[0]["password_enabled"] = "true" service_client.credential_report[0]["access_key_1_active"] = "false" service_client.credential_report[0]["access_key_2_active"] = "false" check = iam_no_root_access_key() result = check.execute() + assert len(result) == 1 assert result[0].status == "PASS" assert ( result[0].status_extended @@ -75,11 +77,13 @@ class Test_iam_no_root_access_key_test: service_client.credential_report[0][ "arn" ] = "arn:aws:iam::123456789012:user/" + service_client.credential_report[0]["password_enabled"] = "true" service_client.credential_report[0]["access_key_1_active"] = "true" service_client.credential_report[0]["access_key_2_active"] = "false" check = iam_no_root_access_key() result = check.execute() + assert len(result) == 1 assert result[0].status == "FAIL" assert ( result[0].status_extended @@ -117,11 +121,13 @@ class Test_iam_no_root_access_key_test: service_client.credential_report[0][ "arn" ] = "arn:aws:iam::123456789012:user/" + service_client.credential_report[0]["password_enabled"] = "false" service_client.credential_report[0]["access_key_1_active"] = "false" service_client.credential_report[0]["access_key_2_active"] = "true" check = iam_no_root_access_key() result = check.execute() + assert len(result) == 1 assert result[0].status == "FAIL" assert ( result[0].status_extended @@ -159,11 +165,13 @@ class Test_iam_no_root_access_key_test: service_client.credential_report[0][ "arn" ] = "arn:aws:iam::123456789012:user/" + service_client.credential_report[0]["password_enabled"] = "false" service_client.credential_report[0]["access_key_1_active"] = "true" service_client.credential_report[0]["access_key_2_active"] = "true" check = iam_no_root_access_key() result = check.execute() + assert len(result) == 1 assert result[0].status == "FAIL" assert ( result[0].status_extended @@ -174,3 +182,179 @@ class Test_iam_no_root_access_key_test: result[0].resource_arn == "arn:aws:iam::123456789012:user/" ) + + @mock_aws + def test_root_no_credentials(self): + iam_client = client("iam") + user = "test" + iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key.iam_client", + new=IAM(aws_provider), + ) as service_client: + from prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key import ( + iam_no_root_access_key, + ) + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0][ + "arn" + ] = "arn:aws:iam::123456789012:user/" + service_client.credential_report[0]["password_enabled"] = "false" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" + check = iam_no_root_access_key() + result = check.execute() + + # Should return no findings since root has no credentials + assert len(result) == 0 + + @mock_aws + def test_root_no_access_keys_with_organizational_management_enabled(self): + iam_client = client("iam") + user = "test" + iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key.iam_client", + new=IAM(aws_provider), + ) as service_client: + from prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key import ( + iam_no_root_access_key, + ) + + # Set up organizational root management + service_client.organization_features = ["RootCredentialsManagement"] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0][ + "arn" + ] = "arn:aws:iam::123456789012:user/" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" + check = iam_no_root_access_key() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Root account has password configured but no access keys. Consider removing individual root credentials since organizational root management is active." + ) + assert result[0].resource_id == "" + assert ( + result[0].resource_arn + == "arn:aws:iam::123456789012:user/" + ) + + @mock_aws + def test_root_access_keys_with_organizational_management_enabled(self): + iam_client = client("iam") + user = "test" + iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key.iam_client", + new=IAM(aws_provider), + ) as service_client: + from prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key import ( + iam_no_root_access_key, + ) + + # Set up organizational root management + service_client.organization_features = ["RootCredentialsManagement"] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0][ + "arn" + ] = "arn:aws:iam::123456789012:user/" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "true" + service_client.credential_report[0]["access_key_2_active"] = "false" + check = iam_no_root_access_key() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Root account has one active access key despite organizational root management being enabled." + ) + assert result[0].resource_id == "" + assert ( + result[0].resource_arn + == "arn:aws:iam::123456789012:user/" + ) + + @mock_aws + def test_root_both_access_keys_with_organizational_management_enabled(self): + iam_client = client("iam") + user = "test" + iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key.iam_client", + new=IAM(aws_provider), + ) as service_client: + from prowler.providers.aws.services.iam.iam_no_root_access_key.iam_no_root_access_key import ( + iam_no_root_access_key, + ) + + # Set up organizational root management + service_client.organization_features = ["RootCredentialsManagement"] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0][ + "arn" + ] = "arn:aws:iam::123456789012:user/" + service_client.credential_report[0]["password_enabled"] = "false" + service_client.credential_report[0]["access_key_1_active"] = "true" + service_client.credential_report[0]["access_key_2_active"] = "true" + check = iam_no_root_access_key() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Root account has two active access keys despite organizational root management being enabled." + ) + assert result[0].resource_id == "" + assert ( + result[0].resource_arn + == "arn:aws:iam::123456789012:user/" + ) diff --git a/tests/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled_test.py b/tests/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled_test.py index 22ac46dd4a..0fa68c33e3 100644 --- a/tests/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled_test.py +++ b/tests/providers/aws/services/iam/iam_root_hardware_mfa_enabled/iam_root_hardware_mfa_enabled_test.py @@ -1,5 +1,9 @@ +from re import search from unittest import mock +from boto3 import client +from moto import mock_aws + from tests.providers.aws.utils import ( AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1, @@ -15,23 +19,14 @@ class Test_iam_root_hardware_mfa_enabled_test: set_mocked_aws_provider, ) + @mock_aws def test_root_virtual_mfa_enabled(self): - iam_client = mock.MagicMock - iam_client.account_summary = { - "SummaryMap": {"AccountMFAEnabled": 1}, - } - iam_client.virtual_mfa_devices = [ - { - "SerialNumber": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa/mfa", - "User": {"Arn": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"}, - } - ] - iam_client.audited_partition = "aws" - iam_client.region = AWS_REGION_US_EAST_1 - iam_client.mfa_arn_template = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa" - iam_client.organization_features = [] + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM with ( mock.patch( @@ -40,13 +35,32 @@ class Test_iam_root_hardware_mfa_enabled_test: ), mock.patch( "prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client", - new=iam_client, - ), + new=IAM(aws_provider), + ) as service_client, ): from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import ( iam_root_hardware_mfa_enabled, ) + # Set up virtual MFA device for root + service_client.virtual_mfa_devices = [ + { + "SerialNumber": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa/mfa", + "User": {"Arn": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"}, + } + ] + service_client.account_summary = { + "SummaryMap": {"AccountMFAEnabled": 1}, + } + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" + service_client.credential_report[0][ + "arn" + ] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + check = iam_root_hardware_mfa_enabled() result = check.execute() assert len(result) == 1 @@ -58,18 +72,14 @@ class Test_iam_root_hardware_mfa_enabled_test: assert result[0].resource_id == "" assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa" + @mock_aws def test_root_hardware_mfa_enabled(self): - iam_client = mock.MagicMock - iam_client.account_summary = { - "SummaryMap": {"AccountMFAEnabled": 1}, - } - iam_client.virtual_mfa_devices = [] - iam_client.audited_partition = "aws" - iam_client.region = AWS_REGION_US_EAST_1 - iam_client.mfa_arn_template = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa" - iam_client.organization_features = [] + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM with ( mock.patch( @@ -78,13 +88,27 @@ class Test_iam_root_hardware_mfa_enabled_test: ), mock.patch( "prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client", - new=iam_client, - ), + new=IAM(aws_provider), + ) as service_client, ): from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import ( iam_root_hardware_mfa_enabled, ) + # No virtual MFA devices (indicating hardware MFA) + service_client.virtual_mfa_devices = [] + service_client.account_summary = { + "SummaryMap": {"AccountMFAEnabled": 1}, + } + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" + service_client.credential_report[0][ + "arn" + ] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + check = iam_root_hardware_mfa_enabled() result = check.execute() assert len(result) == 1 @@ -96,16 +120,14 @@ class Test_iam_root_hardware_mfa_enabled_test: assert result[0].resource_id == "" assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa" + @mock_aws def test_root_hardware_mfa_enabled_none_summary(self): - iam_client = mock.MagicMock - iam_client.account_summary = None - iam_client.virtual_mfa_devices = [] - iam_client.audited_partition = "aws" - iam_client.region = AWS_REGION_US_EAST_1 - iam_client.mfa_arn_template = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa" - iam_client.organization_features = [] + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM with ( mock.patch( @@ -114,13 +136,222 @@ class Test_iam_root_hardware_mfa_enabled_test: ), mock.patch( "prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client", - new=iam_client, - ), + new=IAM(aws_provider), + ) as service_client, ): from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import ( iam_root_hardware_mfa_enabled, ) + # No account summary + service_client.account_summary = None + service_client.virtual_mfa_devices = [] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" + service_client.credential_report[0][ + "arn" + ] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + check = iam_root_hardware_mfa_enabled() result = check.execute() assert len(result) == 0 + + @mock_aws + def test_root_no_credentials(self): + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): + from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import ( + iam_root_hardware_mfa_enabled, + ) + + service_client.account_summary = { + "SummaryMap": {"AccountMFAEnabled": 1}, + } + service_client.virtual_mfa_devices = [] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["password_enabled"] = "false" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" + service_client.credential_report[0][ + "arn" + ] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + + check = iam_root_hardware_mfa_enabled() + result = check.execute() + # Should return no findings since root has no credentials + assert len(result) == 0 + + @mock_aws + def test_root_hardware_mfa_with_organizational_management_enabled(self): + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): + from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import ( + iam_root_hardware_mfa_enabled, + ) + + # Set up organizational root management + service_client.organization_features = ["RootCredentialsManagement"] + service_client.account_summary = { + "SummaryMap": {"AccountMFAEnabled": 1}, + } + service_client.virtual_mfa_devices = [] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "true" + service_client.credential_report[0]["access_key_2_active"] = "false" + service_client.credential_report[0][ + "arn" + ] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + + check = iam_root_hardware_mfa_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "Root account has credentials with hardware MFA enabled. " + "Consider removing individual root credentials since organizational " + "root management is active.", + result[0].status_extended, + ) + assert result[0].resource_id == "" + assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa" + + @mock_aws + def test_root_virtual_mfa_with_organizational_management_enabled(self): + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): + from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import ( + iam_root_hardware_mfa_enabled, + ) + + # Set up organizational root management + service_client.organization_features = ["RootCredentialsManagement"] + service_client.account_summary = { + "SummaryMap": {"AccountMFAEnabled": 1}, + } + service_client.virtual_mfa_devices = [ + { + "SerialNumber": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa/mfa", + "User": {"Arn": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root"}, + } + ] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "true" + service_client.credential_report[0][ + "arn" + ] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + + check = iam_root_hardware_mfa_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "Root account has credentials with virtual MFA instead of hardware MFA " + "despite organizational root management being enabled.", + result[0].status_extended, + ) + assert result[0].resource_id == "" + assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa" + + @mock_aws + def test_root_no_mfa_with_organizational_management_enabled(self): + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): + from prowler.providers.aws.services.iam.iam_root_hardware_mfa_enabled.iam_root_hardware_mfa_enabled import ( + iam_root_hardware_mfa_enabled, + ) + + # Set up organizational root management + service_client.organization_features = ["RootCredentialsManagement"] + service_client.account_summary = { + "SummaryMap": {"AccountMFAEnabled": 0}, + } + service_client.virtual_mfa_devices = [] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" + service_client.credential_report[0][ + "arn" + ] = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root" + + check = iam_root_hardware_mfa_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "Root account has credentials without MFA " + "despite organizational root management being enabled.", + result[0].status_extended, + ) + assert result[0].resource_id == "" + assert result[0].resource_arn == f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:mfa" diff --git a/tests/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled_test.py b/tests/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled_test.py index 67381e1208..618755d88a 100644 --- a/tests/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled_test.py +++ b/tests/providers/aws/services/iam/iam_root_mfa_enabled/iam_root_mfa_enabled_test.py @@ -42,12 +42,16 @@ class Test_iam_root_mfa_enabled_test: service_client.credential_report[0]["user"] = "" service_client.credential_report[0]["mfa_active"] = "false" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" service_client.credential_report[0][ "arn" ] = "arn:aws:iam::123456789012::root" check = iam_root_mfa_enabled() result = check.execute() + assert len(result) == 1 assert result[0].status == "FAIL" assert search( "MFA is not enabled for root account.", result[0].status_extended @@ -80,13 +84,149 @@ class Test_iam_root_mfa_enabled_test: service_client.credential_report[0]["user"] = "" service_client.credential_report[0]["mfa_active"] = "true" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" service_client.credential_report[0][ "arn" ] = "arn:aws:iam::123456789012::root" check = iam_root_mfa_enabled() result = check.execute() + assert len(result) == 1 assert result[0].status == "PASS" assert search("MFA is enabled for root account.", result[0].status_extended) assert result[0].resource_id == "" assert result[0].resource_arn == service_client.credential_report[0]["arn"] + + @mock_aws + def test_root_no_credentials(self): + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): + from prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled import ( + iam_root_mfa_enabled, + ) + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["mfa_active"] = "false" + service_client.credential_report[0]["password_enabled"] = "false" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "false" + service_client.credential_report[0][ + "arn" + ] = "arn:aws:iam::123456789012::root" + + check = iam_root_mfa_enabled() + result = check.execute() + # Should return no findings since root has no credentials + assert len(result) == 0 + + @mock_aws + def test_root_mfa_with_organizational_management_enabled(self): + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): + from prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled import ( + iam_root_mfa_enabled, + ) + + # Set up organizational root management + service_client.organization_features = ["RootCredentialsManagement"] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["mfa_active"] = "true" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "true" + service_client.credential_report[0]["access_key_2_active"] = "false" + service_client.credential_report[0][ + "arn" + ] = "arn:aws:iam::123456789012::root" + + check = iam_root_mfa_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "Root account has credentials with MFA enabled. " + "Consider removing individual root credentials since organizational " + "root management is active.", + result[0].status_extended, + ) + assert result[0].resource_id == "" + assert result[0].resource_arn == service_client.credential_report[0]["arn"] + + @mock_aws + def test_root_mfa_disabled_with_organizational_management_enabled(self): + iam_client = client("iam") + user = "test-user" + iam_client.create_user(UserName=user)["User"]["Arn"] + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled.iam_client", + new=IAM(aws_provider), + ) as service_client, + ): + from prowler.providers.aws.services.iam.iam_root_mfa_enabled.iam_root_mfa_enabled import ( + iam_root_mfa_enabled, + ) + + # Set up organizational root management + service_client.organization_features = ["RootCredentialsManagement"] + + service_client.credential_report[0]["user"] = "" + service_client.credential_report[0]["mfa_active"] = "false" + service_client.credential_report[0]["password_enabled"] = "true" + service_client.credential_report[0]["access_key_1_active"] = "false" + service_client.credential_report[0]["access_key_2_active"] = "true" + service_client.credential_report[0][ + "arn" + ] = "arn:aws:iam::123456789012::root" + + check = iam_root_mfa_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "Root account has credentials without MFA " + "despite organizational root management being enabled.", + result[0].status_extended, + ) + assert result[0].resource_id == "" + assert result[0].resource_arn == service_client.credential_report[0]["arn"]