From 9f2de7d2f9df8ce676d4b2d6078bde70048eb21d Mon Sep 17 00:00:00 2001 From: johannes-engler-mw <132657752+johannes-engler-mw@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:39:16 +0200 Subject: [PATCH] feat(containerregistry): add new check `containerregistry_not_publicly_accessible ` (#5291) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rubén De la Torre Vico --- .../__init__.py | 0 ...stry_not_publicly_accessible.metadata.json | 30 +++ ...ntainerregistry_not_publicly_accessible.py | 32 ++++ .../containerregistry_service.py | 9 +- ...tainerregistry_admin_user_disabled_test.py | 5 + ...erregistry_not_publicly_accessible_test.py | 174 ++++++++++++++++++ .../containerregistry_service_test.py | 1 + 7 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/__init__.py create mode 100644 prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible.metadata.json create mode 100644 prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible.py create mode 100644 tests/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible_test.py diff --git a/prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/__init__.py b/prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible.metadata.json b/prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible.metadata.json new file mode 100644 index 0000000000..93c2b9e885 --- /dev/null +++ b/prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "azure", + "CheckID": "containerregistry_not_publicly_accessible", + "CheckTitle": "Restrict public network access to the Container Registry", + "CheckType": [], + "ServiceName": "containerregistry", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "ContainerRegistry", + "Description": "Ensure that public network access to the Azure Container Registry is restricted.", + "Risk": "Public accessibility exposes the Container Registry to potential attacks, unauthorized usage, and data breaches. Restricting access minimizes the surface area for attacks and ensures that only authorized networks can access the registry.", + "RelatedUrl": "https://learn.microsoft.com/en-us/azure/container-registry/container-registry-access-selected-networks", + "Remediation": { + "Code": { + "CLI": "az acr update --name --default-action Deny", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that the necessary virtual network configurations or IP rules are in place to allow access from required services once public access is restricted. Review the network access settings regularly to maintain a secure environment. To restrict public network access to your Azure Container Registry: 1. Navigate to your Container Registry in the Azure Portal. 2. Under 'Settings'->'Networking', configure the 'Public network access' settings to 'Disabled'. 3. Set up virtual network service endpoints or private endpoints as needed for secure access. 4. Review and adjust IP access rules as necessary.", + "Url": "https://learn.microsoft.com/en-us/azure/container-registry/container-registry-access-selected-networks" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "This feature is only available for Premium SKU registries." +} diff --git a/prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible.py b/prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible.py new file mode 100644 index 0000000000..d8a6a41da4 --- /dev/null +++ b/prowler/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible.py @@ -0,0 +1,32 @@ +from prowler.lib.check.models import Check, Check_Report_Azure +from prowler.providers.azure.services.containerregistry.containerregistry_client import ( + containerregistry_client, +) + + +class containerregistry_not_publicly_accessible(Check): + def execute(self) -> list[Check_Report_Azure]: + findings = [] + + for subscription, registries in containerregistry_client.registries.items(): + for registry_id, container_registry_info in registries.items(): + report = Check_Report_Azure(self.metadata()) + report.subscription = subscription + report.resource_name = container_registry_info.name + report.resource_id = registry_id + report.location = container_registry_info.location + report.status = "FAIL" + report.status_extended = f"Container Registry {container_registry_info.name} from subscription {subscription} allows unrestricted network access." + + if ( + getattr( + container_registry_info.network_rule_set, "default_action", "" + ).lower() + == "deny" + ): + report.status = "PASS" + report.status_extended = f"Container Registry {container_registry_info.name} from subscription {subscription} does not allow unrestricted network access." + + findings.append(report) + + return findings diff --git a/prowler/providers/azure/services/containerregistry/containerregistry_service.py b/prowler/providers/azure/services/containerregistry/containerregistry_service.py index 63b1b22a23..9b8e3ce336 100644 --- a/prowler/providers/azure/services/containerregistry/containerregistry_service.py +++ b/prowler/providers/azure/services/containerregistry/containerregistry_service.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from azure.mgmt.containerregistry import ContainerRegistryManagementClient +from azure.mgmt.containerregistry.models import NetworkRuleSet from prowler.lib.logger import logger from prowler.providers.azure.azure_provider import AzureProvider @@ -39,11 +40,14 @@ class ContainerRegistry(AzureService): admin_user_enabled=getattr( registry, "admin_user_enabled", False ), + network_rule_set=getattr( + registry, "network_rule_set", None + ), monitor_diagnostic_settings=self._get_registry_monitor_settings( registry.name, resource_group, subscription ), ) - } + }, ) except Exception as error: logger.error( @@ -85,4 +89,5 @@ class ContainerRegistryInfo: login_server: str public_network_access: str admin_user_enabled: bool - monitor_diagnostic_settings: list[DiagnosticSetting] = None + network_rule_set: NetworkRuleSet + monitor_diagnostic_settings: list[DiagnosticSetting] diff --git a/tests/providers/azure/services/containerregistry/containerregistry_admin_user_disabled/containerregistry_admin_user_disabled_test.py b/tests/providers/azure/services/containerregistry/containerregistry_admin_user_disabled/containerregistry_admin_user_disabled_test.py index 1d92841844..5ec9277a7c 100644 --- a/tests/providers/azure/services/containerregistry/containerregistry_admin_user_disabled/containerregistry_admin_user_disabled_test.py +++ b/tests/providers/azure/services/containerregistry/containerregistry_admin_user_disabled/containerregistry_admin_user_disabled_test.py @@ -57,6 +57,8 @@ class TestContainerRegistryAdminUserDisabled: login_server="mock_login_server.azurecr.io", public_network_access="Enabled", admin_user_enabled=True, + network_rule_set=None, + monitor_diagnostic_settings=[], ) } } @@ -111,11 +113,14 @@ class TestContainerRegistryAdminUserDisabled: login_server="mock_login_server.azurecr.io", public_network_access="Enabled", admin_user_enabled=False, + network_rule_set=None, + monitor_diagnostic_settings=[], ) } } check = containerregistry_admin_user_disabled() + result = check.execute() assert len(result) == 1 assert result[0].status == "PASS" diff --git a/tests/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible_test.py b/tests/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible_test.py new file mode 100644 index 0000000000..b82c7efc83 --- /dev/null +++ b/tests/providers/azure/services/containerregistry/containerregistry_not_publicly_accessible/containerregistry_not_publicly_accessible_test.py @@ -0,0 +1,174 @@ +from unittest import mock +from unittest.mock import MagicMock +from uuid import uuid4 + +from azure.mgmt.containerregistry.models import NetworkRuleSet + +from tests.providers.azure.azure_fixtures import ( + AZURE_SUBSCRIPTION_ID, + set_mocked_azure_provider, +) + + +class Test_containerregistry_not_publicly_accessible: + def test_no_container_registries(self): + containerregistry_client = MagicMock() + containerregistry_client.registries = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), mock.patch( + "prowler.providers.azure.services.containerregistry.containerregistry_not_publicly_accessible.containerregistry_not_publicly_accessible.containerregistry_client", + new=containerregistry_client, + ): + from prowler.providers.azure.services.containerregistry.containerregistry_not_publicly_accessible.containerregistry_not_publicly_accessible import ( + containerregistry_not_publicly_accessible, + ) + + check = containerregistry_not_publicly_accessible() + result = check.execute() + assert len(result) == 0 + + def test_container_registry_network_access_unrestricted(self): + containerregistry_client = MagicMock() + registry_id = str(uuid4()) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), mock.patch( + "prowler.providers.azure.services.containerregistry.containerregistry_not_publicly_accessible.containerregistry_not_publicly_accessible.containerregistry_client", + new=containerregistry_client, + ): + from prowler.providers.azure.services.containerregistry.containerregistry_not_publicly_accessible.containerregistry_not_publicly_accessible import ( + containerregistry_not_publicly_accessible, + ) + from prowler.providers.azure.services.containerregistry.containerregistry_service import ( + ContainerRegistryInfo, + ) + + containerregistry_client.registries = { + AZURE_SUBSCRIPTION_ID: { + registry_id: ContainerRegistryInfo( + id=registry_id, + name="mock_registry", + location="westeurope", + resource_group="mock_resource_group", + sku="Basic", + login_server="mock_login_server.azurecr.io", + public_network_access="Enabled", + admin_user_enabled=True, + network_rule_set=NetworkRuleSet(default_action="Allow"), + monitor_diagnostic_settings=[ + { + "id": "id1/id1", + "logs": [ + { + "category": "ContainerLogs", + "enabled": True, + }, + { + "category": "AdminLogs", + "enabled": False, + }, + ], + "storage_account_name": "mock_storage_account", + "storage_account_id": "mock_storage_account_id", + "name": "mock_diagnostic_setting", + } + ], + ) + } + } + + check = containerregistry_not_publicly_accessible() + + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Container Registry {containerregistry_client.registries[AZURE_SUBSCRIPTION_ID][registry_id].name} from subscription {AZURE_SUBSCRIPTION_ID} allows unrestricted network access." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == "mock_registry" + assert ( + result[0].resource_id + == containerregistry_client.registries[AZURE_SUBSCRIPTION_ID][ + registry_id + ].id + ) + assert result[0].location == "westeurope" + + def test_container_registry_network_access_restricted(self): + containerregistry_client = mock.MagicMock() + containerregistry_client.registries = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_azure_provider(), + ), mock.patch( + "prowler.providers.azure.services.containerregistry.containerregistry_not_publicly_accessible.containerregistry_not_publicly_accessible.containerregistry_client", + new=containerregistry_client, + ): + from prowler.providers.azure.services.containerregistry.containerregistry_not_publicly_accessible.containerregistry_not_publicly_accessible import ( + containerregistry_not_publicly_accessible, + ) + from prowler.providers.azure.services.containerregistry.containerregistry_service import ( + ContainerRegistryInfo, + ) + + registry_id = "mock_registry_id" + + containerregistry_client.registries = { + AZURE_SUBSCRIPTION_ID: { + registry_id: ContainerRegistryInfo( + id=registry_id, + name="mock_registry", + location="westeurope", + resource_group="mock_resource_group", + sku="Basic", + login_server="mock_login_server.azurecr.io", + public_network_access="Enabled", + admin_user_enabled=False, + network_rule_set=NetworkRuleSet(default_action="Deny"), + monitor_diagnostic_settings=[ + { + "id": "id1/id1", + "logs": [ + { + "category": "ContainerLogs", + "enabled": True, + }, + { + "category": "AdminLogs", + "enabled": False, + }, + ], + "storage_account_name": "mock_storage_account", + "storage_account_id": "mock_storage_account_id", + "name": "mock_diagnostic_setting", + } + ], + ) + } + } + + check = containerregistry_not_publicly_accessible() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Container Registry {containerregistry_client.registries[AZURE_SUBSCRIPTION_ID][registry_id].name} from subscription {AZURE_SUBSCRIPTION_ID} does not allow unrestricted network access." + ) + assert result[0].subscription == AZURE_SUBSCRIPTION_ID + assert result[0].resource_name == "mock_registry" + assert ( + result[0].resource_id + == containerregistry_client.registries[AZURE_SUBSCRIPTION_ID][ + registry_id + ].id + ) + assert result[0].location == "westeurope" diff --git a/tests/providers/azure/services/containerregistry/containerregistry_service_test.py b/tests/providers/azure/services/containerregistry/containerregistry_service_test.py index d4d2a8cb28..a7262b0d37 100644 --- a/tests/providers/azure/services/containerregistry/containerregistry_service_test.py +++ b/tests/providers/azure/services/containerregistry/containerregistry_service_test.py @@ -34,6 +34,7 @@ class TestContainerRegistryService: login_server="mock_login_server.azurecr.io", public_network_access="Enabled", admin_user_enabled=True, + network_rule_set=None, monitor_diagnostic_settings=[ { "id": "id1/id1",