feat(azure): add provider id validation inside test_connection (#5391)

Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
Pedro Martín
2024-10-16 12:02:40 +02:00
committed by GitHub
parent 45c32abcdf
commit 4be83f240a
3 changed files with 131 additions and 3 deletions
+22 -3
View File
@@ -32,6 +32,7 @@ from prowler.providers.azure.exceptions.exceptions import (
AzureGetTokenIdentityError,
AzureHTTPResponseError,
AzureInteractiveBrowserCredentialError,
AzureInvalidAccountCredentialsError,
AzureNoAuthenticationMethodError,
AzureNoSubscriptionsError,
AzureNotTenantIdButClientIdAndClienSecret,
@@ -539,6 +540,7 @@ class AzureProvider(Provider):
raise_on_exception=True,
client_id=None,
client_secret=None,
provider_id=None,
) -> Connection:
"""Test connection to Azure subscription.
@@ -554,6 +556,7 @@ class AzureProvider(Provider):
raise_on_exception (bool): Flag indicating whether to raise an exception if the connection fails.
client_id (str): The Azure client ID.
client_secret (str): The Azure client secret.
provider_id (str): The provider ID, in this case it's the Azure subscription ID.
Returns:
bool: True if the connection is successful, False otherwise.
@@ -610,10 +613,20 @@ class AzureProvider(Provider):
# Create a SubscriptionClient
subscription_client = SubscriptionClient(credentials)
# Get info from the first subscription
subscription = next(subscription_client.subscriptions.list())
# Get info from the subscriptions
available_subscriptions = []
for subscription in subscription_client.subscriptions.list():
available_subscriptions.append(subscription)
logger.info(f"Connected to Azure subscription: {subscription.display_name}")
if provider_id and provider_id not in [
sub.subscription_id for sub in available_subscriptions
]:
raise AzureInvalidAccountCredentialsError(
file=os.path.basename(__file__),
message="The provided credentials are not valid for the specified Azure subscription.",
)
logger.info("Azure provider: Connection to Azure subscription successful")
return Connection(is_connected=True)
# Exceptions from validate_arguments
@@ -697,6 +710,12 @@ class AzureProvider(Provider):
if raise_on_exception:
raise client_secret_error
return Connection(error=client_secret_error)
# Exceptions from provider_id validation
except AzureInvalidAccountCredentialsError as invalid_credentials_error:
logger.error(str(invalid_credentials_error))
if raise_on_exception:
raise invalid_credentials_error
return Connection(error=invalid_credentials_error)
# Exceptions from SubscriptionClient
except HttpResponseError as http_response_error:
logger.error(
@@ -97,6 +97,10 @@ class AzureBaseException(ProwlerException):
"message": "The provided tenant ID and client ID do not belong to the provided client secret",
"remediation": "Check the tenant ID and client ID and ensure they belong to the provided client secret.",
},
(1937, "AzureInvalidAccountCredentialsError"): {
"message": "The provided provider_id does not match with the available subscriptions",
"remediation": "Check the provider_id and ensure it is a valid subscription for the given credentials.",
},
}
def __init__(self, code, file=None, original_exception=None, message=None):
@@ -279,3 +283,10 @@ class AzureTenantIdAndClientIdNotBelongingToClientSecretError(AzureCredentialsEr
super().__init__(
1936, file=file, original_exception=original_exception, message=message
)
class AzureInvalidAccountCredentialsError(AzureBaseException):
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
1937, file=file, original_exception=original_exception, message=message
)
@@ -15,6 +15,7 @@ from prowler.providers.azure.azure_provider import AzureProvider
from prowler.providers.azure.exceptions.exceptions import (
AzureBrowserAuthNoTenantIDError,
AzureHTTPResponseError,
AzureInvalidAccountCredentialsError,
AzureNoAuthenticationMethodError,
AzureTenantIDNoBrowserAuthError,
)
@@ -280,6 +281,103 @@ class TestAzureProvider:
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, AzureInvalidAccountCredentialsError
)
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())