From 0d743fa179429fc201c51c01a41022b7e69328c1 Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Thu, 10 Oct 2024 19:06:34 +0200 Subject: [PATCH] feat(opensearch): Add check logic with respective unit tests and metadata --- .../__init__.py | 0 ...es_and_zoneawareness_enabled.metadata.json | 34 +++ ...t_three_nodes_and_zoneawareness_enabled.py | 33 +++ ...ee_nodes_and_zoneawareness_enabled_test.py | 203 ++++++++++++++++++ 4 files changed, 270 insertions(+) create mode 100644 prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/__init__.py create mode 100644 prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.metadata.json create mode 100644 prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.py create mode 100644 tests/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled_test.py diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/__init__.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.metadata.json b/prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.metadata.json new file mode 100644 index 0000000000..a79fe8a845 --- /dev/null +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled", + "CheckTitle": "Elasticsearch/Opensearch domains should have at least three data nodes", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "opensearch", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:{region}:{account-id}:domain/{domain-name}", + "Severity": "medium", + "ResourceType": "AwsElasticsearchDomain", + "Description": "This control checks whether Elasticsearch/Opensearch domains are configured with at least three data nodes and zoneAwarenessEnabled is true.", + "Risk": "Without at least three data nodes, the Elasticsearch/Opensearch domain may not be fault-tolerant, leading to a higher risk of data loss or unavailability in case of node failure.", + "RelatedUrl": "https://docs.aws.amazon.com/opensearch-service/latest/developerguide/what-is.html", + "Remediation": { + "Code": { + "CLI": "aws opensearch update-domain-config --domain-name --cluster-config InstanceCount=3,ZoneAwarenessEnabled=true", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/es-controls.html#es-6", + "Terraform": "" + }, + "Recommendation": { + "Text": "Modify the Elasticsearch/Opensearch domain to ensure at least three data nodes and enable zone awareness for high availability.", + "Url": "https://docs.aws.amazon.com/opensearch-service/latest/developerguide/managedomains-configuration-changes.html" + } + }, + "Categories": [ + "redundancy" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.py new file mode 100644 index 0000000000..5b7d766344 --- /dev/null +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.py @@ -0,0 +1,33 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.opensearch.opensearch_client import ( + opensearch_client, +) + + +class opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled(Check): + def execute(self): + findings = [] + + for domain in opensearch_client.opensearch_domains.values(): + report = Check_Report_AWS(self.metadata()) + report.region = domain.region + report.resource_id = domain.name + report.resource_arn = domain.arn + report.resource_tags = domain.tags + + report.status = "FAIL" + report.status_extended = f"Opensearch domain {domain.name} does not meet the requirements: it has less than 3 data nodes and zone awareness is not enabled." + + if domain.instance_count >= 3 and domain.zone_awareness_enabled: + report.status = "PASS" + report.status_extended = f"Opensearch domain {domain.name} has {domain.instance_count} data nodes and zone awareness enabled." + elif domain.instance_count >= 3 and not domain.zone_awareness_enabled: + report.status = "FAIL" + report.status_extended = f"Opensearch domain {domain.name} has {domain.instance_count} data nodes, but zone awareness is not enabled." + elif domain.instance_count < 3 and domain.zone_awareness_enabled: + report.status = "FAIL" + report.status_extended = f"Opensearch domain {domain.name} has zone awareness enabled, but only {domain.instance_count} data nodes." + + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled_test.py b/tests/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled_test.py new file mode 100644 index 0000000000..a529fcf3d6 --- /dev/null +++ b/tests/providers/aws/services/opensearch/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled/opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled_test.py @@ -0,0 +1,203 @@ +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, + set_mocked_aws_provider, +) + + +class Test_opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled: + @mock_aws + def test_no_domains(self): + client("opensearch", region_name=AWS_REGION_US_EAST_1) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_client", + new=OpenSearchService(mocked_aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled import ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled, + ) + + check = ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled() + ) + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_instance_count_less_than_three_zoneawareness_enabled(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_EAST_1) + domain = opensearch_client.create_domain( + DomainName="test-domain", + ClusterConfig={"ZoneAwarenessEnabled": True, "InstanceCount": 2}, + ) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_client", + new=OpenSearchService(mocked_aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled import ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled, + ) + + check = ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled() + ) + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Opensearch domain test-domain has zone awareness enabled, but only 2 data nodes." + ) + assert result[0].resource_id == domain["DomainStatus"]["DomainName"] + assert ( + result[0].resource_arn + == f"arn:aws:es:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:domain/{domain["DomainStatus"]["DomainName"]}" + ) + + @mock_aws + def test_instance_count_more_than_three_zoneawareness_not_enabled(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_EAST_1) + domain = opensearch_client.create_domain( + DomainName="test-domain", + ClusterConfig={"ZoneAwarenessEnabled": False, "InstanceCount": 3}, + ) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_client", + new=OpenSearchService(mocked_aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled import ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled, + ) + + check = ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled() + ) + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Opensearch domain test-domain has 3 data nodes, but zone awareness is not enabled." + ) + assert result[0].resource_id == domain["DomainStatus"]["DomainName"] + assert ( + result[0].resource_arn + == f"arn:aws:es:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:domain/{domain["DomainStatus"]["DomainName"]}" + ) + + @mock_aws + def test_instance_count_less_than_three_zoneawareness_not_enabled(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_EAST_1) + domain = opensearch_client.create_domain( + DomainName="test-domain", + ClusterConfig={"ZoneAwarenessEnabled": False, "InstanceCount": 2}, + ) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_client", + new=OpenSearchService(mocked_aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled import ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled, + ) + + check = ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled() + ) + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Opensearch domain test-domain does not meet the requirements: it has less than 3 data nodes and zone awareness is not enabled." + ) + assert result[0].resource_id == domain["DomainStatus"]["DomainName"] + assert ( + result[0].resource_arn + == f"arn:aws:es:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:domain/{domain["DomainStatus"]["DomainName"]}" + ) + + @mock_aws + def test_logging_instance_count_more_than_three_zoneawareness_enabled(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_EAST_1) + domain = opensearch_client.create_domain( + DomainName="test-domain", + ClusterConfig={"ZoneAwarenessEnabled": True, "InstanceCount": 3}, + ) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + mocked_aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=mocked_aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_client", + new=OpenSearchService(mocked_aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled.opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled import ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled, + ) + + check = ( + opensearch_service_domains_at_least_three_nodes_and_zoneawareness_enabled() + ) + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Opensearch domain test-domain has 3 data nodes and zone awareness enabled." + ) + assert result[0].resource_id == domain["DomainStatus"]["DomainName"] + assert ( + result[0].resource_arn + == f"arn:aws:es:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:domain/{domain["DomainStatus"]["DomainName"]}" + )