Files
prowler/tests/providers/azure/azure_provider_test.py
johannes-engler-mw eded97d735 feat(azure): check for minimal TLS version for Azure SQL server (#5745)
Co-authored-by: Rubén De la Torre Vico <ruben@prowler.com>
2024-12-11 16:37:53 +01:00

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