From fdddac248aab95685a6a1f732e2094622cbd1eca Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 23 Sep 2024 15:44:39 -0400 Subject: [PATCH 1/3] chore(organizations): improve AWS Organizations service --- .../organizations_scp_check_deny_regions.py | 144 ++++++++---------- .../organizations/organizations_service.py | 28 ++-- ...ions_tags_policies_enabled_and_attached.py | 23 ++- .../organizations_service_test.py | 23 ++- ...tags_policies_enabled_and_attached_test.py | 47 +++--- 5 files changed, 129 insertions(+), 136 deletions(-) diff --git a/prowler/providers/aws/services/organizations/organizations_scp_check_deny_regions/organizations_scp_check_deny_regions.py b/prowler/providers/aws/services/organizations/organizations_scp_check_deny_regions/organizations_scp_check_deny_regions.py index 7c74be208c2..049ca9f6655 100644 --- a/prowler/providers/aws/services/organizations/organizations_scp_check_deny_regions/organizations_scp_check_deny_regions.py +++ b/prowler/providers/aws/services/organizations/organizations_scp_check_deny_regions/organizations_scp_check_deny_regions.py @@ -16,91 +16,81 @@ def execute(self): report.resource_id = org.id report.resource_arn = org.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": - if org.policies is None: - # Access Denied to list_policies - continue - if not org.policies: - report.status = "FAIL" - report.status_extended = ( - f"AWS Organization {org.id} has no SCP policies." - ) - else: - # We use this flag if we find a statement that is restricting regions but not all the configured ones: - is_region_restricted_statement = False + report.status_extended = ( + f"AWS Organizations {org.id} does not have SCP policies." + ) + # We use this flag if we find a statement that is restricting regions but not all the configured ones: + is_region_restricted_statement = False - for policy in org.policies: - # We only check SCP policies here - if policy.type != "SERVICE_CONTROL_POLICY": - continue + for policy in org.policies.get("SERVICE_CONTROL_POLICY", []): - # Statements are not always list - statements = policy.content.get("Statement") - if type(policy.content["Statement"]) is not list: - statements = [policy.content.get("Statement")] + # Statements are not always list + statements = policy.content.get("Statement") + if type(policy.content["Statement"]) is not list: + statements = [policy.content.get("Statement")] - for statement in statements: - # Deny if Condition = {"StringNotEquals": {"aws:RequestedRegion": [region1, region2]}} - if ( - statement.get("Effect") == "Deny" - and "Condition" in statement - and "StringNotEquals" in statement["Condition"] - and "aws:RequestedRegion" - in statement["Condition"]["StringNotEquals"] + for statement in statements: + # Deny if Condition = {"StringNotEquals": {"aws:RequestedRegion": [region1, region2]}} + if ( + statement.get("Effect") == "Deny" + and "Condition" in statement + and "StringNotEquals" in statement["Condition"] + and "aws:RequestedRegion" + in statement["Condition"]["StringNotEquals"] + ): + if all( + region + in statement["Condition"]["StringNotEquals"][ + "aws:RequestedRegion" + ] + for region in organizations_enabled_regions ): - if all( - region - in statement["Condition"]["StringNotEquals"][ - "aws:RequestedRegion" - ] - for region in organizations_enabled_regions - ): - # All defined regions are restricted, we exit here, no need to continue. - report.status = "PASS" - report.status_extended = f"AWS Organization {org.id} has SCP policy {policy.id} restricting all configured regions found." - findings.append(report) - return findings - else: - # Regions are restricted, but not the ones defined, we keep this finding, but we continue analyzing: - is_region_restricted_statement = True - report.status = "FAIL" - report.status_extended = f"AWS Organization {org.id} has SCP policies {policy.id} restricting some AWS Regions, but not all the configured ones, please check config." + # All defined regions are restricted, we exit here, no need to continue. + report.status = "PASS" + report.status_extended = f"AWS Organization {org.id} has SCP policy {policy.id} restricting all configured regions found." + findings.append(report) + return findings + else: + # Regions are restricted, but not the ones defined, we keep this finding, but we continue analyzing: + is_region_restricted_statement = True + report.status = "FAIL" + report.status_extended = f"AWS Organization {org.id} has SCP policies {policy.id} restricting some AWS Regions, but not all the configured ones, please check config." - # Allow if Condition = {"StringEquals": {"aws:RequestedRegion": [region1, region2]}} - if ( - policy.content.get("Statement") == "Allow" - and "Condition" in statement - and "StringEquals" in statement["Condition"] - and "aws:RequestedRegion" - in statement["Condition"]["StringEquals"] + # Allow if Condition = {"StringEquals": {"aws:RequestedRegion": [region1, region2]}} + if ( + policy.content.get("Statement") == "Allow" + and "Condition" in statement + and "StringEquals" in statement["Condition"] + and "aws:RequestedRegion" + in statement["Condition"]["StringEquals"] + ): + if all( + region + in statement["Condition"]["StringEquals"][ + "aws:RequestedRegion" + ] + for region in organizations_enabled_regions ): - if all( - region - in statement["Condition"]["StringEquals"][ - "aws:RequestedRegion" - ] - for region in organizations_enabled_regions - ): - # All defined regions are restricted, we exit here, no need to continue. - report.status = "PASS" - report.status_extended = f"AWS Organization {org.id} has SCP policy {policy.id} restricting all configured regions found." - findings.append(report) - return findings - else: - # Regions are restricted, but not the ones defined, we keep this finding, but we continue analyzing: - is_region_restricted_statement = True - report.status = "FAIL" - report.status_extended = f"AWS Organization {org.id} has SCP policies {policy.id} restricting some AWS Regions, but not all the configured ones, please check config." + # All defined regions are restricted, we exit here, no need to continue. + report.status = "PASS" + report.status_extended = f"AWS Organization {org.id} has SCP policy {policy.id} restricting all configured regions found." + findings.append(report) + return findings + else: + # Regions are restricted, but not the ones defined, we keep this finding, but we continue analyzing: + is_region_restricted_statement = True + report.status = "FAIL" + report.status_extended = f"AWS Organization {org.id} has SCP policies {policy.id} restricting some AWS Regions, but not all the configured ones, please check config." - if not is_region_restricted_statement: - report.status = "FAIL" - report.status_extended = f"AWS Organization {org.id} has SCP policies but don't restrict AWS Regions." - - else: - report.status = "FAIL" - report.status_extended = ( - "AWS Organizations is not in-use for this AWS Account." - ) + if not is_region_restricted_statement: + report.status = "FAIL" + report.status_extended = f"AWS Organization {org.id} has SCP policies but don't restrict AWS Regions." findings.append(report) diff --git a/prowler/providers/aws/services/organizations/organizations_service.py b/prowler/providers/aws/services/organizations/organizations_service.py index 9f749280c48..68ae41bec48 100644 --- a/prowler/providers/aws/services/organizations/organizations_service.py +++ b/prowler/providers/aws/services/organizations/organizations_service.py @@ -8,7 +8,7 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered from prowler.providers.aws.lib.service.service import AWSService -available_organizations_policies = [ +AVAILABLE_ORGANIZATIONS_POLICIES = [ "SERVICE_CONTROL_POLICY", "TAG_POLICY", "BACKUP_POLICY", @@ -16,13 +16,12 @@ ] -################## Organizations class Organizations(AWSService): def __init__(self, provider): # Call AWSService's __init__ super().__init__(__class__.__name__, provider) self.organizations = [] - self.policies = [] + self.policies = {} self.delegated_administrators = [] self._describe_organization() @@ -30,15 +29,12 @@ def _describe_organization(self): logger.info("Organizations - Describe Organization...") try: - # Check if Organizations is in-use try: organization_desc = self.client.describe_organization()["Organization"] organization_arn = organization_desc.get("Arn") organization_id = organization_desc.get("Id") organization_master_id = organization_desc.get("MasterAccountId") - # Fetch policies for organization: organization_policies = self._list_policies() - # Fetch delegated administrators for organization: organization_delegated_administrator = ( self._list_delegated_administrators() ) @@ -89,23 +85,24 @@ def _describe_organization(self): f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - # I'm using list_policies instead of list_policies_for_target, because the last one only returns "Attached directly" policies but not "Inherited from..." policies. def _list_policies(self): logger.info("Organizations - List policies...") try: list_policies_paginator = self.client.get_paginator("list_policies") - for policy_type in available_organizations_policies: + policies = {} + for policy_type in AVAILABLE_ORGANIZATIONS_POLICIES: logger.info( "Organizations - List policies... - Type: %s", policy_type, ) + policies[policy_type] = [] for page in list_policies_paginator.paginate(Filter=policy_type): for policy in page["Policies"]: policy_id = policy.get("Id") policy_content = self._describe_policy(policy_id) policy_targets = self._list_targets_for_policy(policy_id) - self.policies.append( + policies[policy_type].append( Policy( arn=policy.get("Arn"), id=policy_id, @@ -118,7 +115,7 @@ def _list_policies(self): except ClientError as error: if error.response["Error"]["Code"] == "AccessDeniedException": - self.policies = None + policies = None except Exception as error: logger.error( @@ -126,12 +123,10 @@ def _list_policies(self): ) finally: - return self.policies + return policies def _describe_policy(self, policy_id) -> dict: logger.info("Organizations - Describe policy: %s ...", policy_id) - - # This operation can be called only from the organization’s management account or by a member account that is a delegated administrator for an Amazon Web Services service. try: policy_content = {} if policy_id: @@ -143,8 +138,7 @@ def _describe_policy(self, policy_id) -> dict: if isinstance(policy_content, str): policy_content = json.loads(policy_content) - return policy_content # This could be not be a dict, because json.loads could return a list or a string depending on the content of policy_content object. - + return policy_content except Exception as error: logger.error( f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" @@ -170,7 +164,7 @@ def _list_targets_for_policy(self, policy_id) -> list: return [] def _list_delegated_administrators(self): - logger.info("Organizations - List Delegated Administrators") + logger.info("Organizations - List Delegated Administrators...") try: list_delegated_administrators_paginator = self.client.get_paginator( @@ -225,5 +219,5 @@ class Organization(BaseModel): id: str status: str master_id: str - policies: list[Policy] = None + policies: dict[str, list[Policy]] = {} delegated_administrators: list[DelegatedAdministrator] = None diff --git a/prowler/providers/aws/services/organizations/organizations_tags_policies_enabled_and_attached/organizations_tags_policies_enabled_and_attached.py b/prowler/providers/aws/services/organizations/organizations_tags_policies_enabled_and_attached/organizations_tags_policies_enabled_and_attached.py index 7dcc4d3e954..f3cb419ed32 100644 --- a/prowler/providers/aws/services/organizations/organizations_tags_policies_enabled_and_attached/organizations_tags_policies_enabled_and_attached.py +++ b/prowler/providers/aws/services/organizations/organizations_tags_policies_enabled_and_attached/organizations_tags_policies_enabled_and_attached.py @@ -17,20 +17,17 @@ def execute(self): report.status_extended = ( "AWS Organizations is not in-use for this AWS Account." ) - if org.status == "ACTIVE": - if org.policies is None: - # Access Denied to list_policies - continue - for policy in org.policies: - # We only check SCP policies here - if policy.type != "TAG_POLICY": - continue - - report.status_extended = f"AWS Organization {org.id} has tag policies enabled but not attached." - if policy.targets: - report.status = "PASS" - report.status_extended = f"AWS Organization {org.id} has tag policies enabled and attached to an AWS account." + if org.status == "ACTIVE": + report.status_extended = ( + f"AWS Organizations {org.id} does not have tag policies." + ) + if org.policies is not None: # Access Denied to list_policies + for policy in org.policies.get("TAG_POLICY", []): + report.status_extended = f"AWS Organization {org.id} has tag policies enabled but not attached." + if policy.targets: + report.status = "PASS" + report.status_extended = f"AWS Organization {org.id} has tag policies enabled and attached to an AWS account." findings.append(report) diff --git a/tests/providers/aws/services/organizations/organizations_service_test.py b/tests/providers/aws/services/organizations/organizations_service_test.py index 724f00146c7..dfae12d402d 100644 --- a/tests/providers/aws/services/organizations/organizations_service_test.py +++ b/tests/providers/aws/services/organizations/organizations_service_test.py @@ -22,15 +22,12 @@ def test_service(self): @mock_aws def test_describe_organization(self): - # Create Organization conn = client("organizations", region_name=AWS_REGION_EU_WEST_1) response = conn.create_organization() - # Mock aws_provider = set_mocked_aws_provider( [AWS_REGION_EU_WEST_1], create_default_organization=False ) organizations = Organizations(aws_provider) - # Tests assert len(organizations.organizations) == 1 assert organizations.organizations[0].arn == response["Organization"]["Arn"] assert organizations.organizations[0].id == response["Organization"]["Id"] @@ -43,7 +40,6 @@ def test_describe_organization(self): @mock_aws def test_list_policies(self): - # Create Policy conn = client("organizations", region_name=AWS_REGION_EU_WEST_1) conn.create_organization() response = conn.create_policy( @@ -52,13 +48,28 @@ def test_list_policies(self): Name="Test", Type="SERVICE_CONTROL_POLICY", ) - # Mock aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) organizations = Organizations(aws_provider) - # Tests for policy in organizations.policies: if policy.arn == response["Policy"]["PolicySummary"]["Arn"]: assert policy.type == "SERVICE_CONTROL_POLICY" assert policy.aws_managed is False assert policy.content == json.loads(response["Policy"]["Content"]) assert policy.targets == [] + + @mock_aws + def test_describe_policy(self): + conn = client("organizations", region_name=AWS_REGION_EU_WEST_1) + conn.create_organization() + response = conn.create_policy( + Content=scp_restrict_regions_with_deny(), + Description="Test", + Name="Test", + Type="SERVICE_CONTROL_POLICY", + ) + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + organizations = Organizations(aws_provider) + policy = organizations._describe_policy( + response["Policy"]["PolicySummary"]["Id"] + ) + assert policy == json.loads(response["Policy"]["Content"]) diff --git a/tests/providers/aws/services/organizations/organizations_tags_policies_enabled_and_attached/organizations_tags_policies_enabled_and_attached_test.py b/tests/providers/aws/services/organizations/organizations_tags_policies_enabled_and_attached/organizations_tags_policies_enabled_and_attached_test.py index 966e5fd14d8..9a90c9f61c8 100644 --- a/tests/providers/aws/services/organizations/organizations_tags_policies_enabled_and_attached/organizations_tags_policies_enabled_and_attached_test.py +++ b/tests/providers/aws/services/organizations/organizations_tags_policies_enabled_and_attached/organizations_tags_policies_enabled_and_attached_test.py @@ -10,9 +10,6 @@ set_mocked_aws_provider, ) -# Moto: NotImplementedError: The TAG_POLICY policy type has not been implemented -# Needs to Mock manually - class Test_organizations_tags_policies_enabled_and_attached: def test_organization_no_organization(self): @@ -64,16 +61,18 @@ def test_organization_with_tag_policies_not_attached(self): arn="arn:aws:organizations::1234567890:organization/o-1234567890", status="ACTIVE", master_id="1234567890", - policies=[ - Policy( - id="p-1234567890", - arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890", - type="TAG_POLICY", - aws_managed=False, - content={"tags": {"Owner": {}}}, - targets=[], - ) - ], + policies={ + "TAG_POLICY": [ + Policy( + id="p-1234567890", + arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890", + type="TAG_POLICY", + aws_managed=False, + content={"tags": {"Owner": {}}}, + targets=[], + ) + ] + }, delegated_administrators=None, ) ] @@ -118,16 +117,18 @@ def test_organization_with_tag_policies_attached(self): arn="arn:aws:organizations::1234567890:organization/o-1234567890", status="ACTIVE", master_id="1234567890", - policies=[ - Policy( - id="p-1234567890", - arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890", - type="TAG_POLICY", - aws_managed=False, - content={"tags": {"Owner": {}}}, - targets=["1234567890"], - ) - ], + policies={ + "TAG_POLICY": [ + Policy( + id="p-1234567890", + arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890", + type="TAG_POLICY", + aws_managed=False, + content={"tags": {"Owner": {}}}, + targets=["1234567890"], + ) + ] + }, delegated_administrators=None, ) ] From 61f4a4e7bd60a901add518af689ab48f1cdb82e4 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 23 Sep 2024 15:50:22 -0400 Subject: [PATCH 2/3] feat(aws): add new check --- .../__init__.py | 0 ...s_opt_out_ai_services_policy.metadata.json | 30 ++++ ...rganizations_opt_out_ai_services_policy.py | 37 ++++ ...zations_opt_out_ai_services_policy_test.py | 160 ++++++++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/__init__.py create mode 100644 prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.metadata.json create mode 100644 prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.py create mode 100644 tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py diff --git a/prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/__init__.py b/prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.metadata.json b/prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.metadata.json new file mode 100644 index 00000000000..ef3e8c536e1 --- /dev/null +++ b/prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "organizations_opt_out_ai_services_policy", + "CheckTitle": "Ensure that AWS Organizations opt-out of AI services policy is enabled.", + "CheckType": [], + "ServiceName": "organizations", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service::account-id:organization/organization-id", + "Severity": "low", + "ResourceType": "Other", + "Description": "This control checks whether the AWS Organizations opt-out of AI services policy is enabled. The control fails if the policy is not enabled.", + "Risk": "By default, AWS may be using your data to train its AI models. This may include data from your AWS CloudTrail logs, AWS Config rules, and AWS GuardDuty findings. If you opt out of AI services, AWS will not use your data to train its AI models.", + "RelatedUrl": "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_ai-opt-out_all.html", + "Remediation": { + "Code": { + "CLI": "aws organizations enable-policy-type --root-id --policy-type AI_SERVICES_OPT_OUT {'services': {'default': {'opt_out_policy': {'@@assign': 'optOut'}}}}", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Artificial Intelligence (AI) services opt-out policies enable you to control whether AWS AI services can store and use your content. Enable the AWS Organizations opt-out of AI services policy.", + "Url": "https://docs.aws.amazon.com/organizations/latest/userguide/disable-policy-type.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.py b/prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.py new file mode 100644 index 00000000000..f42263891ad --- /dev/null +++ b/prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.py @@ -0,0 +1,37 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.organizations.organizations_client import ( + organizations_client, +) + + +class organizations_opt_out_ai_services_policy(Check): + def execute(self): + findings = [] + + for org in organizations_client.organizations: + report = Check_Report_AWS(self.metadata()) + report.resource_id = org.id + report.resource_arn = org.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." + if org.policies is not None: # Access Denied to list_policies + for policy in org.policies.get("AISERVICES_OPT_OUT_POLICY", []): + if ( + policy.content.get("services", {}) + .get("default", {}) + .get("opt_out_policy", {}) + .get("@@assign") + == "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." + break + + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py b/tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py new file mode 100644 index 00000000000..9c34bb88bd8 --- /dev/null +++ b/tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py @@ -0,0 +1,160 @@ +from unittest import mock + +from prowler.providers.aws.services.organizations.organizations_service import ( + Organization, + Policy, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_ARN, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) + + +class Test_organizations_tags_policies_enabled_and_attached: + def test_organization_no_organization(self): + organizations_client = mock.MagicMock + organizations_client.region = AWS_REGION_EU_WEST_1 + organizations_client.organizations = [ + Organization( + arn=AWS_ACCOUNT_ARN, + id="AWS Organization", + status="NOT_AVAILABLE", + master_id="", + ) + ] + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client", + new=organizations_client, + ): + # Test Check + from prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy import ( + organizations_opt_out_ai_services_policy, + ) + + check = organizations_opt_out_ai_services_policy() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "AWS Organizations is not in-use for this AWS Account." + ) + assert result[0].resource_id == "AWS Organization" + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_EU_WEST_1 + + def test_organization_with_AI_optout_no_policies(self): + organizations_client = mock.MagicMock + organizations_client.region = AWS_REGION_EU_WEST_1 + organizations_client.organizations = [ + Organization( + id="o-1234567890", + arn="arn:aws:organizations::1234567890:organization/o-1234567890", + status="ACTIVE", + master_id="1234567890", + policies={"AISERVICES_OPT_OUT_POLICY": []}, + delegated_administrators=None, + ) + ] + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client", + new=organizations_client, + ): + # Test Check + from prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy import ( + organizations_opt_out_ai_services_policy, + ) + + check = organizations_opt_out_ai_services_policy() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "AWS Organization o-1234567890 has not opted out of all AI services, granting consent for AWS to access its data." + ) + assert result[0].resource_id == "o-1234567890" + assert ( + result[0].resource_arn + == "arn:aws:organizations::1234567890:organization/o-1234567890" + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + + def test_organization_with_AI_optout_policy(self): + organizations_client = mock.MagicMock + organizations_client.region = AWS_REGION_EU_WEST_1 + organizations_client.organizations = [ + Organization( + id="o-1234567890", + arn="arn:aws:organizations::1234567890:organization/o-1234567890", + status="ACTIVE", + master_id="1234567890", + policies={ + "AISERVICES_OPT_OUT_POLICY": [ + Policy( + id="p-1234567890", + arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890", + type="AISERVICES_OPT_OUT_POLICY", + aws_managed=False, + content={ + "services": { + "default": { + "opt_out_policy": {"@@assign": "optOut"} + } + } + }, + targets=[], + ) + ] + }, + delegated_administrators=None, + ) + ] + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client", + new=organizations_client, + ): + # Test Check + from prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy import ( + organizations_opt_out_ai_services_policy, + ) + + check = organizations_opt_out_ai_services_policy() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "AWS Organization o-1234567890 has opted out of all AI services, not granting consent for AWS to access its data." + ) + assert result[0].resource_id == "o-1234567890" + assert ( + result[0].resource_arn + == "arn:aws:organizations::1234567890:organization/o-1234567890" + ) + assert result[0].region == AWS_REGION_EU_WEST_1 From fba72ace2c3281d41bd4b6a1a976b2274893aa54 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 24 Sep 2024 08:33:33 -0400 Subject: [PATCH 3/3] chore: add test --- ...zations_opt_out_ai_services_policy_test.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py b/tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py index 9c34bb88bd8..5d94c363003 100644 --- a/tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py +++ b/tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py @@ -158,3 +158,59 @@ def test_organization_with_AI_optout_policy(self): == "arn:aws:organizations::1234567890:organization/o-1234567890" ) assert result[0].region == AWS_REGION_EU_WEST_1 + + def test_organization_with_AI_optout_policy_no_content(self): + organizations_client = mock.MagicMock + organizations_client.region = AWS_REGION_EU_WEST_1 + organizations_client.organizations = [ + Organization( + id="o-1234567890", + arn="arn:aws:organizations::1234567890:organization/o-1234567890", + status="ACTIVE", + master_id="1234567890", + policies={ + "AISERVICES_OPT_OUT_POLICY": [ + Policy( + id="p-1234567890", + arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890", + type="AISERVICES_OPT_OUT_POLICY", + aws_managed=False, + content={}, + targets=[], + ) + ] + }, + delegated_administrators=None, + ) + ] + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client", + new=organizations_client, + ): + # Test Check + from prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy import ( + organizations_opt_out_ai_services_policy, + ) + + check = organizations_opt_out_ai_services_policy() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "AWS Organization o-1234567890 has not opted out of all AI services, granting consent for AWS to access its data." + ) + assert result[0].resource_id == "o-1234567890" + assert ( + result[0].resource_arn + == "arn:aws:organizations::1234567890:organization/o-1234567890" + ) + assert result[0].region == AWS_REGION_EU_WEST_1