diff --git a/prowler/providers/aws/services/securityhub/securityhub_enabled/securityhub_enabled.py b/prowler/providers/aws/services/securityhub/securityhub_enabled/securityhub_enabled.py index d44fc2dd..07d43a28 100644 --- a/prowler/providers/aws/services/securityhub/securityhub_enabled/securityhub_enabled.py +++ b/prowler/providers/aws/services/securityhub/securityhub_enabled/securityhub_enabled.py @@ -12,9 +12,13 @@ class securityhub_enabled(Check): report.region = securityhub.region if securityhub.status == "ACTIVE": report.status = "PASS" - report.status_extended = ( - f"Security Hub is enabled with standards {securityhub.standards}" - ) + if securityhub.standards: + report.status_extended = f"Security Hub is enabled with standards: {securityhub.standards}" + elif securityhub.integrations: + report.status_extended = f"Security Hub is enabled without standards but with integrations: {securityhub.integrations}" + else: + report.status = "FAIL" + report.status_extended = "Security Hub is enabled but without any standard or integration" else: report.status = "FAIL" report.status_extended = "Security Hub is not enabled" diff --git a/prowler/providers/aws/services/securityhub/securityhub_service.py b/prowler/providers/aws/services/securityhub/securityhub_service.py index fd9910df..7d43f7c2 100644 --- a/prowler/providers/aws/services/securityhub/securityhub_service.py +++ b/prowler/providers/aws/services/securityhub/securityhub_service.py @@ -1,5 +1,7 @@ import threading -from dataclasses import dataclass + +from botocore.client import ClientError +from pydantic import BaseModel from prowler.lib.logger import logger from prowler.lib.scan_filters.scan_filters import is_resource_filtered @@ -32,87 +34,79 @@ class SecurityHub: def __describe_hub__(self, regional_client): logger.info("SecurityHub - Describing Hub...") try: - get_enabled_standards_paginator = regional_client.get_paginator( - "get_enabled_standards" - ) - standards = "" - for page in get_enabled_standards_paginator.paginate(): - for standard in page["StandardsSubscriptions"]: - standards += f" {standard['StandardsArn'].split('/')[1]}" - # Security Hub is not enabled in region - if standards == "": - self.securityhubs.append( - SecurityHubHub( - "", - "Security Hub", - "NOT_AVAILABLE", - "", - regional_client.region, - ) - ) - else: - # SecurityHub is active so get HubArn + # Check if SecurityHub is active + try: hub_arn = regional_client.describe_hub()["HubArn"] + except ClientError as e: + # Check if Account is subscribed to Security Hub + if e.response["Error"]["Code"] == "InvalidAccessException": + self.securityhubs.append( + SecurityHubHub( + arn="", + id="Security Hub", + status="NOT_AVAILABLE", + standards="", + integrations="", + region=regional_client.region, + ) + ) + else: if not self.audit_resources or ( is_resource_filtered(hub_arn, self.audit_resources) ): hub_id = hub_arn.split("/")[1] + get_enabled_standards_paginator = regional_client.get_paginator( + "get_enabled_standards" + ) + standards = "" + for page in get_enabled_standards_paginator.paginate(): + for standard in page["StandardsSubscriptions"]: + standards += f"{standard['StandardsArn'].split('/')[1]} " + list_enabled_products_for_import_paginator = ( + regional_client.get_paginator( + "list_enabled_products_for_import" + ) + ) + integrations = "" + for page in list_enabled_products_for_import_paginator.paginate(): + for integration in page["ProductSubscriptions"]: + if ( + "/aws/securityhub" not in integration + ): # ignore Security Hub integration with itself + integrations += f"{integration.split('/')[-1]} " self.securityhubs.append( SecurityHubHub( - hub_arn, - hub_id, - "ACTIVE", - standards, - regional_client.region, + arn=hub_arn, + id=hub_id, + status="ACTIVE", + standards=standards, + integrations=integrations, + region=regional_client.region, ) ) else: + # SecurityHub is filtered self.securityhubs.append( SecurityHubHub( - "", - "Security Hub", - "NOT_AVAILABLE", - "", - regional_client.region, + arn="", + id="Security Hub", + status="NOT_AVAILABLE", + standards="", + integrations="", + region=regional_client.region, ) ) except Exception as error: - # Check if Account is subscribed to Security Hub - if "InvalidAccessException" in str(error): - self.securityhubs.append( - SecurityHubHub( - "", - "Security Hub", - "NOT_AVAILABLE", - "", - regional_client.region, - ) - ) - else: - logger.error( - f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) -@dataclass -class SecurityHubHub: +class SecurityHubHub(BaseModel): arn: str id: str status: str standards: str + integrations: str region: str - - def __init__( - self, - arn, - id, - status, - standards, - region, - ): - self.arn = arn - self.id = id - self.status = status - self.standards = standards - self.region = region diff --git a/tests/providers/aws/services/securityhub/securityhub_enabled/securityhub_enabled_test.py b/tests/providers/aws/services/securityhub/securityhub_enabled/securityhub_enabled_test.py index 5254cf83..63e3a041 100644 --- a/tests/providers/aws/services/securityhub/securityhub_enabled/securityhub_enabled_test.py +++ b/tests/providers/aws/services/securityhub/securityhub_enabled/securityhub_enabled_test.py @@ -10,11 +10,12 @@ class Test_accessanalyzer_enabled_without_findings: securityhub_client = mock.MagicMock securityhub_client.securityhubs = [ SecurityHubHub( - "", - "Security Hub", - "NOT_AVAILABLE", - "", - "eu-west-1", + arn="", + id="Security Hub", + status="NOT_AVAILABLE", + standards="", + integrations="", + region="eu-west-1", ) ] with mock.patch( @@ -33,15 +34,16 @@ class Test_accessanalyzer_enabled_without_findings: assert result[0].status_extended == "Security Hub is not enabled" assert result[0].resource_id == "Security Hub" - def test_securityhub_hub_active(self): + def test_securityhub_hub_active_with_standards(self): securityhub_client = mock.MagicMock securityhub_client.securityhubs = [ SecurityHubHub( - "arn:aws:securityhub:us-east-1:0123456789012:hub/default", - "default", - "ACTIVE", - "cis-aws-foundations-benchmark/v/1.2.0", - "eu-west-1", + arn="arn:aws:securityhub:us-east-1:0123456789012:hub/default", + id="default", + status="ACTIVE", + standards="cis-aws-foundations-benchmark/v/1.2.0", + integrations="", + region="eu-west-1", ) ] with mock.patch( @@ -59,6 +61,68 @@ class Test_accessanalyzer_enabled_without_findings: assert result[0].status == "PASS" assert ( result[0].status_extended - == "Security Hub is enabled with standards cis-aws-foundations-benchmark/v/1.2.0" + == "Security Hub is enabled with standards: cis-aws-foundations-benchmark/v/1.2.0" + ) + assert result[0].resource_id == "default" + + def test_securityhub_hub_active_with_integrations(self): + securityhub_client = mock.MagicMock + securityhub_client.securityhubs = [ + SecurityHubHub( + arn="arn:aws:securityhub:us-east-1:0123456789012:hub/default", + id="default", + status="ACTIVE", + standards="", + integrations="prowler", + region="eu-west-1", + ) + ] + with mock.patch( + "prowler.providers.aws.services.securityhub.securityhub_service.SecurityHub", + new=securityhub_client, + ): + # Test Check + from prowler.providers.aws.services.securityhub.securityhub_enabled.securityhub_enabled import ( + securityhub_enabled, + ) + + check = securityhub_enabled() + result = check.execute() + + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Security Hub is enabled without standards but with integrations: prowler" + ) + assert result[0].resource_id == "default" + + def test_securityhub_hub_active_without_integrations_or_standards(self): + securityhub_client = mock.MagicMock + securityhub_client.securityhubs = [ + SecurityHubHub( + arn="arn:aws:securityhub:us-east-1:0123456789012:hub/default", + id="default", + status="ACTIVE", + standards="", + integrations="", + region="eu-west-1", + ) + ] + with mock.patch( + "prowler.providers.aws.services.securityhub.securityhub_service.SecurityHub", + new=securityhub_client, + ): + # Test Check + from prowler.providers.aws.services.securityhub.securityhub_enabled.securityhub_enabled import ( + securityhub_enabled, + ) + + check = securityhub_enabled() + result = check.execute() + + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Security Hub is enabled but without any standard or integration" ) assert result[0].resource_id == "default" diff --git a/tests/providers/aws/services/securityhub/securityhub_service_test.py b/tests/providers/aws/services/securityhub/securityhub_service_test.py index 5715715c..22c2e543 100644 --- a/tests/providers/aws/services/securityhub/securityhub_service_test.py +++ b/tests/providers/aws/services/securityhub/securityhub_service_test.py @@ -30,6 +30,12 @@ def mock_make_api_call(self, operation_name, kwarg): }, ] } + if operation_name == "ListEnabledProductsForImport": + return { + "ProductSubscriptions": [ + "arn:aws:securityhub:us-east-1:0123456789012:product-subscription/prowler/prowler", + ] + } if operation_name == "DescribeHub": return { "HubArn": "arn:aws:securityhub:us-east-1:0123456789012:hub/default", @@ -74,4 +80,5 @@ class Test_SecurityHub_Service: == "arn:aws:securityhub:us-east-1:0123456789012:hub/default" ) assert securityhub.securityhubs[0].id == "default" - assert securityhub.securityhubs[0].standards == " cis-aws-foundations-benchmark" + assert securityhub.securityhubs[0].standards == "cis-aws-foundations-benchmark " + assert securityhub.securityhubs[0].integrations == "prowler "