mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
eded97d735
Co-authored-by: Rubén De la Torre Vico <ruben@prowler.com>
474 lines
18 KiB
Python
474 lines
18 KiB
Python
from unittest.mock import patch
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from azure.core.credentials import AccessToken
|
|
from azure.identity import DefaultAzureCredential
|
|
from mock import MagicMock
|
|
|
|
from prowler.config.config import (
|
|
default_config_file_path,
|
|
default_fixer_config_file_path,
|
|
load_and_validate_config_file,
|
|
)
|
|
from prowler.providers.azure.azure_provider import AzureProvider
|
|
from prowler.providers.azure.exceptions.exceptions import (
|
|
AzureBrowserAuthNoTenantIDError,
|
|
AzureHTTPResponseError,
|
|
AzureInvalidProviderIdError,
|
|
AzureNoAuthenticationMethodError,
|
|
AzureTenantIDNoBrowserAuthError,
|
|
)
|
|
from prowler.providers.azure.models import AzureIdentityInfo, AzureRegionConfig
|
|
from prowler.providers.common.models import Connection
|
|
|
|
|
|
class TestAzureProvider:
|
|
def test_azure_provider(self):
|
|
subscription_id = None
|
|
tenant_id = None
|
|
# We need to set exactly one auth method
|
|
az_cli_auth = True
|
|
sp_env_auth = None
|
|
browser_auth = None
|
|
managed_identity_auth = None
|
|
client_id = None
|
|
client_secret = None
|
|
|
|
fixer_config = load_and_validate_config_file(
|
|
"azure", default_fixer_config_file_path
|
|
)
|
|
azure_region = "AzureCloud"
|
|
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_identity",
|
|
return_value=AzureIdentityInfo(),
|
|
), patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.get_locations",
|
|
return_value={},
|
|
):
|
|
azure_provider = AzureProvider(
|
|
az_cli_auth,
|
|
sp_env_auth,
|
|
browser_auth,
|
|
managed_identity_auth,
|
|
tenant_id,
|
|
azure_region,
|
|
subscription_id,
|
|
config_path=default_config_file_path,
|
|
fixer_config=fixer_config,
|
|
client_id=client_id,
|
|
client_secret=client_secret,
|
|
)
|
|
|
|
assert azure_provider.region_config == AzureRegionConfig(
|
|
name="AzureCloud",
|
|
authority=None,
|
|
base_url="https://management.azure.com",
|
|
credential_scopes=["https://management.azure.com/.default"],
|
|
)
|
|
assert isinstance(azure_provider.session, DefaultAzureCredential)
|
|
assert azure_provider.identity == AzureIdentityInfo(
|
|
identity_id="",
|
|
identity_type="",
|
|
tenant_ids=[],
|
|
tenant_domain="Unknown tenant domain (missing AAD permissions)",
|
|
subscriptions={},
|
|
locations={},
|
|
)
|
|
assert azure_provider.audit_config == {
|
|
"shodan_api_key": None,
|
|
"php_latest_version": "8.2",
|
|
"python_latest_version": "3.12",
|
|
"java_latest_version": "17",
|
|
"recommended_minimal_tls_versions": ["1.2", "1.3"],
|
|
}
|
|
|
|
def test_azure_provider_not_auth_methods(self):
|
|
subscription_id = None
|
|
tenant_id = None
|
|
# We need to set exactly one auth method
|
|
az_cli_auth = None
|
|
sp_env_auth = None
|
|
browser_auth = None
|
|
managed_identity_auth = None
|
|
|
|
config_file = default_config_file_path
|
|
fixer_config = default_fixer_config_file_path
|
|
azure_region = "AzureCloud"
|
|
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_identity",
|
|
return_value=AzureIdentityInfo(),
|
|
), patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.get_locations",
|
|
return_value={},
|
|
):
|
|
|
|
with pytest.raises(AzureNoAuthenticationMethodError) as exception:
|
|
_ = AzureProvider(
|
|
az_cli_auth,
|
|
sp_env_auth,
|
|
browser_auth,
|
|
managed_identity_auth,
|
|
tenant_id,
|
|
azure_region,
|
|
subscription_id,
|
|
config_file,
|
|
fixer_config,
|
|
)
|
|
assert exception.type == AzureNoAuthenticationMethodError
|
|
assert (
|
|
"Azure provider requires at least one authentication method set: [--az-cli-auth | --sp-env-auth | --browser-auth | --managed-identity-auth]"
|
|
in exception.value.args[0]
|
|
)
|
|
|
|
def test_azure_provider_browser_auth_but_not_tenant_id(self):
|
|
subscription_id = None
|
|
tenant_id = None
|
|
# We need to set exactly one auth method
|
|
az_cli_auth = None
|
|
sp_env_auth = None
|
|
browser_auth = True
|
|
managed_identity_auth = None
|
|
config_file = default_config_file_path
|
|
fixer_config = default_fixer_config_file_path
|
|
azure_region = "AzureCloud"
|
|
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_identity",
|
|
return_value=AzureIdentityInfo(),
|
|
), patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.get_locations",
|
|
return_value={},
|
|
):
|
|
|
|
with pytest.raises(AzureBrowserAuthNoTenantIDError) as exception:
|
|
_ = AzureProvider(
|
|
az_cli_auth,
|
|
sp_env_auth,
|
|
browser_auth,
|
|
managed_identity_auth,
|
|
tenant_id,
|
|
azure_region,
|
|
subscription_id,
|
|
config_file,
|
|
fixer_config,
|
|
)
|
|
assert exception.type == AzureBrowserAuthNoTenantIDError
|
|
assert (
|
|
exception.value.args[0]
|
|
== "[2004] Azure Tenant ID (--tenant-id) is required for browser authentication mode"
|
|
)
|
|
|
|
def test_azure_provider_not_browser_auth_but_tenant_id(self):
|
|
subscription_id = None
|
|
|
|
tenant_id = "test-tenant-id"
|
|
# We need to set exactly one auth method
|
|
az_cli_auth = None
|
|
sp_env_auth = None
|
|
browser_auth = False
|
|
managed_identity_auth = None
|
|
config_file = default_config_file_path
|
|
fixer_config = default_fixer_config_file_path
|
|
azure_region = "AzureCloud"
|
|
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_identity",
|
|
return_value=AzureIdentityInfo(),
|
|
), patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.get_locations",
|
|
return_value={},
|
|
):
|
|
|
|
with pytest.raises(AzureTenantIDNoBrowserAuthError) as exception:
|
|
_ = AzureProvider(
|
|
az_cli_auth,
|
|
sp_env_auth,
|
|
browser_auth,
|
|
managed_identity_auth,
|
|
tenant_id,
|
|
azure_region,
|
|
subscription_id,
|
|
config_file,
|
|
fixer_config,
|
|
)
|
|
assert exception.type == AzureTenantIDNoBrowserAuthError
|
|
assert (
|
|
exception.value.args[0]
|
|
== "[2005] Azure Tenant ID (--tenant-id) is required for browser authentication mode"
|
|
)
|
|
|
|
def test_test_connection_browser_auth(self):
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.DefaultAzureCredential"
|
|
) as mock_default_credential, patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_session"
|
|
) as mock_setup_session, patch(
|
|
"prowler.providers.azure.azure_provider.SubscriptionClient"
|
|
) as mock_resource_client:
|
|
|
|
# Mock the return value of DefaultAzureCredential
|
|
mock_credentials = MagicMock()
|
|
mock_credentials.get_token.return_value = AccessToken(
|
|
token="fake_token", expires_on=9999999999
|
|
)
|
|
mock_default_credential.return_value = mock_credentials
|
|
|
|
# Mock setup_session to return a mocked session object
|
|
mock_session = MagicMock()
|
|
mock_setup_session.return_value = mock_session
|
|
|
|
# Mock ResourceManagementClient to avoid real API calls
|
|
mock_client = MagicMock()
|
|
mock_resource_client.return_value = mock_client
|
|
|
|
test_connection = AzureProvider.test_connection(
|
|
browser_auth=True,
|
|
tenant_id=str(uuid4()),
|
|
region="AzureCloud",
|
|
raise_on_exception=False,
|
|
)
|
|
|
|
assert isinstance(test_connection, Connection)
|
|
assert test_connection.is_connected
|
|
assert test_connection.error is None
|
|
|
|
def test_test_connection_tenant_id_client_id_client_secret(self):
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.DefaultAzureCredential"
|
|
) as mock_default_credential, patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_session"
|
|
) as mock_setup_session, patch(
|
|
"prowler.providers.azure.azure_provider.SubscriptionClient"
|
|
) as mock_resource_client, patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.validate_static_credentials"
|
|
) as mock_validate_static_credentials:
|
|
|
|
# Mock the return value of DefaultAzureCredential
|
|
mock_credentials = MagicMock()
|
|
mock_credentials.get_token.return_value = AccessToken(
|
|
token="fake_token", expires_on=9999999999
|
|
)
|
|
mock_default_credential.return_value = {
|
|
"client_id": str(uuid4()),
|
|
"client_secret": str(uuid4()),
|
|
"tenant_id": str(uuid4()),
|
|
}
|
|
|
|
# Mock setup_session to return a mocked session object
|
|
mock_session = MagicMock()
|
|
mock_setup_session.return_value = mock_session
|
|
|
|
# Mock ValidateStaticCredentials to avoid real API calls
|
|
mock_validate_static_credentials.return_value = None
|
|
|
|
# Mock ResourceManagementClient to avoid real API calls
|
|
mock_client = MagicMock()
|
|
mock_resource_client.return_value = mock_client
|
|
|
|
test_connection = AzureProvider.test_connection(
|
|
browser_auth=False,
|
|
tenant_id=str(uuid4()),
|
|
region="AzureCloud",
|
|
raise_on_exception=False,
|
|
client_id=str(uuid4()),
|
|
client_secret=str(uuid4()),
|
|
)
|
|
|
|
assert isinstance(test_connection, Connection)
|
|
assert test_connection.is_connected
|
|
assert test_connection.error is None
|
|
|
|
def test_test_connection_provider_validation(self):
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.DefaultAzureCredential"
|
|
) as mock_default_credential, patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_session"
|
|
) as mock_setup_session, patch(
|
|
"prowler.providers.azure.azure_provider.SubscriptionClient"
|
|
) as mock_resource_client, patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.validate_static_credentials"
|
|
) as mock_validate_static_credentials:
|
|
|
|
# Mock the return value of DefaultAzureCredential
|
|
mock_default_credential.return_value = {
|
|
"client_id": str(uuid4()),
|
|
"client_secret": str(uuid4()),
|
|
"tenant_id": str(uuid4()),
|
|
}
|
|
|
|
# Mock setup_session to return a mocked session object
|
|
mock_session = MagicMock()
|
|
mock_setup_session.return_value = mock_session
|
|
|
|
# Mock ValidateStaticCredentials to avoid real API calls
|
|
mock_validate_static_credentials.return_value = None
|
|
|
|
# Mock ResourceManagementClient to avoid real API calls
|
|
mock_subscription = MagicMock()
|
|
mock_subscription.subscription_id = "test_provider_id"
|
|
mock_return_value = MagicMock()
|
|
mock_return_value.subscriptions.list.return_value = [mock_subscription]
|
|
mock_resource_client.return_value = mock_return_value
|
|
|
|
test_connection = AzureProvider.test_connection(
|
|
browser_auth=False,
|
|
tenant_id=str(uuid4()),
|
|
region="AzureCloud",
|
|
raise_on_exception=False,
|
|
client_id=str(uuid4()),
|
|
client_secret=str(uuid4()),
|
|
provider_id="test_provider_id",
|
|
)
|
|
|
|
assert isinstance(test_connection, Connection)
|
|
assert test_connection.is_connected
|
|
assert test_connection.error is None
|
|
|
|
def test_test_connection_provider_validation_error(self):
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.DefaultAzureCredential"
|
|
) as mock_default_credential, patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_session"
|
|
) as mock_setup_session, patch(
|
|
"prowler.providers.azure.azure_provider.SubscriptionClient"
|
|
) as mock_resource_client, patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.validate_static_credentials"
|
|
) as mock_validate_static_credentials:
|
|
|
|
# Mock the return value of DefaultAzureCredential
|
|
mock_default_credential.return_value = {
|
|
"client_id": str(uuid4()),
|
|
"client_secret": str(uuid4()),
|
|
"tenant_id": str(uuid4()),
|
|
}
|
|
|
|
# Mock setup_session to return a mocked session object
|
|
mock_session = MagicMock()
|
|
mock_setup_session.return_value = mock_session
|
|
|
|
# Mock ValidateStaticCredentials to avoid real API calls
|
|
mock_validate_static_credentials.return_value = None
|
|
|
|
# Mock ResourceManagementClient to avoid real API calls
|
|
mock_subscription = MagicMock()
|
|
mock_subscription.subscription_id = "test_invalid_provider_id"
|
|
mock_return_value = MagicMock()
|
|
mock_return_value.subscriptions.list.return_value = [mock_subscription]
|
|
mock_resource_client.return_value = mock_return_value
|
|
|
|
test_connection = AzureProvider.test_connection(
|
|
browser_auth=False,
|
|
tenant_id=str(uuid4()),
|
|
region="AzureCloud",
|
|
raise_on_exception=False,
|
|
client_id=str(uuid4()),
|
|
client_secret=str(uuid4()),
|
|
provider_id="test_provider_id",
|
|
)
|
|
|
|
assert test_connection.error is not None
|
|
assert isinstance(test_connection.error, AzureInvalidProviderIdError)
|
|
assert (
|
|
"The provided credentials are not valid for the specified Azure subscription."
|
|
in test_connection.error.args[0]
|
|
)
|
|
|
|
def test_test_connection_with_ClientAuthenticationError(self):
|
|
with pytest.raises(AzureHTTPResponseError) as exception:
|
|
tenant_id = str(uuid4())
|
|
AzureProvider.test_connection(
|
|
browser_auth=True,
|
|
tenant_id=tenant_id,
|
|
region="AzureCloud",
|
|
)
|
|
|
|
assert exception.type == AzureHTTPResponseError
|
|
assert (
|
|
exception.value.args[0]
|
|
== f"[2010] Error in HTTP response from Azure - Authentication failed: Unable to get authority configuration for https://login.microsoftonline.com/{tenant_id}. Authority would typically be in a format of https://login.microsoftonline.com/your_tenant or https://tenant_name.ciamlogin.com or https://tenant_name.b2clogin.com/tenant.onmicrosoft.com/policy. Also please double check your tenant name or GUID is correct."
|
|
)
|
|
|
|
def test_test_connection_without_any_method(self):
|
|
with pytest.raises(AzureNoAuthenticationMethodError) as exception:
|
|
AzureProvider.test_connection()
|
|
|
|
assert exception.type == AzureNoAuthenticationMethodError
|
|
assert (
|
|
"[2003] Azure provider requires at least one authentication method set: [--az-cli-auth | --sp-env-auth | --browser-auth | --managed-identity-auth]"
|
|
in exception.value.args[0]
|
|
)
|
|
|
|
def test_test_connection_with_httpresponseerror(self):
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.get_locations",
|
|
return_value={},
|
|
), patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_session"
|
|
) as mock_setup_session:
|
|
|
|
mock_setup_session.side_effect = AzureHTTPResponseError(
|
|
file="test_file", original_exception="Simulated HttpResponseError"
|
|
)
|
|
|
|
with pytest.raises(AzureHTTPResponseError) as exception:
|
|
AzureProvider.test_connection(
|
|
az_cli_auth=True,
|
|
raise_on_exception=True,
|
|
)
|
|
|
|
assert exception.type == AzureHTTPResponseError
|
|
assert (
|
|
exception.value.args[0]
|
|
== "[2010] Error in HTTP response from Azure - Simulated HttpResponseError"
|
|
)
|
|
|
|
def test_test_connection_with_exception(self):
|
|
with patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.setup_session"
|
|
) as mock_setup_session:
|
|
|
|
mock_setup_session.side_effect = Exception("Simulated Exception")
|
|
|
|
with pytest.raises(Exception) as exception:
|
|
AzureProvider.test_connection(
|
|
sp_env_auth=True,
|
|
raise_on_exception=True,
|
|
)
|
|
|
|
assert exception.type == Exception
|
|
assert exception.value.args[0] == "Simulated Exception"
|
|
|
|
@pytest.mark.parametrize(
|
|
"subscription_ids, expected_regions",
|
|
[
|
|
(None, {"region1", "region2", "region3"}),
|
|
(["sub1", "sub2"], {"region1", "region2", "region3"}),
|
|
("sub1", {"region1", "region2"}),
|
|
("not_exists", set()),
|
|
],
|
|
)
|
|
@patch("prowler.providers.azure.azure_provider.AzureProvider.get_locations")
|
|
@patch(
|
|
"prowler.providers.azure.azure_provider.AzureProvider.__init__",
|
|
return_value=None,
|
|
)
|
|
def test_get_regions(
|
|
self,
|
|
azure_provider_init_mock, # noqa: F841
|
|
azure_get_locations_mock,
|
|
subscription_ids,
|
|
expected_regions,
|
|
):
|
|
azure_get_locations_mock.return_value = {
|
|
"sub1": ["region1", "region2"],
|
|
"sub2": ["region2", "region3"],
|
|
}
|
|
|
|
azure_provider = AzureProvider()
|
|
regions = azure_provider.get_regions(subscription_ids=subscription_ids)
|
|
|
|
assert regions == expected_regions
|