From fa2ec63f453a48f71e76f91bb1f25ce13a4192d6 Mon Sep 17 00:00:00 2001 From: Gabriel Soltz Date: Fri, 14 Apr 2023 10:18:36 +0200 Subject: [PATCH] feat(check): New Check and Service: resourceexplorer2_indexes_found (#2196) Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com> --- .../services/resourceexplorer2/__init__.py | 0 .../resourceexplorer2_client.py | 6 + .../__init__.py | 0 ...ourceexplorer2_indexes_found.metadata.json | 30 +++++ .../resourceexplorer2_indexes_found.py | 22 ++++ .../resourceexplorer2_service.py | 58 ++++++++++ .../resourceexplorer2_indexes_found_test.py | 108 ++++++++++++++++++ .../resourceexplorer2_service_test.py | 88 ++++++++++++++ 8 files changed, 312 insertions(+) create mode 100644 prowler/providers/aws/services/resourceexplorer2/__init__.py create mode 100644 prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_client.py create mode 100644 prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/__init__.py create mode 100644 prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found.metadata.json create mode 100644 prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found.py create mode 100644 prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_service.py create mode 100644 tests/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found_test.py create mode 100644 tests/providers/aws/services/resourceexplorer2/resourceexplorer2_service_test.py diff --git a/prowler/providers/aws/services/resourceexplorer2/__init__.py b/prowler/providers/aws/services/resourceexplorer2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_client.py b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_client.py new file mode 100644 index 00000000..78a873b4 --- /dev/null +++ b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_client.py @@ -0,0 +1,6 @@ +from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info +from prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_service import ( + ResourceExplorer2, +) + +resource_explorer_2_client = ResourceExplorer2(current_audit_info) diff --git a/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/__init__.py b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found.metadata.json b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found.metadata.json new file mode 100644 index 00000000..7733b396 --- /dev/null +++ b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "resourceexplorer2_indexes_found", + "CheckTitle": "Resource Explorer Indexes Found", + "CheckType": [], + "ServiceName": "resourceexplorer2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:resource-explorer-2:region:account-id:index/index-id", + "Severity": "low", + "ResourceType": "Other", + "Description": "Resource Explorer Indexes Found", + "Risk": "Not having Resource Explorer indexes can result in increased complexity and overhead in managing your resources, as well as increased risk of security and compliance issues.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Create indexes", + "Url": "https://docs.aws.amazon.com/resource-explorer/latest/userguide/manage-service-turn-on-region.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found.py b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found.py new file mode 100644 index 00000000..e9bbe7fe --- /dev/null +++ b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found.py @@ -0,0 +1,22 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_client import ( + resource_explorer_2_client, +) + + +class resourceexplorer2_indexes_found(Check): + def execute(self): + findings = [] + report = Check_Report_AWS(self.metadata()) + report.status = "FAIL" + report.status_extended = "No Resource Explorer Indexes found" + report.region = resource_explorer_2_client.region + report.resource_arn = "NoResourceExplorer" + if resource_explorer_2_client.indexes: + report.region = resource_explorer_2_client.indexes[0].region + report.resource_arn = resource_explorer_2_client.indexes[0].arn + report.status = "PASS" + report.status_extended = f"Resource Explorer Indexes found: {len(resource_explorer_2_client.indexes)}" + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_service.py b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_service.py new file mode 100644 index 00000000..9e14379f --- /dev/null +++ b/prowler/providers/aws/services/resourceexplorer2/resourceexplorer2_service.py @@ -0,0 +1,58 @@ +import threading + +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.aws.aws_provider import generate_regional_clients + + +################################ ResourceExplorer2 +class ResourceExplorer2: + def __init__(self, audit_info): + self.service = "resource-explorer-2" + self.session = audit_info.audit_session + self.audit_resources = audit_info.audit_resources + self.regional_clients = generate_regional_clients(self.service, audit_info) + self.region = audit_info.profile_region + self.indexes = [] + self.__threading_call__(self.__list_indexes__) + + def __get_session__(self): + return self.session + + def __threading_call__(self, call): + threads = [] + for regional_client in self.regional_clients.values(): + threads.append(threading.Thread(target=call, args=(regional_client,))) + for t in threads: + t.start() + for t in threads: + t.join() + + def __list_indexes__(self, regional_client): + logger.info("ResourceExplorer - list indexes...") + try: + list_indexes_paginator = regional_client.get_paginator("list_indexes") + for page in list_indexes_paginator.paginate(): + for index in page.get("Indexes"): + if not self.audit_resources or ( + is_resource_filtered(index["Arn"], self.audit_resources) + ): + self.indexes.append( + Indexes( + arn=index["Arn"], + region=index["Region"], + type=index["Type"], + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class Indexes(BaseModel): + arn: str + region: str + type: str diff --git a/tests/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found_test.py b/tests/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found_test.py new file mode 100644 index 00000000..39a885c0 --- /dev/null +++ b/tests/providers/aws/services/resourceexplorer2/resourceexplorer2_indexes_found/resourceexplorer2_indexes_found_test.py @@ -0,0 +1,108 @@ +from unittest import mock +from unittest.mock import patch + +import botocore +from boto3 import session + +from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info +from prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_service import ( + Indexes, +) + +AWS_ACCOUNT_NUMBER = "123456789012" +AWS_REGION = "us-east-1" +INDEX_ARN = "arn:aws:resource-explorer-2:ap-south-1:123456789012:index/123456-2896-4fe8-93d2-15ec137e5c47" +INDEX_REGION = "us-east-1" + + +# Mocking Backup Calls +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + """ + Mock every AWS API call + """ + if operation_name == "ListIndexes": + return { + "Indexes": [ + {"Arn": INDEX_ARN, "Region": INDEX_REGION, "Type": "LOCAL"}, + ], + "NextToken": "string", + } + return make_api_call(self, operation_name, kwarg) + + +@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +class Test_resourceexplorer2_indexes_found: + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + session_config=None, + original_session=None, + audit_session=session.Session( + profile_name=None, + botocore_session=None, + ), + audited_account=AWS_ACCOUNT_NUMBER, + audited_user_id=None, + audited_partition="aws", + audited_identity_arn=None, + profile=None, + profile_region="us-east-1", + credentials=None, + assumed_role_info=None, + audited_regions="us-east-1", + organizations_metadata=None, + audit_resources=None, + ) + return audit_info + + def test_no_indexes_found(self): + resourceexplorer2_client = mock.MagicMock + resourceexplorer2_client.indexes = [] + resourceexplorer2_client.region = AWS_REGION + with mock.patch( + "prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_service.ResourceExplorer2", + new=resourceexplorer2_client, + ): + # Test Check + from prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_indexes_found.resourceexplorer2_indexes_found import ( + resourceexplorer2_indexes_found, + ) + + check = resourceexplorer2_indexes_found() + result = check.execute() + + # Assertions + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == "No Resource Explorer Indexes found" + assert result[0].resource_id == "" + assert result[0].resource_arn == "NoResourceExplorer" + assert result[0].region == AWS_REGION + + def test_one_index_found(self): + resourceexplorer2_client = mock.MagicMock + resourceexplorer2_client.indexes = [ + Indexes(arn=INDEX_ARN, region=INDEX_REGION, type="LOCAL") + ] + resourceexplorer2_client.region = AWS_REGION + with mock.patch( + "prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_service.ResourceExplorer2", + new=resourceexplorer2_client, + ): + # Test Check + from prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_indexes_found.resourceexplorer2_indexes_found import ( + resourceexplorer2_indexes_found, + ) + + check = resourceexplorer2_indexes_found() + result = check.execute() + + # Assertions + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == "Resource Explorer Indexes found: 1" + assert result[0].resource_id == "" + assert result[0].resource_arn == INDEX_ARN + assert result[0].region == AWS_REGION diff --git a/tests/providers/aws/services/resourceexplorer2/resourceexplorer2_service_test.py b/tests/providers/aws/services/resourceexplorer2/resourceexplorer2_service_test.py new file mode 100644 index 00000000..2f80feef --- /dev/null +++ b/tests/providers/aws/services/resourceexplorer2/resourceexplorer2_service_test.py @@ -0,0 +1,88 @@ +from unittest.mock import patch + +import botocore +from boto3 import session + +from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info +from prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_service import ( + ResourceExplorer2, +) + +AWS_ACCOUNT_NUMBER = "123456789012" +AWS_REGION = "eu-west-1" +INDEX_ARN = "arn:aws:resource-explorer-2:ap-south-1:123456789012:index/123456-2896-4fe8-93d2-15ec137e5c47" +INDEX_REGION = "us-east-1" + +# Mocking Backup Calls +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + """ + Mock every AWS API call + """ + if operation_name == "ListIndexes": + return { + "Indexes": [ + {"Arn": INDEX_ARN, "Region": INDEX_REGION, "Type": "LOCAL"}, + ] + } + return make_api_call(self, operation_name, kwarg) + + +def mock_generate_regional_clients(service, audit_info): + regional_client = audit_info.audit_session.client(service, region_name=AWS_REGION) + regional_client.region = AWS_REGION + return {AWS_REGION: regional_client} + + +@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +@patch( + "prowler.providers.aws.services.resourceexplorer2.resourceexplorer2_service.generate_regional_clients", + new=mock_generate_regional_clients, +) +class Test_ResourceExplorer2_Service: + + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + session_config=None, + original_session=None, + audit_session=session.Session( + profile_name=None, + botocore_session=None, + ), + audited_account=AWS_ACCOUNT_NUMBER, + audited_user_id=None, + audited_partition="aws", + audited_identity_arn=None, + profile=None, + profile_region=None, + credentials=None, + assumed_role_info=None, + audited_regions="us-east-1", + organizations_metadata=None, + audit_resources=None, + ) + return audit_info + + def test__get_client__(self): + audit_info = self.set_mocked_audit_info() + resourceeplorer2 = ResourceExplorer2(audit_info) + assert ( + resourceeplorer2.regional_clients[AWS_REGION].__class__.__name__ + == "ResourceExplorer" + ) + + def test__get_service__(self): + audit_info = self.set_mocked_audit_info() + resourceeplorer2 = ResourceExplorer2(audit_info) + assert resourceeplorer2.service == "resource-explorer-2" + + def test__list_indexes__(self): + audit_info = self.set_mocked_audit_info() + resourceeplorer2 = ResourceExplorer2(audit_info) + assert len(resourceeplorer2.indexes) == 1 + assert resourceeplorer2.indexes[0].arn == INDEX_ARN + assert resourceeplorer2.indexes[0].region == INDEX_REGION + assert resourceeplorer2.indexes[0].type == "LOCAL"