From ed1574ef4aaeba650a157919df8bb61dbe8eb539 Mon Sep 17 00:00:00 2001 From: issabayevmk Date: Thu, 9 Jan 2025 11:36:05 +0100 Subject: [PATCH] Added Azure Container Registry (ACR) Support --- ...vices.acr.subscriptions.id.registries.html | 36 +++++++++ .../output/data/inc-scoutsuite/scoutsuite.js | 1 + ScoutSuite/providers/azure/facade/acr.py | 28 +++++++ ScoutSuite/providers/azure/facade/base.py | 2 + ScoutSuite/providers/azure/metadata.json | 10 +++ .../providers/azure/resources/acr/base.py | 9 +++ .../azure/resources/acr/registries.py | 75 +++++++++++++++++++ .../findings/acr-admin-user-enabled.json | 20 +++++ .../rules/findings/acr-public-access.json | 21 ++++++ ScoutSuite/providers/azure/services.py | 2 + ScoutSuite/utils.py | 1 + requirements.txt | 1 + 12 files changed, 206 insertions(+) create mode 100755 ScoutSuite/output/data/html/partials/azure/services.acr.subscriptions.id.registries.html create mode 100644 ScoutSuite/providers/azure/facade/acr.py create mode 100644 ScoutSuite/providers/azure/resources/acr/base.py create mode 100644 ScoutSuite/providers/azure/resources/acr/registries.py create mode 100644 ScoutSuite/providers/azure/rules/findings/acr-admin-user-enabled.json create mode 100644 ScoutSuite/providers/azure/rules/findings/acr-public-access.json diff --git a/ScoutSuite/output/data/html/partials/azure/services.acr.subscriptions.id.registries.html b/ScoutSuite/output/data/html/partials/azure/services.acr.subscriptions.id.registries.html new file mode 100755 index 000000000..290974338 --- /dev/null +++ b/ScoutSuite/output/data/html/partials/azure/services.acr.subscriptions.id.registries.html @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/ScoutSuite/output/data/inc-scoutsuite/scoutsuite.js b/ScoutSuite/output/data/inc-scoutsuite/scoutsuite.js index f40c25120..7b8ffa152 100755 --- a/ScoutSuite/output/data/inc-scoutsuite/scoutsuite.js +++ b/ScoutSuite/output/data/inc-scoutsuite/scoutsuite.js @@ -1233,6 +1233,7 @@ function makeTitle(title) { const uppercaseTitles = [ 'acm', 'aks', 'ec2', 'ecr', 'ecs', 'efs', 'eks', 'gke', 'iam', 'kms', 'rbac', 'rds', 'sns', 'ses', 'sqs', 'vpc', 'elb', 'elbv2', 'emr', 'dns', 'oss', 'ram', + 'acr' ] const formattedTitles = { diff --git a/ScoutSuite/providers/azure/facade/acr.py b/ScoutSuite/providers/azure/facade/acr.py new file mode 100644 index 000000000..f885ea25e --- /dev/null +++ b/ScoutSuite/providers/azure/facade/acr.py @@ -0,0 +1,28 @@ +from azure.mgmt.containerregistry import ContainerRegistryManagementClient + +from ScoutSuite.core.console import print_exception +from ScoutSuite.providers.utils import run_concurrently +from ScoutSuite.utils import get_user_agent + + +class ACRFacade: + + def __init__(self, credentials): + self.credentials = credentials + + def get_client(self, subscription_id: str): + client = ContainerRegistryManagementClient(self.credentials.get_credentials(), + subscription_id=subscription_id, user_agent=get_user_agent()) + return client + + async def get_registries(self, subscription_id: str): + try: + client = self.get_client(subscription_id) + registries = await run_concurrently( + lambda: list(client.registries.list()) + ) + except Exception as e: + print_exception(f'Failed to retrieve container registries: {e}') + return [] + else: + return registries diff --git a/ScoutSuite/providers/azure/facade/base.py b/ScoutSuite/providers/azure/facade/base.py index b209e2e43..29c50ee4f 100755 --- a/ScoutSuite/providers/azure/facade/base.py +++ b/ScoutSuite/providers/azure/facade/base.py @@ -1,5 +1,6 @@ from ScoutSuite.providers.azure.authentication_strategy import AzureCredentials from ScoutSuite.providers.azure.facade.aad import AADFacade +from ScoutSuite.providers.azure.facade.acr import ACRFacade from ScoutSuite.providers.azure.facade.rbac import RBACFacade from ScoutSuite.providers.azure.facade.keyvault import KeyVaultFacade from ScoutSuite.providers.azure.facade.network import NetworkFacade @@ -48,6 +49,7 @@ def __init__(self, self.all_subscriptions = all_subscriptions self.aad = AADFacade(credentials) + self.acr = ACRFacade(credentials) self.rbac = RBACFacade(credentials) self.keyvault = KeyVaultFacade(credentials) self.virtualmachines = VirtualMachineFacade(credentials) diff --git a/ScoutSuite/providers/azure/metadata.json b/ScoutSuite/providers/azure/metadata.json index b35f9b7ec..96f92fc48 100755 --- a/ScoutSuite/providers/azure/metadata.json +++ b/ScoutSuite/providers/azure/metadata.json @@ -31,6 +31,16 @@ } } }, + "container": { + "acr": { + "resources": { + "registries": { + "cols": 2, + "path": "services.acrs.subscriptions.id.registries" + } + } + } + }, "database": { "sqldatabase": { "resources": { diff --git a/ScoutSuite/providers/azure/resources/acr/base.py b/ScoutSuite/providers/azure/resources/acr/base.py new file mode 100644 index 000000000..166d29c41 --- /dev/null +++ b/ScoutSuite/providers/azure/resources/acr/base.py @@ -0,0 +1,9 @@ +from ScoutSuite.providers.azure.resources.subscriptions import Subscriptions + +from .registries import Registries + + +class ACRRegistries(Subscriptions): + _children = [ + (Registries, 'registries') + ] diff --git a/ScoutSuite/providers/azure/resources/acr/registries.py b/ScoutSuite/providers/azure/resources/acr/registries.py new file mode 100644 index 000000000..247d03322 --- /dev/null +++ b/ScoutSuite/providers/azure/resources/acr/registries.py @@ -0,0 +1,75 @@ +from ScoutSuite.providers.azure.facade.base import AzureFacade +from ScoutSuite.providers.azure.resources.base import AzureResources +from ScoutSuite.providers.utils import get_non_provider_id + + +class Registries(AzureResources): + + def __init__(self, facade: AzureFacade, subscription_id: str): + super().__init__(facade) + self.subscription_id = subscription_id + + async def fetch_all(self): + for raw_registry in await self.facade.acr.get_registries(self.subscription_id): + id, registry = self._parse_registry(raw_registry) + self[id] = registry + + def _parse_registry(self, raw_registry): + + registry = {} + registry['id'] = get_non_provider_id(raw_registry.id) + registry['name'] = raw_registry.name + registry['type'] = raw_registry.type + registry['location'] = raw_registry.location + if raw_registry.tags is not None: + registry['tags'] = ["{}:{}".format(key, value) for key, value in raw_registry.tags.items()] + else: + registry['tags'] = [] + + registry['sku'] = {} + registry['sku']['name'] = raw_registry.sku.name + registry['sku']['tier'] = raw_registry.sku.tier + + registry['identity'] = raw_registry.identity + registry['login_server'] = raw_registry.login_server + registry['provisioning_state'] = raw_registry.provisioning_state + registry['status'] = raw_registry.status + registry['admin_user_enabled'] = bool(raw_registry.admin_user_enabled) + registry['network_rule_set'] = raw_registry.network_rule_set + + registry['policies'] = { + 'azureAD_authentication_policy': {}, + 'soft_delete_policy': {}, + 'quarantine_policy': {}, + 'trust_policy': {}, + 'retention_policy': {}, + 'export_policy': {} + } + + registry['policies']['azureAD_suthentication_policy'] = raw_registry.policies.additional_properties['azureADAuthenticationAsArmPolicy'] + + registry['policies']['soft_delete_policy']['retention_days'] = raw_registry.policies.additional_properties['softDeletePolicy']['retentionDays'] + registry['policies']['soft_delete_policy']['status'] = raw_registry.policies.additional_properties['softDeletePolicy']['status'] + + registry['policies']['quarantine_policy']['status'] = raw_registry.policies.quarantine_policy.status + + registry['policies']['trust_policy']['status'] = raw_registry.policies.trust_policy.status + registry['policies']['trust_policy']['type'] = raw_registry.policies.trust_policy.type + + registry['policies']['retention_policy']['status'] = raw_registry.policies.retention_policy.status + registry['policies']['retention_policy']['type'] = raw_registry.policies.retention_policy.days + + registry['policies']['export_policy']['status'] = raw_registry.policies.export_policy.status + + registry['encryption'] = {} + registry['encryption']['status'] = raw_registry.encryption.status + registry['encryption']['key_vault_properties'] = raw_registry.encryption.key_vault_properties + + registry['data_endpoint_enabled'] = bool(raw_registry.data_endpoint_enabled) + registry['data_endpoint_host_names'] = raw_registry.data_endpoint_host_names + registry['private_endpoint_connections'] = raw_registry.private_endpoint_connections + registry['public_network_access'] = raw_registry.public_network_access + registry['network_rule_bypass_options'] = raw_registry.network_rule_bypass_options + registry['zone_redundancy'] = raw_registry.zone_redundancy + + return registry['id'], registry \ No newline at end of file diff --git a/ScoutSuite/providers/azure/rules/findings/acr-admin-user-enabled.json b/ScoutSuite/providers/azure/rules/findings/acr-admin-user-enabled.json new file mode 100644 index 000000000..dc791c33f --- /dev/null +++ b/ScoutSuite/providers/azure/rules/findings/acr-admin-user-enabled.json @@ -0,0 +1,20 @@ +{ + "description": "Container Registry admin user Enabled", + "rationale": "", + "remediation": "", + "compliance": [ + ], + "references": [ + ], + "dashboard_name": "Registries", + "path": "containerregistry.subscriptions.id.registries.id", + "conditions": [ + "and", + [ + "containerregistry.subscriptions.id.registries.id.admin_user_enabled", + "notEqual", + "False" + ] + ], + "id_suffix": "admin_user_enabled" +} \ No newline at end of file diff --git a/ScoutSuite/providers/azure/rules/findings/acr-public-access.json b/ScoutSuite/providers/azure/rules/findings/acr-public-access.json new file mode 100644 index 000000000..df16e1e2c --- /dev/null +++ b/ScoutSuite/providers/azure/rules/findings/acr-public-access.json @@ -0,0 +1,21 @@ +{ + "description": "Container Registry public access Enabled", + "rationale": "", + "remediation": "", + "compliance": [ + ], + "references": [ + "https://rnd-confluence.veeam.local/display/AP/AppSec+Cloud+Infrastructure+Security+Requirements" + ], + "dashboard_name": "Registries", + "path": "containerregistry.subscriptions.id.registries.id", + "conditions": [ + "and", + [ + "containerregistry.subscriptions.id.registries.id.public_network_access", + "equal", + "Enabled" + ] + ], + "id_suffix": "public_access_enabled" +} \ No newline at end of file diff --git a/ScoutSuite/providers/azure/services.py b/ScoutSuite/providers/azure/services.py index a8604ea7a..64f793e42 100755 --- a/ScoutSuite/providers/azure/services.py +++ b/ScoutSuite/providers/azure/services.py @@ -1,6 +1,7 @@ from ScoutSuite.providers.azure.authentication_strategy import AzureCredentials from ScoutSuite.providers.azure.facade.base import AzureFacade from ScoutSuite.providers.azure.resources.aad.base import AAD +from ScoutSuite.providers.azure.resources.acr.base import ACRRegistries from ScoutSuite.providers.azure.resources.rbac.base import RBAC from ScoutSuite.providers.azure.resources.keyvault.base import KeyVaults from ScoutSuite.providers.azure.resources.network.base import Networks @@ -44,6 +45,7 @@ def __init__(self, programmatic_execution) self.aad = AAD(facade) + self.acr = ACRRegistries(facade) self.rbac = RBAC(facade) self.securitycenter = SecurityCenter(facade) self.sqldatabase = Servers(facade) diff --git a/ScoutSuite/utils.py b/ScoutSuite/utils.py index 3965e717b..8647b0a29 100755 --- a/ScoutSuite/utils.py +++ b/ScoutSuite/utils.py @@ -38,6 +38,7 @@ 'ssm': 'Systems Manager', # Azure 'aad': 'Azure Active Directory', + 'registry': 'ACR Registry', 'storageaccounts': 'Storage Accounts', 'sqldatabase': 'SQL Database', 'securitycenter': 'Security Center', diff --git a/requirements.txt b/requirements.txt index c2041cd46..3eeec4b95 100755 --- a/requirements.txt +++ b/requirements.txt @@ -34,6 +34,7 @@ azure-identity==1.5.0 ## for resources +azure-mgmt-containerregistry==10.3.0 azure-mgmt-resource==15.0.0 azure-mgmt-storage==17.0.0 azure-mgmt-monitor==2.0.0