From fdb1956b0b504c68cb3545259505d32cea010654 Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:29:48 +0100 Subject: [PATCH] feat(opensearch): add new fixer `opensearch_service_domains_not_publicly_accessible_fixer` (#5926) --- ...e_domains_not_publicly_accessible_fixer.py | 43 ++++++ ...ains_not_publicly_accessible_fixer_test.py | 135 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer.py create mode 100644 tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer_test.py diff --git a/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer.py b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer.py new file mode 100644 index 0000000000..a43a94fc6e --- /dev/null +++ b/prowler/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer.py @@ -0,0 +1,43 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.opensearch.opensearch_client import ( + opensearch_client, +) + + +def fixer(resource_id: str, region: str) -> bool: + """ + Modify the OpenSearch domain's resource-based policy to remove public access. + Specifically, this fixer update the domain config and add an empty policy to remove the old one. + Requires the es:UpdateDomainConfig permission. + Permissions: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "es:UpdateDomainConfig", + "Resource": "*" + } + ] + } + Args: + resource_id (str): The OpenSearch domain name. + region (str): AWS region where the OpenSearch domain exists. + Returns: + bool: True if the operation is successful (policy updated), False otherwise. + """ + try: + regional_client = opensearch_client.regional_clients[region] + + regional_client.update_domain_config( + DomainName=resource_id, + AccessPolicies="", + ) + + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + else: + return True diff --git a/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer_test.py b/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer_test.py new file mode 100644 index 0000000000..01bb37f03d --- /dev/null +++ b/tests/providers/aws/services/opensearch/opensearch_service_domains_not_publicly_accessible/opensearch_service_domains_not_publicly_accessible_fixer_test.py @@ -0,0 +1,135 @@ +from json import dumps +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_WEST_2, + set_mocked_aws_provider, +) + +domain_name = "test-domain" + +policy_data_restricted = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": [f"{AWS_ACCOUNT_NUMBER}"]}, + "Action": ["es:*"], + "Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*", + } + ], +} + +policy_data_not_restricted = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": ["*"]}, + "Action": ["es:*"], + "Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*", + } + ], +} + +policy_data_not_restricted_principal = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": ["es:*"], + "Resource": f"arn:aws:es:us-west-2:{AWS_ACCOUNT_NUMBER}:domain/{domain_name}/*", + } + ], +} + + +class Test_opensearch_service_domains_not_publicly_accessible_fixer: + @mock_aws + def test_policy_data_restricted_error(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_WEST_2) + opensearch_client.create_domain(DomainName=domain_name)["DomainStatus"]["ARN"] + opensearch_client.update_domain_config( + DomainName=domain_name, + AccessPolicies=str(policy_data_restricted), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_WEST_2]) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer.opensearch_client", + new=OpenSearchService(aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer import ( + fixer, + ) + + assert not fixer("domain_name_non_existing", AWS_REGION_US_WEST_2) + + @mock_aws + def test_policy_data_not_restricted_with_principal_AWS(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_WEST_2) + opensearch_client.create_domain(DomainName=domain_name)["DomainStatus"]["ARN"] + opensearch_client.update_domain_config( + DomainName=domain_name, + AccessPolicies=dumps(policy_data_not_restricted), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_WEST_2]) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer.opensearch_client", + new=OpenSearchService(aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(domain_name, AWS_REGION_US_WEST_2) + + @mock_aws + def test_policy_data_not_restricted_with_principal_no_AWS(self): + opensearch_client = client("opensearch", region_name=AWS_REGION_US_WEST_2) + opensearch_client.create_domain(DomainName=domain_name)["DomainStatus"]["ARN"] + opensearch_client.update_domain_config( + DomainName=domain_name, + AccessPolicies=dumps(policy_data_not_restricted_principal), + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_WEST_2]) + + from prowler.providers.aws.services.opensearch.opensearch_service import ( + OpenSearchService, + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer.opensearch_client", + new=OpenSearchService(aws_provider), + ): + from prowler.providers.aws.services.opensearch.opensearch_service_domains_not_publicly_accessible.opensearch_service_domains_not_publicly_accessible_fixer import ( + fixer, + ) + + assert fixer(domain_name, AWS_REGION_US_WEST_2)