Merge branch 'PROWLER-1391-provider-contract-dynamic-discovery' into PROWLER-1771-public-dynamic-provider-class-resolver

# Conflicts:
#	prowler/CHANGELOG.md
This commit is contained in:
StylusFrost committed 2026-05-31 20:05:35 +02:00
commit fe821a41ea
306 files changed
+19238 -2270

No files matched your search

+7
View File
@@ -6,6 +6,7 @@ from unittest import mock
from requests import Response
from prowler.config.config import (
Provider,
check_current_version,
get_available_compliance_frameworks,
load_and_validate_config_file,
@@ -17,6 +18,12 @@ MOCK_OLD_PROWLER_VERSION = "0.0.0"
MOCK_PROWLER_MASTER_VERSION = "3.4.0"
def test_provider_enum_includes_stackit_for_global_discovery():
providers = [provider.value for provider in Provider]
assert "stackit" in providers
def mock_prowler_get_latest_release(_, **_kwargs):
"""Mock requests.get() to get the Prowler latest release"""
response = Response()
+2 -1
View File
@@ -17,7 +17,7 @@ prowler_command = "prowler"
# capsys
# https://docs.pytest.org/en/7.1.x/how-to/capture-stdout-stderr.html
prowler_default_usage_error = "usage: prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,vercel,dashboard,iac,image,llm} ..."
prowler_default_usage_error = "usage: prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,stackit,vercel,dashboard,iac,image,llm} ..."
def mock_get_available_providers():
@@ -38,6 +38,7 @@ def mock_get_available_providers():
"llm",
"cloudflare",
"openstack",
"stackit",
]
+2
View File
@@ -160,6 +160,7 @@ class TestGetSensitiveArguments:
assert "--atlas-private-key" in result
assert "--nhn-password" in result
assert "--os-password" in result
assert "--stackit-service-account-key" in result
def test_does_not_include_non_sensitive_flags(self):
"""Verify non-sensitive flags are not in the set."""
@@ -168,3 +169,4 @@ class TestGetSensitiveArguments:
assert "--region" not in result
assert "--profile" not in result
assert "--output-formats" not in result
assert "--stackit-service-account-key-path" not in result
+103
View File
@@ -1507,3 +1507,106 @@ class TestFinding:
with pytest.raises(KeyError):
Finding.transform_api_finding(dummy_finding, provider)
@patch(
"prowler.lib.outputs.finding.get_check_compliance",
new=mock_get_check_compliance,
)
def test_generate_output_stackit(self):
provider = MagicMock()
provider.type = "stackit"
provider.auth_method = "service_account_key"
provider.identity.project_id = "test-project-id"
provider.identity.project_name = "test-project-name"
check_output = MagicMock()
check_output.resource_id = "test_resource_id"
check_output.resource_name = "test_resource_name"
check_output.resource_details = ""
check_output.location = "eu01"
check_output.status = Status.PASS
check_output.status_extended = "mock_status_extended"
check_output.muted = False
check_output.check_metadata = mock_check_metadata(provider="stackit")
check_output.resource = {}
check_output.compliance = {}
output_options = MagicMock()
output_options.unix_timestamp = True
finding_output = Finding.generate_output(provider, check_output, output_options)
assert isinstance(finding_output, Finding)
assert finding_output.auth_method == "service_account_key"
assert finding_output.account_uid == "test-project-id"
assert finding_output.account_name == "test-project-name"
assert finding_output.resource_name == "test_resource_name"
assert finding_output.resource_uid == "test_resource_id"
assert finding_output.region == "eu01"
assert finding_output.status == Status.PASS
assert finding_output.muted is False
def test_transform_api_finding_stackit(self):
provider = MagicMock()
provider.type = "stackit"
provider.auth_method = "service_account_key"
provider.identity.project_id = "stackit-project-id"
provider.identity.project_name = "stackit-project-name"
dummy_finding = DummyAPIFinding()
dummy_finding.inserted_at = 1234567890
dummy_finding.scan = DummyScan(provider=provider)
dummy_finding.uid = "finding-uid-stackit"
dummy_finding.status = "PASS"
dummy_finding.status_extended = "StackIT check extended"
check_metadata = {
"provider": "stackit",
"checkid": "service_stackit_check_001",
"checktitle": "Test StackIT Check",
"checktype": [],
"servicename": "service",
"subservicename": "",
"severity": "high",
"resourcetype": "StackITResourceType",
"description": "StackIT check description",
"risk": "High risk",
"relatedurl": "",
"remediation": {
"code": {
"nativeiac": "",
"terraform": "",
"cli": "",
"other": "",
},
"recommendation": {
"text": "Fix it",
"url": "https://hub.prowler.com/check/service_stackit_check_001",
},
},
"resourceidtemplate": "template",
"categories": ["encryption"],
"dependson": [],
"relatedto": [],
"notes": "StackIT notes",
}
dummy_finding.check_metadata = check_metadata
dummy_finding.raw_result = {}
dummy_finding.resource_name = "stackit-resource-name"
dummy_finding.resource_id = "stackit-resource-uid"
dummy_finding.location = "eu01"
dummy_finding.project_id = "stackit-project-id"
resource = DummyResource(
uid="stackit-resource-uid",
name="stackit-resource-name",
resource_arn="",
region="eu01",
tags=[],
)
dummy_finding.resources = DummyResources(resource)
dummy_finding.muted = False
finding_obj = Finding.transform_api_finding(dummy_finding, provider)
assert finding_obj.auth_method == "service_account_key"
assert finding_obj.account_uid == "stackit-project-id"
assert finding_obj.account_name == "stackit-project-name"
assert finding_obj.resource_name == "stackit-resource-name"
assert finding_obj.resource_uid == "stackit-resource-uid"
assert finding_obj.region == "eu01"
+37
View File
@@ -962,6 +962,43 @@ class TestHTML:
assert summary == image_list_html_assessment_summary
def test_stackit_get_assessment_summary(self):
"""Test StackIT HTML assessment summary shows the project ID."""
findings = [generate_finding_output()]
output = HTML(findings)
provider = MagicMock()
provider.type = "stackit"
provider.identity.project_id = "f033ea6d-8697-40eb-a60e-acfa9128480d"
provider.identity.project_name = "ProwlerDev"
provider.identity.audited_regions = {"eu01", "eu02"}
summary = output.get_assessment_summary(provider)
assert "StackIT Assessment Summary" in summary
assert "StackIT Credentials" in summary
assert "<b>Project ID:</b> f033ea6d-8697-40eb-a60e-acfa9128480d" in summary
assert "<b>Project Name:</b> ProwlerDev" in summary
assert "<b>Regions:</b> eu01, eu02" in summary
assert "<b>Authentication Type:</b> Service Account Key" in summary
def test_stackit_get_assessment_summary_without_project_name(self):
"""Project ID is always shown; the Project Name line is omitted when
the service account cannot read it from Resource Manager."""
findings = [generate_finding_output()]
output = HTML(findings)
provider = MagicMock()
provider.type = "stackit"
provider.identity.project_id = "f033ea6d-8697-40eb-a60e-acfa9128480d"
provider.identity.project_name = ""
provider.identity.audited_regions = {"eu01"}
summary = output.get_assessment_summary(provider)
assert "<b>Project ID:</b> f033ea6d-8697-40eb-a60e-acfa9128480d" in summary
assert "<b>Project Name:</b>" not in summary
def test_process_markdown_bold_text(self):
"""Test that **text** is converted to <strong>text</strong>"""
test_text = "This is **bold text** and this is **also bold**"
+40
View File
@@ -1256,3 +1256,43 @@ class TestReport:
f"\t{Fore.YELLOW}INFO{Style.RESET_ALL} There are no resources"
)
mocked_print.assert_called() # Verifying that print was called
def test_report_with_stackit_provider_pass(self):
finding = MagicMock()
finding.status = "PASS"
finding.muted = False
finding.location = "eu01"
finding.check_metadata.Provider = "stackit"
finding.status_extended = "Security group has no unrestricted SSH access"
output_options = MagicMock()
output_options.verbose = True
output_options.status = ["PASS", "FAIL"]
output_options.fixer = False
provider = MagicMock()
provider.type = "stackit"
with mock.patch("builtins.print") as mocked_print:
report([finding], provider, output_options)
mocked_print.assert_called()
def test_report_with_stackit_provider_fail(self):
finding = MagicMock()
finding.status = "FAIL"
finding.muted = False
finding.location = "eu01"
finding.check_metadata.Provider = "stackit"
finding.status_extended = "Security group allows unrestricted SSH access"
output_options = MagicMock()
output_options.verbose = True
output_options.status = ["PASS", "FAIL"]
output_options.fixer = False
provider = MagicMock()
provider.type = "stackit"
with mock.patch("builtins.print") as mocked_print:
report([finding], provider, output_options)
mocked_print.assert_called()
+62
View File
@@ -40,3 +40,65 @@ class TestDisplaySummaryTable:
assert "Subscriptions scanned:" in captured.out
assert "Duplicate Subscription (subscription-id-1)" in captured.out
assert "Duplicate Subscription (subscription-id-2)" in captured.out
def test_stackit_summary_with_project_name(self, capsys):
provider = SimpleNamespace(
type="stackit",
identity=SimpleNamespace(
project_id="test-project-id",
project_name="my-prod-env",
),
)
output_options = SimpleNamespace(
output_directory="out",
output_filename="report",
output_modes=[],
)
findings = [
SimpleNamespace(
status="PASS",
muted=False,
check_metadata=SimpleNamespace(
ServiceName="iaas",
Provider="stackit",
Severity="high",
),
)
]
display_summary_table(findings, provider, output_options)
captured = capsys.readouterr()
assert "Project" in captured.out
assert "my-prod-env" in captured.out
def test_stackit_summary_with_project_id_only(self, capsys):
provider = SimpleNamespace(
type="stackit",
identity=SimpleNamespace(
project_id="test-project-id",
project_name=None,
),
)
output_options = SimpleNamespace(
output_directory="out",
output_filename="report",
output_modes=[],
)
findings = [
SimpleNamespace(
status="PASS",
muted=False,
check_metadata=SimpleNamespace(
ServiceName="iaas",
Provider="stackit",
Severity="high",
),
)
]
display_summary_table(findings, provider, output_options)
captured = capsys.readouterr()
assert "Project ID" in captured.out
assert "test-project-id" in captured.out
@@ -725,6 +725,300 @@ class TestAzureProviderSetupIdentitySubscriptions:
}
class TestAzureProviderSovereignCloudSupport:
"""Sovereign-cloud authentication coverage across AzureCloud,
AzureChinaCloud and AzureUSGovernment for every authentication code path
Prowler exposes. Pinned to issue #8425."""
REGION_CASES = [
(
"AzureCloud",
None,
"https://management.azure.com",
["https://management.azure.com/.default"],
"https://graph.microsoft.com/.default",
"https://api.loganalytics.io",
"login.microsoftonline.com",
),
(
"AzureChinaCloud",
"login.chinacloudapi.cn",
"https://management.chinacloudapi.cn",
["https://management.chinacloudapi.cn/.default"],
"https://microsoftgraph.chinacloudapi.cn/.default",
"https://api.loganalytics.azure.cn",
"login.chinacloudapi.cn",
),
(
"AzureUSGovernment",
"login.microsoftonline.us",
"https://management.usgovcloudapi.net",
["https://management.usgovcloudapi.net/.default"],
"https://graph.microsoft.us/.default",
"https://api.loganalytics.us",
"login.microsoftonline.us",
),
]
@pytest.mark.parametrize(
"region,authority,base_url,credential_scopes,graph_scope,logs_endpoint,_login_endpoint",
REGION_CASES,
)
def test_setup_region_config_per_cloud(
self,
region,
authority,
base_url,
credential_scopes,
graph_scope,
logs_endpoint,
_login_endpoint,
):
config = AzureProvider.setup_region_config(region)
# graph_host mirrors graph_scope without the `/.default` suffix; we
# derive it here to avoid threading a separate parameter through every
# parametrized test in this class.
expected_graph_host = graph_scope.removesuffix("/.default")
assert config == AzureRegionConfig(
name=region,
authority=authority,
base_url=base_url,
credential_scopes=credential_scopes,
graph_host=expected_graph_host,
graph_scope=graph_scope,
logs_endpoint=logs_endpoint,
)
@pytest.mark.parametrize(
"region,authority,_base_url,_credential_scopes,_graph_scope,_logs_endpoint,_login_endpoint",
REGION_CASES,
)
def test_setup_session_static_credentials_passes_authority(
self,
region,
authority,
_base_url,
_credential_scopes,
_graph_scope,
_logs_endpoint,
_login_endpoint,
):
with patch(
"prowler.providers.azure.azure_provider.ClientSecretCredential"
) as mock_client_secret_credential:
azure_credentials = {
"tenant_id": str(uuid4()),
"client_id": str(uuid4()),
"client_secret": "fake-secret-value",
}
region_config = AzureProvider.setup_region_config(region)
AzureProvider.setup_session(
az_cli_auth=False,
sp_env_auth=False,
browser_auth=False,
managed_identity_auth=False,
tenant_id=azure_credentials["tenant_id"],
azure_credentials=azure_credentials,
region_config=region_config,
)
mock_client_secret_credential.assert_called_once_with(
tenant_id=azure_credentials["tenant_id"],
client_id=azure_credentials["client_id"],
client_secret=azure_credentials["client_secret"],
authority=authority,
)
@pytest.mark.parametrize(
"region,authority,_base_url,_credential_scopes,_graph_scope,_logs_endpoint,_login_endpoint",
REGION_CASES,
)
def test_setup_session_browser_auth_passes_authority(
self,
region,
authority,
_base_url,
_credential_scopes,
_graph_scope,
_logs_endpoint,
_login_endpoint,
):
with patch(
"prowler.providers.azure.azure_provider.InteractiveBrowserCredential"
) as mock_interactive_browser_credential:
tenant_id = str(uuid4())
region_config = AzureProvider.setup_region_config(region)
AzureProvider.setup_session(
az_cli_auth=False,
sp_env_auth=False,
browser_auth=True,
managed_identity_auth=False,
tenant_id=tenant_id,
azure_credentials=None,
region_config=region_config,
)
mock_interactive_browser_credential.assert_called_once_with(
tenant_id=tenant_id,
authority=authority,
)
@pytest.mark.parametrize(
"region,authority,_base_url,_credential_scopes,_graph_scope,_logs_endpoint,_login_endpoint",
REGION_CASES,
)
def test_setup_session_default_credential_passes_authority(
self,
region,
authority,
_base_url,
_credential_scopes,
_graph_scope,
_logs_endpoint,
_login_endpoint,
):
with patch(
"prowler.providers.azure.azure_provider.DefaultAzureCredential"
) as mock_default_credential:
region_config = AzureProvider.setup_region_config(region)
AzureProvider.setup_session(
az_cli_auth=True,
sp_env_auth=False,
browser_auth=False,
managed_identity_auth=False,
tenant_id=None,
azure_credentials=None,
region_config=region_config,
)
_, called_kwargs = mock_default_credential.call_args
assert called_kwargs["authority"] == authority
assert called_kwargs["exclude_cli_credential"] is False
assert called_kwargs["exclude_environment_credential"] is True
assert called_kwargs["exclude_managed_identity_credential"] is True
@pytest.mark.parametrize(
"region,_authority,_base_url,_credential_scopes,graph_scope,_logs_endpoint,login_endpoint",
REGION_CASES,
)
def test_verify_client_uses_per_cloud_endpoints(
self,
region,
_authority,
_base_url,
_credential_scopes,
graph_scope,
_logs_endpoint,
login_endpoint,
):
tenant_id = str(uuid4())
client_id = str(uuid4())
client_secret = "fake-secret"
region_config = AzureProvider.setup_region_config(region)
with patch("prowler.providers.azure.azure_provider.requests.post") as mock_post:
mock_post.return_value = MagicMock()
mock_post.return_value.json.return_value = {"access_token": "fake-token"}
AzureProvider.verify_client(
tenant_id, client_id, client_secret, region_config
)
mock_post.assert_called_once()
args, kwargs = mock_post.call_args
assert args[0] == (
f"https://{login_endpoint}/{tenant_id}/oauth2/v2.0/token"
)
assert kwargs["data"]["scope"] == graph_scope
assert kwargs["data"]["client_id"] == client_id
assert kwargs["data"]["client_secret"] == client_secret
@pytest.mark.parametrize(
"region,_authority,base_url,credential_scopes,_graph_scope,_logs_endpoint,_login_endpoint",
REGION_CASES,
)
def test_test_connection_passes_base_url_to_subscription_client(
self,
region,
_authority,
base_url,
credential_scopes,
_graph_scope,
_logs_endpoint,
_login_endpoint,
):
subscription_client_instance = MagicMock()
subscription_client_instance.subscriptions = MagicMock()
subscription_client_instance.subscriptions.list = MagicMock(return_value=[])
subscription_client_class = MagicMock(return_value=subscription_client_instance)
with (
patch(
"prowler.providers.azure.azure_provider.AzureProvider.setup_session"
) as mock_setup_session,
patch(
"prowler.providers.azure.azure_provider.SubscriptionClient",
subscription_client_class,
),
):
mock_setup_session.return_value = MagicMock()
AzureProvider.test_connection(
az_cli_auth=True,
region=region,
raise_on_exception=False,
)
subscription_client_class.assert_called_once()
_, kwargs = subscription_client_class.call_args
assert kwargs["base_url"] == base_url
assert kwargs["credential_scopes"] == credential_scopes
@pytest.mark.parametrize(
"region,_authority,base_url,credential_scopes,_graph_scope,_logs_endpoint,_login_endpoint",
REGION_CASES,
)
def test_get_locations_passes_base_url_to_subscription_client(
self,
region,
_authority,
base_url,
credential_scopes,
_graph_scope,
_logs_endpoint,
_login_endpoint,
):
subscription_client_instance = MagicMock()
subscription_client_instance.subscriptions = MagicMock()
subscription_client_instance.subscriptions.list_locations = MagicMock(
return_value=[]
)
subscription_client_class = MagicMock(return_value=subscription_client_instance)
with (
patch.object(AzureProvider, "__init__", return_value=None),
patch(
"prowler.providers.azure.azure_provider.SubscriptionClient",
subscription_client_class,
),
):
azure_provider = AzureProvider()
azure_provider._session = MagicMock()
azure_provider._region_config = AzureProvider.setup_region_config(region)
azure_provider._identity = AzureIdentityInfo(subscriptions={})
azure_provider.get_locations()
subscription_client_class.assert_called_once()
_, kwargs = subscription_client_class.call_args
assert kwargs["base_url"] == base_url
assert kwargs["credential_scopes"] == credential_scopes
class TestAzureProviderSetupIdentityEventLoop:
"""Regression for the Celery worker scenario where
asyncio.get_event_loop() raised "There is no current event loop in
@@ -2,8 +2,17 @@ from azure.identity import AzureAuthorityHosts
from prowler.providers.azure.lib.regions.regions import (
AZURE_CHINA_CLOUD,
AZURE_CHINA_GRAPH_HOST,
AZURE_CHINA_GRAPH_SCOPE,
AZURE_CHINA_LOGS_ENDPOINT,
AZURE_GENERIC_CLOUD,
AZURE_GENERIC_GRAPH_HOST,
AZURE_GENERIC_GRAPH_SCOPE,
AZURE_GENERIC_LOGS_ENDPOINT,
AZURE_US_GOV_CLOUD,
AZURE_US_GOV_GRAPH_HOST,
AZURE_US_GOV_GRAPH_SCOPE,
AZURE_US_GOV_LOGS_ENDPOINT,
get_regions_config,
)
@@ -20,16 +29,25 @@ class Test_azure_regions:
"authority": None,
"base_url": AZURE_GENERIC_CLOUD,
"credential_scopes": [AZURE_GENERIC_CLOUD + "/.default"],
"graph_host": AZURE_GENERIC_GRAPH_HOST,
"graph_scope": AZURE_GENERIC_GRAPH_SCOPE,
"logs_endpoint": AZURE_GENERIC_LOGS_ENDPOINT,
},
"AzureChinaCloud": {
"authority": AzureAuthorityHosts.AZURE_CHINA,
"base_url": AZURE_CHINA_CLOUD,
"credential_scopes": [AZURE_CHINA_CLOUD + "/.default"],
"graph_host": AZURE_CHINA_GRAPH_HOST,
"graph_scope": AZURE_CHINA_GRAPH_SCOPE,
"logs_endpoint": AZURE_CHINA_LOGS_ENDPOINT,
},
"AzureUSGovernment": {
"authority": AzureAuthorityHosts.AZURE_GOVERNMENT,
"base_url": AZURE_US_GOV_CLOUD,
"credential_scopes": [AZURE_US_GOV_CLOUD + "/.default"],
"graph_host": AZURE_US_GOV_GRAPH_HOST,
"graph_scope": AZURE_US_GOV_GRAPH_SCOPE,
"logs_endpoint": AZURE_US_GOV_LOGS_ENDPOINT,
},
}
@@ -0,0 +1,108 @@
from unittest.mock import MagicMock, patch
import pytest
from prowler.providers.azure.lib.service.service import AzureService
from prowler.providers.azure.models import AzureIdentityInfo, AzureRegionConfig
REGION_CASES = [
(
"AzureCloud",
"https://graph.microsoft.com",
"https://graph.microsoft.com/.default",
"https://api.loganalytics.io",
),
(
"AzureChinaCloud",
"https://microsoftgraph.chinacloudapi.cn",
"https://microsoftgraph.chinacloudapi.cn/.default",
"https://api.loganalytics.azure.cn",
),
(
"AzureUSGovernment",
"https://graph.microsoft.us",
"https://graph.microsoft.us/.default",
"https://api.loganalytics.us",
),
]
def _identity_and_session():
identity = AzureIdentityInfo(
tenant_domain="tenant.onmicrosoft.com",
subscriptions={"sub-1": "Subscription 1"},
)
session = MagicMock()
return identity, session
class TestAzureServiceSovereignClouds:
"""Cover __set_clients__ kwargs for the Graph and Logs clients across the
three sovereign clouds — these are the two service slots in service.py
that historically defaulted to public-cloud endpoints."""
@pytest.mark.parametrize(
"_region,graph_host,graph_scope,_logs_endpoint",
REGION_CASES,
)
def test_set_clients_graph_uses_per_cloud_host_scope_and_adapter(
self, _region, graph_host, graph_scope, _logs_endpoint
):
graph_service = MagicMock()
graph_service.__str__ = MagicMock(return_value="GraphServiceClient")
region_config = AzureRegionConfig(
graph_host=graph_host,
graph_scope=graph_scope,
logs_endpoint=_logs_endpoint,
)
identity, session = _identity_and_session()
with (
patch.object(AzureService, "__init__", return_value=None),
patch(
"prowler.providers.azure.lib.service.service.AzureIdentityAuthenticationProvider"
) as mock_auth_provider_cls,
patch(
"prowler.providers.azure.lib.service.service.GraphClientFactory"
) as mock_factory,
patch(
"prowler.providers.azure.lib.service.service.GraphRequestAdapter"
) as mock_adapter_cls,
):
service = AzureService.__new__(AzureService)
service.__set_clients__(identity, session, graph_service, region_config)
mock_auth_provider_cls.assert_called_once_with(session, scopes=[graph_scope])
mock_factory.create_with_default_middleware.assert_called_once_with(
host=graph_host
)
mock_adapter_cls.assert_called_once_with(
mock_auth_provider_cls.return_value,
client=mock_factory.create_with_default_middleware.return_value,
)
graph_service.assert_called_once_with(
request_adapter=mock_adapter_cls.return_value
)
@pytest.mark.parametrize(
"_region,_graph_host,_graph_scope,logs_endpoint",
REGION_CASES,
)
def test_set_clients_logs_passes_per_cloud_endpoint(
self, _region, _graph_host, _graph_scope, logs_endpoint
):
logs_service = MagicMock()
logs_service.__str__ = MagicMock(return_value="LogsQueryClient")
region_config = AzureRegionConfig(
graph_host=_graph_host,
graph_scope=_graph_scope,
logs_endpoint=logs_endpoint,
)
identity, session = _identity_and_session()
with patch.object(AzureService, "__init__", return_value=None):
service = AzureService.__new__(AzureService)
service.__set_clients__(identity, session, logs_service, region_config)
logs_service.assert_called_once_with(credential=session, endpoint=logs_endpoint)
@@ -0,0 +1,183 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
RULE_NAME = "User granted Admin privilege"
class TestRulesAdminPrivilegeGrantedAlertConfigured:
def test_pass_fully_configured(self):
"""Test PASS when alert is ON, email notifications ON, recipients = all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured import (
rules_admin_privilege_granted_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=True,
)
]
check = rules_admin_privilege_granted_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "properly configured" in findings[0].status_extended
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_alert_off(self):
"""Test FAIL when alert is OFF (INACTIVE state)."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured import (
rules_admin_privilege_granted_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="INACTIVE",
)
]
check = rules_admin_privilege_granted_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "alert is OFF" in findings[0].status_extended
def test_fail_no_email_notifications(self):
"""Test FAIL when alert is ON but email notifications are disabled."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured import (
rules_admin_privilege_granted_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=False,
all_super_admins=False,
)
]
check = rules_admin_privilege_granted_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "email notifications are disabled" in findings[0].status_extended
def test_fail_recipients_not_all_super_admins(self):
"""Test FAIL when email notifications ON but recipients do not include all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured import (
rules_admin_privilege_granted_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=False,
)
]
check = rules_admin_privilege_granted_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert (
"do not include all super administrators" in findings[0].status_extended
)
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_admin_privilege_granted_alert_configured.rules_admin_privilege_granted_alert_configured import (
rules_admin_privilege_granted_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = False
mock_rules_client.system_defined_alerts = []
check = rules_admin_privilege_granted_alert_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,183 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
RULE_NAME = "Gmail potential employee spoofing"
class TestRulesGmailEmployeeSpoofingAlertConfigured:
def test_pass_fully_configured(self):
"""Test PASS when alert is ON, email notifications ON, recipients = all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured import (
rules_gmail_employee_spoofing_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=True,
)
]
check = rules_gmail_employee_spoofing_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "properly configured" in findings[0].status_extended
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_alert_off(self):
"""Test FAIL when alert is OFF (INACTIVE state)."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured import (
rules_gmail_employee_spoofing_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="INACTIVE",
)
]
check = rules_gmail_employee_spoofing_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "alert is OFF" in findings[0].status_extended
def test_fail_no_email_notifications(self):
"""Test FAIL when alert is ON but email notifications are disabled."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured import (
rules_gmail_employee_spoofing_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=False,
all_super_admins=False,
)
]
check = rules_gmail_employee_spoofing_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "email notifications are disabled" in findings[0].status_extended
def test_fail_recipients_not_all_super_admins(self):
"""Test FAIL when email notifications ON but recipients do not include all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured import (
rules_gmail_employee_spoofing_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=False,
)
]
check = rules_gmail_employee_spoofing_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert (
"do not include all super administrators" in findings[0].status_extended
)
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_gmail_employee_spoofing_alert_configured.rules_gmail_employee_spoofing_alert_configured import (
rules_gmail_employee_spoofing_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = False
mock_rules_client.system_defined_alerts = []
check = rules_gmail_employee_spoofing_alert_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,183 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
RULE_NAME = "Government-backed attacks"
class TestRulesGovernmentBackedAttacksAlertConfigured:
def test_pass_fully_configured(self):
"""Test PASS when alert is ON, email notifications ON, recipients = all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured import (
rules_government_backed_attacks_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=True,
)
]
check = rules_government_backed_attacks_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "properly configured" in findings[0].status_extended
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_alert_off(self):
"""Test FAIL when alert is OFF (INACTIVE state)."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured import (
rules_government_backed_attacks_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="INACTIVE",
)
]
check = rules_government_backed_attacks_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "alert is OFF" in findings[0].status_extended
def test_fail_no_email_notifications(self):
"""Test FAIL when alert is ON but email notifications are disabled."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured import (
rules_government_backed_attacks_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=False,
all_super_admins=False,
)
]
check = rules_government_backed_attacks_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "email notifications are disabled" in findings[0].status_extended
def test_fail_recipients_not_all_super_admins(self):
"""Test FAIL when email notifications ON but recipients do not include all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured import (
rules_government_backed_attacks_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=False,
)
]
check = rules_government_backed_attacks_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert (
"do not include all super administrators" in findings[0].status_extended
)
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_government_backed_attacks_alert_configured.rules_government_backed_attacks_alert_configured import (
rules_government_backed_attacks_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = False
mock_rules_client.system_defined_alerts = []
check = rules_government_backed_attacks_alert_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,183 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
RULE_NAME = "Leaked password"
class TestRulesLeakedPasswordAlertConfigured:
def test_pass_fully_configured(self):
"""Test PASS when alert is ON, email notifications ON, recipients = all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured import (
rules_leaked_password_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=True,
)
]
check = rules_leaked_password_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "properly configured" in findings[0].status_extended
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_alert_off(self):
"""Test FAIL when alert is OFF (INACTIVE state)."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured import (
rules_leaked_password_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="INACTIVE",
)
]
check = rules_leaked_password_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "alert is OFF" in findings[0].status_extended
def test_fail_no_email_notifications(self):
"""Test FAIL when alert is ON but email notifications are disabled."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured import (
rules_leaked_password_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=False,
all_super_admins=False,
)
]
check = rules_leaked_password_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "email notifications are disabled" in findings[0].status_extended
def test_fail_recipients_not_all_super_admins(self):
"""Test FAIL when email notifications ON but recipients do not include all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured import (
rules_leaked_password_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=False,
)
]
check = rules_leaked_password_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert (
"do not include all super administrators" in findings[0].status_extended
)
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_leaked_password_alert_configured.rules_leaked_password_alert_configured import (
rules_leaked_password_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = False
mock_rules_client.system_defined_alerts = []
check = rules_leaked_password_alert_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,183 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
RULE_NAME = "User's password changed"
class TestRulesPasswordChangedAlertConfigured:
def test_pass_fully_configured(self):
"""Test PASS when alert is ON, email notifications ON, recipients = all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured import (
rules_password_changed_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=True,
)
]
check = rules_password_changed_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "properly configured" in findings[0].status_extended
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_alert_off(self):
"""Test FAIL when alert is OFF (INACTIVE state)."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured import (
rules_password_changed_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="INACTIVE",
)
]
check = rules_password_changed_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "alert is OFF" in findings[0].status_extended
def test_fail_no_email_notifications(self):
"""Test FAIL when alert is ON but email notifications are disabled."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured import (
rules_password_changed_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=False,
all_super_admins=False,
)
]
check = rules_password_changed_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "email notifications are disabled" in findings[0].status_extended
def test_fail_recipients_not_all_super_admins(self):
"""Test FAIL when email notifications ON but recipients do not include all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured import (
rules_password_changed_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=False,
)
]
check = rules_password_changed_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert (
"do not include all super administrators" in findings[0].status_extended
)
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_password_changed_alert_configured.rules_password_changed_alert_configured import (
rules_password_changed_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = False
mock_rules_client.system_defined_alerts = []
check = rules_password_changed_alert_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,323 @@
from unittest.mock import MagicMock, patch
from tests.providers.googleworkspace.googleworkspace_fixtures import (
set_mocked_googleworkspace_provider,
)
class TestRulesService:
def test_fetch_fully_configured_rule(self):
"""Test fetching a system-defined alert rule with all 3 conditions met."""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_policies_list = MagicMock()
mock_policies_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/rule.system_defined_alerts",
"value": {
"displayName": "Suspicious login",
"description": "Google detected a suspicious sign-in.",
"action": {
"alertCenterAction": {
"recipients": [{"allSuperAdmins": True}],
"alertCenterConfig": {"severity": "LOW"},
}
},
"state": "ACTIVE",
},
}
},
]
}
mock_service.policies().list.return_value = mock_policies_list
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.rules.rules_service import (
Rules,
)
rules = Rules(mock_provider)
assert rules.policies_fetched is True
assert len(rules.system_defined_alerts) == 8
suspicious_login = next(
a
for a in rules.system_defined_alerts
if a.display_name == "Suspicious login"
)
assert suspicious_login.state == "ACTIVE"
assert suspicious_login.email_notifications_enabled is True
assert suspicious_login.all_super_admins is True
assert suspicious_login.severity == "LOW"
def test_fetch_rule_without_email_notifications(self):
"""Test a rule that is ACTIVE but has no email recipients configured."""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_policies_list = MagicMock()
mock_policies_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/rule.system_defined_alerts",
"value": {
"displayName": "Government-backed attacks",
"description": "Google believes a user is targeted.",
"action": {
"alertCenterAction": {
"alertCenterConfig": {"severity": "HIGH"}
}
},
"state": "ACTIVE",
},
}
},
]
}
mock_service.policies().list.return_value = mock_policies_list
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.rules.rules_service import (
Rules,
)
rules = Rules(mock_provider)
gov_attack = next(
a
for a in rules.system_defined_alerts
if a.display_name == "Government-backed attacks"
)
assert gov_attack.state == "ACTIVE"
assert gov_attack.email_notifications_enabled is False
assert gov_attack.all_super_admins is False
assert gov_attack.severity == "HIGH"
def test_empty_response_fills_defaults(self):
"""Test that all 8 rules get default values when API returns nothing."""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_policies_list = MagicMock()
mock_policies_list.execute.return_value = {"policies": []}
mock_service.policies().list.return_value = mock_policies_list
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.rules.rules_service import (
Rules,
)
rules = Rules(mock_provider)
assert rules.policies_fetched is True
assert len(rules.system_defined_alerts) == 8
# All defaults should have no severity (not returned by API)
for alert in rules.system_defined_alerts:
assert alert.severity is None
# INACTIVE defaults: no email notifications
password_changed = next(
a
for a in rules.system_defined_alerts
if a.display_name == "User's password changed"
)
assert password_changed.state == "INACTIVE"
assert password_changed.email_notifications_enabled is False
assert password_changed.all_super_admins is False
admin_privilege = next(
a
for a in rules.system_defined_alerts
if a.display_name == "User granted Admin privilege"
)
assert admin_privilege.state == "INACTIVE"
assert admin_privilege.email_notifications_enabled is False
assert admin_privilege.all_super_admins is False
# ACTIVE defaults: email notifications ON with all super admins
suspicious_login = next(
a
for a in rules.system_defined_alerts
if a.display_name == "Suspicious login"
)
assert suspicious_login.state == "ACTIVE"
assert suspicious_login.email_notifications_enabled is True
assert suspicious_login.all_super_admins is True
gov_attacks = next(
a
for a in rules.system_defined_alerts
if a.display_name == "Government-backed attacks"
)
assert gov_attacks.state == "ACTIVE"
assert gov_attacks.email_notifications_enabled is True
assert gov_attacks.all_super_admins is True
def test_api_error_sets_policies_fetched_false(self):
"""Test that API errors result in policies_fetched being False."""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_service.policies().list.side_effect = Exception("API Error")
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.rules.rules_service import (
Rules,
)
rules = Rules(mock_provider)
assert rules.policies_fetched is False
def test_build_service_returns_none(self):
"""Test early return when _build_service fails."""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_service.GoogleWorkspaceService._build_service",
return_value=None,
),
):
from prowler.providers.googleworkspace.services.rules.rules_service import (
Rules,
)
rules = Rules(mock_provider)
assert rules.policies_fetched is False
assert len(rules.system_defined_alerts) == 0
def test_non_cis_rules_are_ignored(self):
"""Test that system-defined rules not in the 8 CIS rules are ignored."""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_policies_list = MagicMock()
mock_policies_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/rule.system_defined_alerts",
"value": {
"displayName": "Device compromised",
"description": "A device has been compromised.",
"state": "ACTIVE",
},
}
},
]
}
mock_service.policies().list.return_value = mock_policies_list
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.rules.rules_service import (
Rules,
)
rules = Rules(mock_provider)
assert len(rules.system_defined_alerts) == 8
names = {a.display_name for a in rules.system_defined_alerts}
assert "Device compromised" not in names
def test_system_defined_alert_model(self):
"""Test SystemDefinedAlert Pydantic model defaults."""
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
alert = SystemDefinedAlert(display_name="Test rule")
assert alert.state == "INACTIVE"
assert alert.severity is None
assert alert.email_notifications_enabled is False
assert alert.all_super_admins is False
@@ -0,0 +1,183 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
RULE_NAME = "User suspended due to suspicious activity"
class TestRulesSuspiciousActivitySuspensionAlertConfigured:
def test_pass_fully_configured(self):
"""Test PASS when alert is ON, email notifications ON, recipients = all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured import (
rules_suspicious_activity_suspension_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=True,
)
]
check = rules_suspicious_activity_suspension_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "properly configured" in findings[0].status_extended
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_alert_off(self):
"""Test FAIL when alert is OFF (INACTIVE state)."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured import (
rules_suspicious_activity_suspension_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="INACTIVE",
)
]
check = rules_suspicious_activity_suspension_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "alert is OFF" in findings[0].status_extended
def test_fail_no_email_notifications(self):
"""Test FAIL when alert is ON but email notifications are disabled."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured import (
rules_suspicious_activity_suspension_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=False,
all_super_admins=False,
)
]
check = rules_suspicious_activity_suspension_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "email notifications are disabled" in findings[0].status_extended
def test_fail_recipients_not_all_super_admins(self):
"""Test FAIL when email notifications ON but recipients do not include all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured import (
rules_suspicious_activity_suspension_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=False,
)
]
check = rules_suspicious_activity_suspension_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert (
"do not include all super administrators" in findings[0].status_extended
)
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_activity_suspension_alert_configured.rules_suspicious_activity_suspension_alert_configured import (
rules_suspicious_activity_suspension_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = False
mock_rules_client.system_defined_alerts = []
check = rules_suspicious_activity_suspension_alert_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,183 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
RULE_NAME = "Suspicious login"
class TestRulesSuspiciousLoginAlertConfigured:
def test_pass_fully_configured(self):
"""Test PASS when alert is ON, email notifications ON, recipients = all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured import (
rules_suspicious_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=True,
)
]
check = rules_suspicious_login_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "properly configured" in findings[0].status_extended
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_alert_off(self):
"""Test FAIL when alert is OFF (INACTIVE state)."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured import (
rules_suspicious_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="INACTIVE",
)
]
check = rules_suspicious_login_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "alert is OFF" in findings[0].status_extended
def test_fail_no_email_notifications(self):
"""Test FAIL when alert is ON but email notifications are disabled."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured import (
rules_suspicious_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=False,
all_super_admins=False,
)
]
check = rules_suspicious_login_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "email notifications are disabled" in findings[0].status_extended
def test_fail_recipients_not_all_super_admins(self):
"""Test FAIL when email notifications ON but recipients do not include all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured import (
rules_suspicious_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=False,
)
]
check = rules_suspicious_login_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert (
"do not include all super administrators" in findings[0].status_extended
)
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_login_alert_configured.rules_suspicious_login_alert_configured import (
rules_suspicious_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = False
mock_rules_client.system_defined_alerts = []
check = rules_suspicious_login_alert_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,183 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.rules.rules_service import (
SystemDefinedAlert,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
RULE_NAME = "Suspicious programmatic login"
class TestRulesSuspiciousProgrammaticLoginAlertConfigured:
def test_pass_fully_configured(self):
"""Test PASS when alert is ON, email notifications ON, recipients = all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured import (
rules_suspicious_programmatic_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=True,
)
]
check = rules_suspicious_programmatic_login_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "properly configured" in findings[0].status_extended
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_alert_off(self):
"""Test FAIL when alert is OFF (INACTIVE state)."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured import (
rules_suspicious_programmatic_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="INACTIVE",
)
]
check = rules_suspicious_programmatic_login_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "alert is OFF" in findings[0].status_extended
def test_fail_no_email_notifications(self):
"""Test FAIL when alert is ON but email notifications are disabled."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured import (
rules_suspicious_programmatic_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=False,
all_super_admins=False,
)
]
check = rules_suspicious_programmatic_login_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "email notifications are disabled" in findings[0].status_extended
def test_fail_recipients_not_all_super_admins(self):
"""Test FAIL when email notifications ON but recipients do not include all super admins."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured import (
rules_suspicious_programmatic_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = True
mock_rules_client.system_defined_alerts = [
SystemDefinedAlert(
display_name=RULE_NAME,
state="ACTIVE",
severity="MEDIUM",
email_notifications_enabled=True,
all_super_admins=False,
)
]
check = rules_suspicious_programmatic_login_alert_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert (
"do not include all super administrators" in findings[0].status_extended
)
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed."""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured.rules_client"
) as mock_rules_client,
):
from prowler.providers.googleworkspace.services.rules.rules_suspicious_programmatic_login_alert_configured.rules_suspicious_programmatic_login_alert_configured import (
rules_suspicious_programmatic_login_alert_configured,
)
mock_rules_client.provider = mock_provider
mock_rules_client.policies_fetched = False
mock_rules_client.system_defined_alerts = []
check = rules_suspicious_programmatic_login_alert_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,499 @@
from unittest.mock import MagicMock, patch
from tests.providers.googleworkspace.googleworkspace_fixtures import (
set_mocked_googleworkspace_provider,
)
class TestSecurityService:
def test_fetch_policies_all_security_settings(self):
"""Test fetching security policies from Cloud Identity API"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_credentials = MagicMock()
mock_session = MagicMock()
mock_session.credentials = mock_credentials
mock_provider.session = mock_session
mock_service = MagicMock()
# First call: security.* settings
mock_security_list = MagicMock()
mock_security_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/security.two_step_verification_enrollment",
"value": {
"allowEnrollment": True,
},
}
},
{
"setting": {
"type": "settings/security.two_step_verification_enforcement",
"value": {
"enforcedFrom": "2026-05-25T15:27:52.352Z",
},
}
},
{
"setting": {
"type": "settings/security.two_step_verification_enforcement_factor",
"value": {
"allowedSignInFactorSet": "ALL",
},
}
},
{
"setting": {
"type": "settings/security.two_step_verification_device_trust",
"value": {
"allowTrustingDevice": True,
},
}
},
{
"setting": {
"type": "settings/security.two_step_verification_grace_period",
"value": {
"enrollmentGracePeriod": "0s",
},
}
},
{
"setting": {
"type": "settings/security.two_step_verification_sign_in_code",
"value": {
"backupCodeExceptionPeriod": "86400s",
},
}
},
{
"setting": {
"type": "settings/security.super_admin_account_recovery",
"value": {
"enableAccountRecovery": True,
},
}
},
{
"setting": {
"type": "settings/security.user_account_recovery",
"value": {
"enableAccountRecovery": False,
},
}
},
{
"setting": {
"type": "settings/security.password",
"value": {
"allowedStrength": "STRONG",
"minimumLength": 8,
"maximumLength": 100,
"enforceRequirementsAtLogin": False,
"allowReuse": False,
"expirationDuration": "0s",
},
}
},
{
"setting": {
"type": "settings/security.session_controls",
"value": {
"webSessionDuration": "1209600s",
},
}
},
{
"setting": {
"type": "settings/security.less_secure_apps",
"value": {
"allowLessSecureApps": False,
},
}
},
{
"setting": {
"type": "settings/security.advanced_protection_program",
"value": {
"enableAdvancedProtectionSelfEnrollment": True,
"securityCodeOption": "CODES_NOT_ALLOWED",
},
}
},
{
"setting": {
"type": "settings/security.login_challenges",
"value": {
"enableEmployeeIdChallenge": False,
},
}
},
{
"setting": {
"type": "settings/security.passkeys_restriction",
"value": {
"allowedPasskeysType": "ANY_DEVICE_OR_PLATFORM",
},
}
},
]
}
# Second call: api_controls.* settings
mock_api_controls_list = MagicMock()
mock_api_controls_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/api_controls.internal_apps",
"value": {
"trustInternalApps": True,
},
}
},
{
"setting": {
"type": "settings/api_controls.google_services",
"value": {
"services": [
{"scopesGroup": "DRIVE_ALL", "isEnabled": True},
{"scopesGroup": "GMAIL_HIGH_RISK", "isEnabled": False},
],
},
}
},
]
}
# Third call: rule.dlp
mock_dlp_list = MagicMock()
mock_dlp_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/rule.dlp",
"value": {
"displayName": "PII Detection",
"triggers": ["google.workspace.drive.file.v1.share"],
"state": "ACTIVE",
},
}
},
]
}
mock_service.policies().list.side_effect = [
mock_security_list,
mock_api_controls_list,
mock_dlp_list,
]
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.security.security_service import (
Security,
)
security = Security(mock_provider)
assert security.policies_fetched is True
assert security.policies.two_sv_allow_enrollment is True
assert security.policies.two_sv_enforced_from == "2026-05-25T15:27:52.352Z"
assert security.policies.two_sv_allowed_factor_set == "ALL"
assert security.policies.two_sv_allow_trusting_device is True
assert security.policies.two_sv_enrollment_grace_period == "0s"
assert security.policies.two_sv_backup_code_exception_period == "86400s"
assert security.policies.super_admin_recovery_enabled is True
assert security.policies.user_recovery_enabled is False
assert security.policies.password_minimum_length == 8
assert security.policies.password_allowed_strength == "STRONG"
assert security.policies.password_allow_reuse is False
assert security.policies.password_enforce_at_login is False
assert security.policies.password_expiration_duration == "0s"
assert security.policies.web_session_duration == "1209600s"
assert security.policies.less_secure_apps_allowed is False
assert security.policies.advanced_protection_enrollment is True
assert (
security.policies.advanced_protection_security_code_option
== "CODES_NOT_ALLOWED"
)
assert security.policies.login_challenge_employee_id is False
assert security.policies.passkeys_type == "ANY_DEVICE_OR_PLATFORM"
assert security.policies.trust_internal_apps is True
assert security.policies.google_services_restricted is True
assert security.policies.dlp_drive_rules_exist is True
def test_fetch_policies_empty_response(self):
"""Test handling empty policies response across all namespaces"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_empty = MagicMock()
mock_empty.execute.return_value = {"policies": []}
mock_service.policies().list.side_effect = [
mock_empty,
mock_empty,
mock_empty,
]
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.security.security_service import (
Security,
)
security = Security(mock_provider)
assert security.policies_fetched is True
assert security.policies.two_sv_enforced_from is None
assert security.policies.super_admin_recovery_enabled is None
assert security.policies.password_minimum_length is None
assert security.policies.web_session_duration is None
assert security.policies.less_secure_apps_allowed is None
assert security.policies.trust_internal_apps is None
assert security.policies.dlp_drive_rules_exist is None
def test_fetch_policies_api_error(self):
"""Test handling of API errors during policy fetch"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_service.policies().list.side_effect = Exception("API Error")
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.security.security_service import (
Security,
)
security = Security(mock_provider)
assert security.policies_fetched is False
def test_fetch_policies_build_service_returns_none(self):
"""Test early return when _build_service fails"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_service.GoogleWorkspaceService._build_service",
return_value=None,
),
):
from prowler.providers.googleworkspace.services.security.security_service import (
Security,
)
security = Security(mock_provider)
assert security.policies_fetched is False
def test_fetch_policies_execute_raises(self):
"""Test inner except handler when request.execute() raises during pagination"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_request = MagicMock()
mock_request.execute.side_effect = Exception("Execute failed")
mock_service.policies().list.return_value = mock_request
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.security.security_service import (
Security,
)
security = Security(mock_provider)
assert security.policies_fetched is False
def test_fetch_policies_google_services_no_restricted(self):
"""Test google_services with all services enabled sets restricted to False"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_empty = MagicMock()
mock_empty.execute.return_value = {"policies": []}
mock_api_list = MagicMock()
mock_api_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/api_controls.google_services",
"value": {
"services": [
{"scopesGroup": "DRIVE_ALL", "isEnabled": True},
{"scopesGroup": "GMAIL_ALL", "isEnabled": True},
],
},
}
},
]
}
mock_service.policies().list.side_effect = [
mock_empty,
mock_api_list,
mock_empty,
]
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.security.security_service import (
Security,
)
security = Security(mock_provider)
assert security.policies_fetched is True
assert security.policies.google_services_restricted is False
def test_dlp_rule_without_drive_trigger_ignored(self):
"""Test that DLP rules without Drive triggers don't set dlp_drive_rules_exist"""
mock_provider = set_mocked_googleworkspace_provider()
mock_provider.audit_config = {}
mock_provider.fixer_config = {}
mock_session = MagicMock()
mock_session.credentials = MagicMock()
mock_provider.session = mock_session
mock_service = MagicMock()
mock_empty = MagicMock()
mock_empty.execute.return_value = {"policies": []}
mock_dlp_list = MagicMock()
mock_dlp_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/rule.dlp",
"value": {
"displayName": "Gmail Only Rule",
"triggers": ["google.workspace.gmail.email.v1.send"],
"state": "ACTIVE",
},
}
},
]
}
mock_service.policies().list.side_effect = [
mock_empty,
mock_empty,
mock_dlp_list,
]
mock_service.policies().list_next.return_value = None
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.security.security_service import (
Security,
)
security = Security(mock_provider)
assert security.policies_fetched is True
assert security.policies.dlp_drive_rules_exist is None
def test_security_policies_model(self):
"""Test SecurityPolicies Pydantic model"""
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
policies = SecurityPolicies(
two_sv_enforced_from="2026-05-25T15:27:52.352Z",
super_admin_recovery_enabled=False,
password_minimum_length=14,
web_session_duration="43200s",
)
assert policies.two_sv_enforced_from == "2026-05-25T15:27:52.352Z"
assert policies.super_admin_recovery_enabled is False
assert policies.password_minimum_length == 14
assert policies.web_session_duration == "43200s"
@@ -0,0 +1,156 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurity2svEnforced:
def test_pass_2sv_enforced(self):
"""Test PASS when 2-Step Verification enforcement is active"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced import (
security_2sv_enforced,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
two_sv_enforced_from="2026-05-25T15:27:52.352Z"
)
check = security_2sv_enforced()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "active" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_none_not_configured(self):
"""Test FAIL when 2-Step Verification enforcement is not configured (None)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced import (
security_2sv_enforced,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(two_sv_enforced_from=None)
check = security_2sv_enforced()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not configured" in findings[0].status_extended
def test_fail_empty_off(self):
"""Test FAIL when 2-Step Verification enforcement is set to OFF (empty string)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced import (
security_2sv_enforced,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(two_sv_enforced_from="")
check = security_2sv_enforced()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "OFF" in findings[0].status_extended
def test_fail_epoch_enforcement_off(self):
"""Test FAIL when API returns epoch zero timestamp (enforcement OFF)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced import (
security_2sv_enforced,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
two_sv_enforced_from="1970-01-01T00:00:00Z"
)
check = security_2sv_enforced()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "OFF" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_enforced.security_2sv_enforced import (
security_2sv_enforced,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_2sv_enforced()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,126 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurity2svHardwareKeysAdmins:
def test_pass_passkey_only(self):
"""Test PASS when 2SV enforcement requires security keys only"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_hardware_keys_admins.security_2sv_hardware_keys_admins.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_hardware_keys_admins.security_2sv_hardware_keys_admins import (
security_2sv_hardware_keys_admins,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
two_sv_allowed_factor_set="PASSKEY_ONLY"
)
check = security_2sv_hardware_keys_admins()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "security keys only" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_all_methods_allowed(self):
"""Test FAIL when 2SV enforcement allows ALL methods"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_hardware_keys_admins.security_2sv_hardware_keys_admins.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_hardware_keys_admins.security_2sv_hardware_keys_admins import (
security_2sv_hardware_keys_admins,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(two_sv_allowed_factor_set="ALL")
check = security_2sv_hardware_keys_admins()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "ALL" in findings[0].status_extended
def test_fail_none_not_configured(self):
"""Test FAIL when 2SV enforcement factor is not configured (None)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_hardware_keys_admins.security_2sv_hardware_keys_admins.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_hardware_keys_admins.security_2sv_hardware_keys_admins import (
security_2sv_hardware_keys_admins,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(two_sv_allowed_factor_set=None)
check = security_2sv_hardware_keys_admins()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not configured" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_2sv_hardware_keys_admins.security_2sv_hardware_keys_admins.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_2sv_hardware_keys_admins.security_2sv_hardware_keys_admins import (
security_2sv_hardware_keys_admins,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_2sv_hardware_keys_admins()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,163 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurityAdvancedProtectionConfigured:
def test_pass_properly_configured(self):
"""Test PASS when Advanced Protection is configured with enrollment and codes blocked"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured import (
security_advanced_protection_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
advanced_protection_enrollment=True,
advanced_protection_security_code_option="CODES_NOT_ALLOWED",
)
check = security_advanced_protection_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "configured" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_codes_allowed(self):
"""Test FAIL when enrollment is enabled but security codes are allowed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured import (
security_advanced_protection_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
advanced_protection_enrollment=True,
advanced_protection_security_code_option="ALLOWED_WITHOUT_REMOTE_ACCESS",
)
check = security_advanced_protection_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not properly configured" in findings[0].status_extended
def test_fail_enrollment_disabled(self):
"""Test FAIL when enrollment is disabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured import (
security_advanced_protection_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
advanced_protection_enrollment=False,
)
check = security_advanced_protection_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not properly configured" in findings[0].status_extended
def test_fail_enrollment_unset(self):
"""Test FAIL when enrollment is None (not configured)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured import (
security_advanced_protection_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
advanced_protection_enrollment=None,
advanced_protection_security_code_option="CODES_NOT_ALLOWED",
)
check = security_advanced_protection_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "enrollment is not configured" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_advanced_protection_configured.security_advanced_protection_configured import (
security_advanced_protection_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_advanced_protection_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,124 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurityAppAccessRestricted:
def test_pass_access_restricted(self):
"""Test PASS when application access to Google services is restricted"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_app_access_restricted.security_app_access_restricted.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_app_access_restricted.security_app_access_restricted import (
security_app_access_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(google_services_restricted=True)
check = security_app_access_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "restricted" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_access_unrestricted(self):
"""Test FAIL when application access to Google services is unrestricted"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_app_access_restricted.security_app_access_restricted.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_app_access_restricted.security_app_access_restricted import (
security_app_access_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(google_services_restricted=False)
check = security_app_access_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "unrestricted" in findings[0].status_extended
def test_fail_none_not_configured(self):
"""Test FAIL when application access is not configured (None)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_app_access_restricted.security_app_access_restricted.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_app_access_restricted.security_app_access_restricted import (
security_app_access_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(google_services_restricted=None)
check = security_app_access_restricted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not configured" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_app_access_restricted.security_app_access_restricted.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_app_access_restricted.security_app_access_restricted import (
security_app_access_restricted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_app_access_restricted()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,124 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurityDlpDriveRulesConfigured:
def test_pass_dlp_rules_configured(self):
"""Test PASS when DLP policies for Google Drive are configured"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_dlp_drive_rules_configured.security_dlp_drive_rules_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_dlp_drive_rules_configured.security_dlp_drive_rules_configured import (
security_dlp_drive_rules_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(dlp_drive_rules_exist=True)
check = security_dlp_drive_rules_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "configured" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_no_dlp_rules(self):
"""Test FAIL when no DLP policies for Google Drive are configured"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_dlp_drive_rules_configured.security_dlp_drive_rules_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_dlp_drive_rules_configured.security_dlp_drive_rules_configured import (
security_dlp_drive_rules_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(dlp_drive_rules_exist=False)
check = security_dlp_drive_rules_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "No active" in findings[0].status_extended
def test_fail_none_no_dlp_rules(self):
"""Test FAIL when DLP rules existence is None (no active rules)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_dlp_drive_rules_configured.security_dlp_drive_rules_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_dlp_drive_rules_configured.security_dlp_drive_rules_configured import (
security_dlp_drive_rules_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(dlp_drive_rules_exist=None)
check = security_dlp_drive_rules_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "No active" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_dlp_drive_rules_configured.security_dlp_drive_rules_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_dlp_drive_rules_configured.security_dlp_drive_rules_configured import (
security_dlp_drive_rules_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_dlp_drive_rules_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,127 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurityInternalAppsTrusted:
def test_pass_internal_apps_trusted(self):
"""Test PASS when internal domain-owned apps are trusted"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_internal_apps_trusted.security_internal_apps_trusted.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_internal_apps_trusted.security_internal_apps_trusted import (
security_internal_apps_trusted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(trust_internal_apps=True)
check = security_internal_apps_trusted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "trusted" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_pass_none_secure_default(self):
"""Test PASS when internal apps trust is None (secure default)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_internal_apps_trusted.security_internal_apps_trusted.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_internal_apps_trusted.security_internal_apps_trusted import (
security_internal_apps_trusted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(trust_internal_apps=None)
check = security_internal_apps_trusted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "secure default" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_internal_apps_not_trusted(self):
"""Test FAIL when internal domain-owned apps are not trusted"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_internal_apps_trusted.security_internal_apps_trusted.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_internal_apps_trusted.security_internal_apps_trusted import (
security_internal_apps_trusted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(trust_internal_apps=False)
check = security_internal_apps_trusted()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not trusted" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_internal_apps_trusted.security_internal_apps_trusted.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_internal_apps_trusted.security_internal_apps_trusted import (
security_internal_apps_trusted,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_internal_apps_trusted()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,127 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurityLessSecureAppsDisabled:
def test_pass_less_secure_apps_disabled(self):
"""Test PASS when less secure app access is explicitly disabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_less_secure_apps_disabled.security_less_secure_apps_disabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_less_secure_apps_disabled.security_less_secure_apps_disabled import (
security_less_secure_apps_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(less_secure_apps_allowed=False)
check = security_less_secure_apps_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_pass_none_secure_default(self):
"""Test PASS when less secure app access is None (secure default)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_less_secure_apps_disabled.security_less_secure_apps_disabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_less_secure_apps_disabled.security_less_secure_apps_disabled import (
security_less_secure_apps_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(less_secure_apps_allowed=None)
check = security_less_secure_apps_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "secure default" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_less_secure_apps_enabled(self):
"""Test FAIL when less secure app access is enabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_less_secure_apps_disabled.security_less_secure_apps_disabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_less_secure_apps_disabled.security_less_secure_apps_disabled import (
security_less_secure_apps_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(less_secure_apps_allowed=True)
check = security_less_secure_apps_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "enabled" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_less_secure_apps_disabled.security_less_secure_apps_disabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_less_secure_apps_disabled.security_less_secure_apps_disabled import (
security_less_secure_apps_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_less_secure_apps_disabled()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,127 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurityLoginChallengesConfigured:
def test_pass_employee_id_challenge_disabled(self):
"""Test PASS when employee ID login challenge is disabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_login_challenges_configured.security_login_challenges_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_login_challenges_configured.security_login_challenges_configured import (
security_login_challenges_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(login_challenge_employee_id=False)
check = security_login_challenges_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_pass_none_secure_default(self):
"""Test PASS when employee ID login challenge is None (secure default)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_login_challenges_configured.security_login_challenges_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_login_challenges_configured.security_login_challenges_configured import (
security_login_challenges_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(login_challenge_employee_id=None)
check = security_login_challenges_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "secure default" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_employee_id_challenge_enabled(self):
"""Test FAIL when employee ID login challenge is enabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_login_challenges_configured.security_login_challenges_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_login_challenges_configured.security_login_challenges_configured import (
security_login_challenges_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(login_challenge_employee_id=True)
check = security_login_challenges_configured()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "enabled" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_login_challenges_configured.security_login_challenges_configured.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_login_challenges_configured.security_login_challenges_configured import (
security_login_challenges_configured,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_login_challenges_configured()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,210 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurityPasswordPolicyStrong:
def test_pass_strong_password_policy(self):
"""Test PASS when password policy meets CIS requirements"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong import (
security_password_policy_strong,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
password_minimum_length=14,
password_allowed_strength="STRONG",
password_allow_reuse=False,
password_enforce_at_login=True,
password_expiration_duration="31536000s",
)
check = security_password_policy_strong()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "meets CIS requirements" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_weak_password_policy(self):
"""Test FAIL when password policy does not meet CIS requirements"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong import (
security_password_policy_strong,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
password_minimum_length=8,
password_allowed_strength="STRONG",
password_allow_reuse=False,
password_enforce_at_login=False,
password_expiration_duration="0s",
)
check = security_password_policy_strong()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "does not meet" in findings[0].status_extended
def test_fail_none_all_defaults(self):
"""Test FAIL when all password policy fields are None (defaults)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong import (
security_password_policy_strong,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
password_minimum_length=None,
password_allowed_strength=None,
password_allow_reuse=None,
password_enforce_at_login=None,
password_expiration_duration=None,
)
check = security_password_policy_strong()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "does not meet" in findings[0].status_extended
def test_fail_strength_unset_treated_as_missing(self):
"""Test FAIL when password_allowed_strength is None even with other fields strong"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong import (
security_password_policy_strong,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
password_minimum_length=14,
password_allowed_strength=None,
password_allow_reuse=False,
password_enforce_at_login=True,
password_expiration_duration="31536000s",
)
check = security_password_policy_strong()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "password strength is not configured" in findings[0].status_extended
def test_fail_min_length_unset_reports_not_configured(self):
"""Test FAIL message uses 'not configured' when password_minimum_length is None"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong import (
security_password_policy_strong,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(
password_minimum_length=None,
password_allowed_strength="STRONG",
password_allow_reuse=False,
password_enforce_at_login=True,
password_expiration_duration="31536000s",
)
check = security_password_policy_strong()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "minimum length is not configured" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_password_policy_strong.security_password_policy_strong import (
security_password_policy_strong,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_password_policy_strong()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,152 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecuritySessionDurationLimited:
def test_pass_session_12_hours(self):
"""Test PASS when session duration is set to 12 hours"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited import (
security_session_duration_limited,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(web_session_duration="43200s")
check = security_session_duration_limited()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "12 hours" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_long_session_duration(self):
"""Test FAIL when session duration is too long (336 hours)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited import (
security_session_duration_limited,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(web_session_duration="1209600s")
check = security_session_duration_limited()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "336 hours" in findings[0].status_extended
def test_fail_none_not_configured(self):
"""Test FAIL when session duration is not explicitly configured (None)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited import (
security_session_duration_limited,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(web_session_duration=None)
check = security_session_duration_limited()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not explicitly configured" in findings[0].status_extended
def test_fail_unparseable_duration(self):
"""Test FAIL when session duration has an unexpected format instead of crashing"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited import (
security_session_duration_limited,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(web_session_duration="invalid")
check = security_session_duration_limited()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not parseable" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_session_duration_limited.security_session_duration_limited import (
security_session_duration_limited,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_session_duration_limited()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,127 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecuritySuperAdminRecoveryDisabled:
def test_pass_recovery_disabled(self):
"""Test PASS when Super Admin account recovery is explicitly disabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_super_admin_recovery_disabled.security_super_admin_recovery_disabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_super_admin_recovery_disabled.security_super_admin_recovery_disabled import (
security_super_admin_recovery_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(super_admin_recovery_enabled=False)
check = security_super_admin_recovery_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disabled" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_pass_none_secure_default(self):
"""Test PASS when Super Admin account recovery is None (secure default)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_super_admin_recovery_disabled.security_super_admin_recovery_disabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_super_admin_recovery_disabled.security_super_admin_recovery_disabled import (
security_super_admin_recovery_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(super_admin_recovery_enabled=None)
check = security_super_admin_recovery_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "secure default" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_recovery_enabled(self):
"""Test FAIL when Super Admin account recovery is enabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_super_admin_recovery_disabled.security_super_admin_recovery_disabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_super_admin_recovery_disabled.security_super_admin_recovery_disabled import (
security_super_admin_recovery_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(super_admin_recovery_enabled=True)
check = security_super_admin_recovery_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "enabled" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_super_admin_recovery_disabled.security_super_admin_recovery_disabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_super_admin_recovery_disabled.security_super_admin_recovery_disabled import (
security_super_admin_recovery_disabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_super_admin_recovery_disabled()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,124 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.security.security_service import (
SecurityPolicies,
)
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
set_mocked_googleworkspace_provider,
)
class TestSecurityUserRecoveryEnabled:
def test_pass_recovery_enabled(self):
"""Test PASS when user account recovery is enabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_user_recovery_enabled.security_user_recovery_enabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_user_recovery_enabled.security_user_recovery_enabled import (
security_user_recovery_enabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(user_recovery_enabled=True)
check = security_user_recovery_enabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "enabled" in findings[0].status_extended
assert findings[0].resource_name == "Security Policies"
assert findings[0].resource_id == "securityPolicies"
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_recovery_disabled(self):
"""Test FAIL when user account recovery is disabled"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_user_recovery_enabled.security_user_recovery_enabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_user_recovery_enabled.security_user_recovery_enabled import (
security_user_recovery_enabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(user_recovery_enabled=False)
check = security_user_recovery_enabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "disabled" in findings[0].status_extended
def test_fail_none_not_configured(self):
"""Test FAIL when user account recovery is None (not explicitly configured)"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_user_recovery_enabled.security_user_recovery_enabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_user_recovery_enabled.security_user_recovery_enabled import (
security_user_recovery_enabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = True
mock_client.policies = SecurityPolicies(user_recovery_enabled=None)
check = security_user_recovery_enabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "not explicitly configured" in findings[0].status_extended
def test_no_findings_when_fetch_failed(self):
"""Test no findings returned when the API fetch failed"""
mock_provider = set_mocked_googleworkspace_provider()
with (
patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=mock_provider,
),
patch(
"prowler.providers.googleworkspace.services.security.security_user_recovery_enabled.security_user_recovery_enabled.security_client"
) as mock_client,
):
from prowler.providers.googleworkspace.services.security.security_user_recovery_enabled.security_user_recovery_enabled import (
security_user_recovery_enabled,
)
mock_client.provider = mock_provider
mock_client.policies_fetched = False
mock_client.policies = SecurityPolicies()
check = security_user_recovery_enabled()
findings = check.execute()
assert len(findings) == 0
@@ -107,7 +107,9 @@ class Test_entra_managed_device_required_for_authentication:
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
application_enforced_restrictions=ApplicationEnforcedRestrictions(is_enabled=False),
application_enforced_restrictions=ApplicationEnforcedRestrictions(
is_enabled=False
),
),
state=ConditionalAccessPolicyState.DISABLED,
)
@@ -186,7 +188,9 @@ class Test_entra_managed_device_required_for_authentication:
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
application_enforced_restrictions=ApplicationEnforcedRestrictions(is_enabled=False),
application_enforced_restrictions=ApplicationEnforcedRestrictions(
is_enabled=False
),
),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
)
@@ -269,7 +273,9 @@ class Test_entra_managed_device_required_for_authentication:
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
application_enforced_restrictions=ApplicationEnforcedRestrictions(is_enabled=False),
application_enforced_restrictions=ApplicationEnforcedRestrictions(
is_enabled=False
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
@@ -0,0 +1,68 @@
import argparse
import pytest
from prowler.providers.stackit.lib.arguments.arguments import init_parser
@pytest.fixture
def parser():
parser = argparse.ArgumentParser()
parser.common_providers_parser = argparse.ArgumentParser(add_help=False)
parser.subparsers = parser.add_subparsers(dest="provider")
init_parser(parser)
return parser
class TestStackITArguments:
def test_project_id_argument_is_registered(self, parser):
args = parser.parse_args(
[
"stackit",
"--stackit-project-id",
"12345678-1234-1234-1234-123456789abc",
]
)
assert args.stackit_project_id == "12345678-1234-1234-1234-123456789abc"
def test_api_token_argument_is_not_registered(self, parser):
"""Tokens were removed in favour of the service account key file."""
with pytest.raises(SystemExit):
parser.parse_args(["stackit", "--stackit-api-token", "secret-token"])
def test_service_account_key_path_argument_is_registered(self, parser):
args = parser.parse_args(
[
"stackit",
"--stackit-service-account-key-path",
"/tmp/sa-key.json",
]
)
assert args.stackit_service_account_key_path == "/tmp/sa-key.json"
def test_service_account_key_path_defaults_to_none(self, parser):
args = parser.parse_args(["stackit"])
assert args.stackit_service_account_key_path is None
def test_service_account_key_argument_is_registered(self, parser):
args = parser.parse_args(
[
"stackit",
"--stackit-service-account-key",
'{"keyId": "abc"}',
]
)
assert args.stackit_service_account_key == '{"keyId": "abc"}'
def test_service_account_key_defaults_to_none(self, parser):
args = parser.parse_args(["stackit"])
assert args.stackit_service_account_key is None
def test_scan_unused_services_defaults_to_false(self, parser):
args = parser.parse_args(["stackit"])
assert args.scan_unused_services is False
def test_scan_unused_services_flag_sets_true(self, parser):
args = parser.parse_args(["stackit", "--scan-unused-services"])
assert args.scan_unused_services is True
@@ -0,0 +1,33 @@
from unittest.mock import MagicMock
from prowler.providers.stackit.lib.mutelist.mutelist import StackITMutelist
class TestStackITMutelist:
def test_is_finding_muted_uses_project_id_as_account(self):
mutelist_content = {
"Accounts": {
"project_1": {
"Checks": {
"check_test": {
"Regions": ["*"],
"Resources": ["test_resource"],
}
}
}
}
}
mutelist = StackITMutelist(mutelist_content=mutelist_content)
finding = MagicMock()
finding.project_id = "project_1"
finding.resource_id = "resource_1"
finding.check_metadata = MagicMock()
finding.check_metadata.CheckID = "check_test"
finding.status = "FAIL"
finding.resource_name = "test_resource"
finding.location = "eu01"
finding.resource_tags = {}
assert mutelist.is_finding_muted(finding=finding)
@@ -0,0 +1,504 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.stackit.services.iaas.iaas_service import (
SecurityGroup,
SecurityGroupRule,
)
from tests.providers.stackit.stackit_fixtures import (
STACKIT_PROJECT_ID,
set_mocked_stackit_provider,
)
class Test_iaas_security_group_all_traffic_unrestricted:
def setup_method(self):
mock.MagicMock.scan_unused_services = False
def test_no_security_groups(self):
"""Test with no security groups - should return empty results."""
iaas_client = mock.MagicMock
iaas_client.security_groups = []
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 0
def test_security_group_not_in_use(self):
"""Test security group not in use - should be skipped."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=None,
port_range_max=None,
)
],
in_use=False,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 0
def test_security_group_no_rules(self):
"""Test security group with no rules - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Security group {security_group_name} does not allow unrestricted access to all traffic."
)
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
assert result[0].location == "eu01"
def test_security_group_all_ports_none_range(self):
"""Test security group with None port range (all ports) - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=None,
port_range_max=None,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
"allows unrestricted access to all traffic" in result[0].status_extended
)
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
assert result[0].location == "eu01"
def test_security_group_all_ports_full_range(self):
"""Test security group with full port range 1-65535 - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=1,
port_range_max=65535,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
"allows unrestricted access to all traffic" in result[0].status_extended
)
def test_security_group_all_ports_zero_range(self):
"""Test security group with port range 0-65535 - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=0,
port_range_max=65535,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_security_group_limited_port_range(self):
"""Test security group with limited port range - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=80,
port_range_max=443,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
"does not allow unrestricted access to all traffic"
in result[0].status_extended
)
def test_security_group_restricted_ip_all_ports(self):
"""Test security group with all ports but restricted IP - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="10.0.0.0/8", # Restricted IP
port_range_min=None,
port_range_max=None,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_security_group_egress_all_ports(self):
"""Test security group with egress rule for all ports - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="egress", # Egress, not ingress
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=None,
port_range_max=None,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_security_group_multiple_unrestricted_rules(self):
"""Test security group with multiple unrestricted rules - should FAIL with all listed."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=None,
port_range_max=None,
),
SecurityGroupRule(
id="rule-2",
direction="ingress",
protocol="udp",
ip_range="0.0.0.0/0",
port_range_min=1,
port_range_max=65535,
),
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_all_traffic_unrestricted.iaas_security_group_all_traffic_unrestricted import (
iaas_security_group_all_traffic_unrestricted,
)
check = iaas_security_group_all_traffic_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "rule-1" in result[0].status_extended
assert "rule-2" in result[0].status_extended
@@ -0,0 +1,503 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.stackit.services.iaas.iaas_service import (
SecurityGroup,
SecurityGroupRule,
)
from tests.providers.stackit.stackit_fixtures import (
STACKIT_PROJECT_ID,
set_mocked_stackit_provider,
)
class Test_iaas_security_group_database_unrestricted:
def setup_method(self):
mock.MagicMock.scan_unused_services = False
def test_no_security_groups(self):
"""Test with no security groups - should return empty results."""
iaas_client = mock.MagicMock
iaas_client.security_groups = []
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 0
def test_security_group_not_in_use(self):
"""Test security group not in use - should be skipped."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=3306,
port_range_max=3306,
)
],
in_use=False,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 0
def test_security_group_no_rules(self):
"""Test security group with no rules - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Security group {security_group_name} does not allow unrestricted database access."
)
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
assert result[0].location == "eu01"
def test_security_group_mysql_unrestricted(self):
"""Test security group with MySQL port 3306 unrestricted - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=3306,
port_range_max=3306,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "MySQL (port 3306)" in result[0].status_extended
assert "allows unrestricted database access" in result[0].status_extended
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
assert result[0].location == "eu01"
def test_security_group_postgresql_unrestricted(self):
"""Test security group with PostgreSQL port 5432 unrestricted - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=5432,
port_range_max=5432,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "PostgreSQL (port 5432)" in result[0].status_extended
def test_security_group_mongodb_unrestricted(self):
"""Test security group with MongoDB port 27017 unrestricted - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=27017,
port_range_max=27017,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "MongoDB (port 27017)" in result[0].status_extended
def test_security_group_multiple_databases_unrestricted(self):
"""Test security group with multiple database ports unrestricted - should FAIL with all listed."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=3306,
port_range_max=3306,
),
SecurityGroupRule(
id="rule-2",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=5432,
port_range_max=5432,
),
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "MySQL (port 3306)" in result[0].status_extended
assert "PostgreSQL (port 5432)" in result[0].status_extended
def test_security_group_database_port_range(self):
"""Test security group with port range including database port - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=3000,
port_range_max=4000,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "MySQL (port 3306)" in result[0].status_extended
def test_security_group_database_restricted_ip(self):
"""Test security group with database port restricted to specific IP - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="10.0.0.0/8",
port_range_min=3306,
port_range_max=3306,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
"does not allow unrestricted database access"
in result[0].status_extended
)
def test_security_group_non_database_port(self):
"""Test security group with unrestricted access but non-database port - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=80,
port_range_max=80,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_database_unrestricted.iaas_security_group_database_unrestricted import (
iaas_security_group_database_unrestricted,
)
check = iaas_security_group_database_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
@@ -0,0 +1,389 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.stackit.services.iaas.iaas_service import (
SecurityGroup,
SecurityGroupRule,
)
from tests.providers.stackit.stackit_fixtures import (
STACKIT_PROJECT_ID,
set_mocked_stackit_provider,
)
class Test_iaas_security_group_rdp_unrestricted:
def setup_method(self):
mock.MagicMock.scan_unused_services = False
def test_no_security_groups(self):
"""Test with no security groups - should return empty results."""
iaas_client = mock.MagicMock
iaas_client.security_groups = []
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_rdp_unrestricted.iaas_security_group_rdp_unrestricted import (
iaas_security_group_rdp_unrestricted,
)
check = iaas_security_group_rdp_unrestricted()
result = check.execute()
assert len(result) == 0
def test_security_group_not_in_use(self):
"""Test security group not in use - should be skipped."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=3389,
port_range_max=3389,
)
],
in_use=False,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_rdp_unrestricted.iaas_security_group_rdp_unrestricted import (
iaas_security_group_rdp_unrestricted,
)
check = iaas_security_group_rdp_unrestricted()
result = check.execute()
assert len(result) == 0
def test_security_group_no_rules(self):
"""Test security group with no rules - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_rdp_unrestricted.iaas_security_group_rdp_unrestricted import (
iaas_security_group_rdp_unrestricted,
)
check = iaas_security_group_rdp_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Security group {security_group_name} does not allow unrestricted RDP access."
)
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
assert result[0].location == "eu01"
def test_security_group_rdp_unrestricted_exact_port(self):
"""Test security group with RDP port 3389 unrestricted - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=3389,
port_range_max=3389,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_rdp_unrestricted.iaas_security_group_rdp_unrestricted import (
iaas_security_group_rdp_unrestricted,
)
check = iaas_security_group_rdp_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "allows unrestricted RDP access" in result[0].status_extended
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
assert result[0].location == "eu01"
def test_security_group_rdp_unrestricted_port_range(self):
"""Test security group with port range including RDP - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=3380,
port_range_max=3400,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_rdp_unrestricted.iaas_security_group_rdp_unrestricted import (
iaas_security_group_rdp_unrestricted,
)
check = iaas_security_group_rdp_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "allows unrestricted RDP access" in result[0].status_extended
def test_security_group_rdp_restricted_ip(self):
"""Test security group with RDP restricted to specific IP - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="10.0.0.0/8",
port_range_min=3389,
port_range_max=3389,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_rdp_unrestricted.iaas_security_group_rdp_unrestricted import (
iaas_security_group_rdp_unrestricted,
)
check = iaas_security_group_rdp_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "does not allow unrestricted RDP access" in result[0].status_extended
def test_security_group_different_port(self):
"""Test security group with unrestricted access but different port - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=80,
port_range_max=80,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_rdp_unrestricted.iaas_security_group_rdp_unrestricted import (
iaas_security_group_rdp_unrestricted,
)
check = iaas_security_group_rdp_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_security_group_none_ip_range(self):
"""Test security group with None ip_range (unrestricted) - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range=None,
port_range_min=3389,
port_range_max=3389,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_rdp_unrestricted.iaas_security_group_rdp_unrestricted import (
iaas_security_group_rdp_unrestricted,
)
check = iaas_security_group_rdp_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
@@ -0,0 +1,589 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.stackit.services.iaas.iaas_service import (
SecurityGroup,
SecurityGroupRule,
)
from tests.providers.stackit.stackit_fixtures import (
STACKIT_PROJECT_ID,
set_mocked_stackit_provider,
)
class Test_iaas_security_group_ssh_unrestricted:
def setup_method(self):
mock.MagicMock.scan_unused_services = False
def test_no_security_groups(self):
"""Test with no security groups - should return empty results."""
iaas_client = mock.MagicMock
iaas_client.security_groups = []
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 0
def test_security_group_not_in_use(self):
"""Test security group not in use - should be skipped."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
],
in_use=False, # Not in use - should be skipped
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 0
def test_security_group_no_rules(self):
"""Test security group with no rules - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Security group {security_group_name} does not allow unrestricted SSH access."
)
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
assert result[0].location == "eu01"
def test_security_group_ssh_unrestricted_exact_port(self):
"""Test security group with SSH port 22 unrestricted - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "allows unrestricted SSH access" in result[0].status_extended
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
assert result[0].location == "eu01"
def test_security_group_ssh_unrestricted_port_range(self):
"""Test security group with port range including SSH - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=20,
port_range_max=25,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "allows unrestricted SSH access" in result[0].status_extended
assert result[0].resource_id == security_group_id
assert result[0].resource_name == security_group_name
def test_security_group_ssh_restricted_ip(self):
"""Test security group with SSH restricted to specific IP - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="10.0.0.0/8",
port_range_min=22,
port_range_max=22,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "does not allow unrestricted SSH access" in result[0].status_extended
def test_security_group_different_port(self):
"""Test security group with unrestricted access but different port - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=80,
port_range_max=80,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_security_group_egress_rule(self):
"""Test security group with egress rule - should PASS."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="egress", # Egress, not ingress
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_security_group_none_protocol(self):
"""Test security group with None protocol (all protocols) - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol=None, # None means all protocols (includes TCP)
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_security_group_none_ip_range(self):
"""Test security group with None ip_range (unrestricted) - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range=None, # None means unrestricted access
port_range_min=22,
port_range_max=22,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_security_group_none_port_range(self):
"""Test security group with None port range (all ports) - should FAIL."""
iaas_client = mock.MagicMock
security_group_name = "test-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=None, # None means all ports
port_range_max=None,
)
],
in_use=True,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_security_group_not_in_use_with_scan_unused_services(self):
"""Unused SG with unrestricted SSH must FAIL when scan_unused_services=True."""
iaas_client = mock.MagicMock
iaas_client.scan_unused_services = True
security_group_name = "unused-security-group"
security_group_id = str(uuid4())
iaas_client.security_groups = [
SecurityGroup(
id=security_group_id,
name=security_group_name,
project_id=STACKIT_PROJECT_ID,
region="eu01",
rules=[
SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
],
in_use=False,
)
]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_stackit_provider(scan_unused_services=True),
),
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService",
new=iaas_client,
) as service_client,
mock.patch(
"prowler.providers.stackit.services.iaas.iaas_client.iaas_client",
new=service_client,
),
):
from prowler.providers.stackit.services.iaas.iaas_security_group_ssh_unrestricted.iaas_security_group_ssh_unrestricted import (
iaas_security_group_ssh_unrestricted,
)
check = iaas_security_group_ssh_unrestricted()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "allows unrestricted SSH access" in result[0].status_extended
@@ -0,0 +1,515 @@
from unittest.mock import MagicMock, patch
import pytest
from prowler.providers.stackit.exceptions.exceptions import StackITInvalidTokenError
from prowler.providers.stackit.services.iaas.iaas_service import (
IaaSService,
SecurityGroupRule,
)
from tests.providers.stackit.stackit_fixtures import (
STACKIT_PROJECT_ID,
set_mocked_stackit_provider,
)
def mock_iaas_fetch_all_regions(_):
"""Mock the _fetch_all_regions method to avoid real API calls."""
@patch(
"prowler.providers.stackit.services.iaas.iaas_service.IaaSService._fetch_all_regions",
new=mock_iaas_fetch_all_regions,
)
class Test_IaaS_Service:
def test_service_initialization(self):
"""Test that the IaaS service initializes correctly."""
iaas_service = IaaSService(set_mocked_stackit_provider())
assert iaas_service.project_id == STACKIT_PROJECT_ID
assert iaas_service.service_account_key_path is not None
assert isinstance(iaas_service.security_groups, list)
assert isinstance(iaas_service.server_nics, list)
assert isinstance(iaas_service.in_use_sg_ids, set)
assert iaas_service.scan_unused_services is False
def test_service_project_id(self):
"""Test that the service correctly extracts project_id from provider."""
iaas_service = IaaSService(set_mocked_stackit_provider())
assert iaas_service.project_id == STACKIT_PROJECT_ID
def test_service_service_account_key_path(self):
"""Test that the service correctly extracts the SA key path from provider."""
custom_path = "/tmp/custom-sa.json"
provider = set_mocked_stackit_provider(service_account_key_path=custom_path)
iaas_service = IaaSService(provider)
assert iaas_service.service_account_key_path == custom_path
def test_security_groups_list_structure(self):
"""Test that security_groups is properly initialized as a list."""
iaas_service = IaaSService(set_mocked_stackit_provider())
assert hasattr(iaas_service, "security_groups")
assert isinstance(iaas_service.security_groups, list)
def test_in_use_sg_ids_set_structure(self):
"""Test that in_use_sg_ids is properly initialized as a set."""
iaas_service = IaaSService(set_mocked_stackit_provider())
assert hasattr(iaas_service, "in_use_sg_ids")
assert isinstance(iaas_service.in_use_sg_ids, set)
@pytest.mark.parametrize(
"method_name,client_method_name",
[
("_list_server_nics", "list_project_nics"),
("_list_security_groups", "list_security_groups"),
("_list_security_group_rules", "list_security_group_rules"),
],
)
def test_list_methods_propagate_api_errors(self, method_name, client_method_name):
"""API/auth failures must fail the scan instead of returning empty data."""
iaas_service = IaaSService(set_mocked_stackit_provider())
client = MagicMock()
getattr(client, client_method_name).side_effect = StackITInvalidTokenError(
message="Invalid token"
)
with pytest.raises(StackITInvalidTokenError):
if method_name == "_list_security_group_rules":
getattr(iaas_service, method_name)(client, "eu01", "sg-1")
else:
getattr(iaas_service, method_name)(client, "eu01")
def test_security_group_parsing_errors_are_skipped_locally(self):
"""Malformed resources are skipped while valid resources are retained."""
class MalformedSecurityGroup:
@property
def id(self):
raise ValueError("malformed security group")
iaas_service = IaaSService(set_mocked_stackit_provider())
client = MagicMock()
client.list_security_groups.return_value = [
MalformedSecurityGroup(),
{"id": "sg-1", "name": "valid-sg"},
]
client.list_security_group_rules.return_value = []
iaas_service._list_security_groups(client, "eu01")
assert len(iaas_service.security_groups) == 1
assert iaas_service.security_groups[0].id == "sg-1"
def test_in_use_considers_all_nics_not_only_public(self):
"""A SG attached to any NIC (public or private) counts as in_use."""
iaas_service = IaaSService(set_mocked_stackit_provider())
# NIC without a public IP, but has a security group attached
private_nic = {"id": "nic-private", "security_groups": ["sg-private"]}
used = iaas_service._get_used_security_group_ids([private_nic])
assert "sg-private" in used
def test_in_use_sg_ids_populated_via_list_server_nics(self):
"""_list_server_nics marks SGs on any NIC as in_use."""
iaas_service = IaaSService(set_mocked_stackit_provider())
client = MagicMock()
client.list_project_nics.return_value = [
{"id": "nic-1", "security_groups": ["sg-1"]},
{"id": "nic-2", "security_groups": ["sg-2"]},
]
iaas_service._list_server_nics(client, "eu01")
assert "sg-1" in iaas_service.in_use_sg_ids
assert "sg-2" in iaas_service.in_use_sg_ids
# Test SecurityGroupRule helper methods
class Test_SecurityGroupRule:
def test_is_unrestricted_with_none(self):
"""Test that None ip_range is considered unrestricted."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range=None,
port_range_min=22,
port_range_max=22,
)
assert rule.is_unrestricted() is True
def test_is_unrestricted_with_cidr(self):
"""Test that 0.0.0.0/0 is considered unrestricted."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
assert rule.is_unrestricted() is True
def test_is_unrestricted_with_ipv6(self):
"""Test that ::/0 is considered unrestricted."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="::/0",
port_range_min=22,
port_range_max=22,
)
assert rule.is_unrestricted() is True
def test_is_restricted_with_specific_ip(self):
"""Test that specific IP range is considered restricted."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="10.0.0.0/8",
port_range_min=22,
port_range_max=22,
)
assert rule.is_unrestricted() is False
def test_is_ingress_true(self):
"""Test that ingress direction is properly detected."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
assert rule.is_ingress() is True
def test_is_ingress_false(self):
"""Test that egress direction returns false for is_ingress."""
rule = SecurityGroupRule(
id="rule-1",
direction="egress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
assert rule.is_ingress() is False
def test_is_tcp_with_tcp_protocol(self):
"""Test that TCP protocol is detected correctly."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
assert rule.is_tcp() is True
def test_is_tcp_with_none_protocol(self):
"""Test that None protocol is treated as TCP (all protocols)."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol=None,
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
assert rule.is_tcp() is True
def test_is_tcp_with_all_protocol(self):
"""Test that 'all' protocol is treated as TCP."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="all",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
assert rule.is_tcp() is True
def test_is_tcp_with_udp_protocol(self):
"""Test that UDP protocol returns false for is_tcp."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="udp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
assert rule.is_tcp() is False
def test_includes_port_exact_match(self):
"""Test that exact port match is detected."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=22,
port_range_max=22,
)
assert rule.includes_port(22) is True
def test_includes_port_in_range(self):
"""Test that port within range is detected."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=20,
port_range_max=25,
)
assert rule.includes_port(22) is True
assert rule.includes_port(20) is True
assert rule.includes_port(25) is True
def test_includes_port_outside_range(self):
"""Test that port outside range is not detected."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=80,
port_range_max=443,
)
assert rule.includes_port(22) is False
def test_includes_port_with_none_range(self):
"""Test that None port range means all ports."""
rule = SecurityGroupRule(
id="rule-1",
direction="ingress",
protocol="tcp",
ip_range="0.0.0.0/0",
port_range_min=None,
port_range_max=None,
)
assert rule.includes_port(22) is True
assert rule.includes_port(443) is True
assert rule.includes_port(65535) is True
class Test_IaaS_Service_Extract_Items:
"""Cover ``IaaSService._extract_items`` against the three response shapes the
StackIT SDK can return. The previous implementation matched ``dict`` via
``hasattr(response, "items")`` and returned the bound method instead of the
items field.
"""
def test_extract_items_from_sdk_model(self):
"""SDK models expose ``items`` as a non-callable attribute."""
response = MagicMock(spec=["items"])
sentinel = [{"id": "sg-1"}, {"id": "sg-2"}]
response.items = sentinel
assert IaaSService._extract_items(response, "endpoint") is sentinel
def test_extract_items_from_dict_with_items_key(self):
"""Dict responses must use the ``items`` key, not ``dict.items()``."""
response = {"items": [{"id": "sg-1"}]}
assert IaaSService._extract_items(response, "endpoint") == [{"id": "sg-1"}]
def test_extract_items_from_empty_dict(self):
"""An empty dict yields an empty list, not the ``dict.items`` method."""
assert IaaSService._extract_items({}, "endpoint") == []
def test_extract_items_from_list(self):
"""A plain list response is returned as-is."""
response = [{"id": "sg-1"}]
assert IaaSService._extract_items(response, "endpoint") is response
def test_extract_items_unknown_shape_returns_empty(self):
"""Unknown shapes fall back to an empty list and log a warning."""
assert IaaSService._extract_items(42, "endpoint") == []
def test_extract_items_ignores_dict_items_method(self):
"""Regression: ``dict`` exposes ``items`` as a method; ensure the
``isinstance(dict)`` branch wins and we do not return the bound method.
"""
result = IaaSService._extract_items({"items": ["ok"]}, "endpoint")
assert result == ["ok"]
assert not callable(result)
class Test_IaaS_Service_Used_Security_Group_Ids:
"""Cover ``_get_used_security_group_ids``. The SDK returns NIC security
group references as ``uuid.UUID`` while a security group id is a ``str``;
they must be normalized to ``str`` so the in-use membership test matches.
"""
def _service(self):
return object.__new__(IaaSService)
def test_uuid_references_are_normalized_to_str(self):
from uuid import UUID
sg_uuid = UUID("2040c1fa-72a6-47bc-a53b-f62075ae6d35")
nic = MagicMock(spec=["security_groups"])
nic.security_groups = [sg_uuid]
used = self._service()._get_used_security_group_ids([nic])
# Stored as the string form so `str(sg.id) in used` matches.
assert used == {"2040c1fa-72a6-47bc-a53b-f62075ae6d35"}
assert all(isinstance(x, str) for x in used)
def test_dict_nic_camelcase_security_groups_key(self):
nic = {"securityGroups": ["sg-aaaa", "sg-bbbb"]}
used = self._service()._get_used_security_group_ids([nic])
assert used == {"sg-aaaa", "sg-bbbb"}
def test_empty_nic_security_groups(self):
nic = MagicMock(spec=["security_groups"])
nic.security_groups = []
assert self._service()._get_used_security_group_ids([nic]) == set()
def test_in_use_matches_uuid_reference_end_to_end(self):
"""A security group whose id (str) matches a NIC reference (UUID) must
be flagged in_use=True after a full region fetch.
"""
from uuid import UUID
sg_id = "2040c1fa-72a6-47bc-a53b-f62075ae6d35"
client = MagicMock()
nic = MagicMock(spec=["security_groups"])
nic.security_groups = [UUID(sg_id)]
client.list_project_nics.return_value = {"items": [nic]}
client.list_security_groups.return_value = {"items": [{"id": sg_id}]}
client.list_security_group_rules.return_value = {"items": []}
from prowler.providers.stackit.stackit_provider import StackitProvider
service = object.__new__(IaaSService)
service.provider = MagicMock()
service.provider.handle_api_error = StackitProvider.handle_api_error
service.project_id = STACKIT_PROJECT_ID
service.scan_unused_services = False
service.regional_clients = {"eu01": client}
service.security_groups = []
service.server_nics = []
service.in_use_sg_ids = set()
service._fetch_all_regions()
assert len(service.security_groups) == 1
assert service.security_groups[0].in_use is True
class Test_IaaS_Service_Fetch_All_Regions:
"""Cover ``_fetch_all_regions`` multi-region behaviour. A project is not
provisioned in every StackIT region; the region where it is absent answers
with HTTP 404. That must be skipped, not abort the whole scan (which
previously left every check failing to load with an empty report).
"""
class _NotFound(Exception):
status = 404
class _Forbidden(Exception):
status = 403
def _service(self, regional_clients):
from prowler.providers.stackit.stackit_provider import StackitProvider
service = object.__new__(IaaSService)
service.provider = MagicMock()
# Reuse the real centralized error handler so 401/403/404 semantics
# match production.
service.provider.handle_api_error = StackitProvider.handle_api_error
service.project_id = STACKIT_PROJECT_ID
service.scan_unused_services = True
service.regional_clients = regional_clients
service.security_groups = []
service.server_nics = []
service.in_use_sg_ids = set()
return service
def _good_client(self, sg_id="sg-eu01"):
client = MagicMock()
client.list_project_nics.return_value = {"items": []}
client.list_security_groups.return_value = {"items": [{"id": sg_id}]}
client.list_security_group_rules.return_value = {"items": []}
return client
def _missing_region_client(self):
client = MagicMock()
client.list_project_nics.side_effect = self._NotFound()
client.list_security_groups.side_effect = self._NotFound()
return client
def test_skips_region_where_project_is_absent(self):
service = self._service(
{"eu01": self._good_client(), "eu02": self._missing_region_client()}
)
service._fetch_all_regions()
# eu01 security group is collected; the eu02 404 is skipped silently.
assert [sg.id for sg in service.security_groups] == ["sg-eu01"]
def test_403_still_aborts(self):
bad = MagicMock()
bad.list_project_nics.side_effect = self._Forbidden()
service = self._service({"eu01": bad})
with pytest.raises(StackITInvalidTokenError):
service._fetch_all_regions()
class Test_IaaS_Service_Log_Skipped_Security_Groups:
"""``_log_skipped_security_groups`` should emit a hint only when groups
exist, none are in use, and ``scan_unused_services`` is off.
"""
def _service(self, security_groups, scan_unused_services):
service = object.__new__(IaaSService)
service.scan_unused_services = scan_unused_services
service.security_groups = security_groups
return service
def _sg(self, in_use):
sg = MagicMock()
sg.in_use = in_use
return sg
def test_logs_when_all_skipped(self, caplog):
import logging
service = self._service([self._sg(False), self._sg(False)], False)
with caplog.at_level(logging.INFO):
service._log_skipped_security_groups()
assert "scan-unused-services" in caplog.text
def test_no_log_when_scan_unused_services_enabled(self, caplog):
import logging
service = self._service([self._sg(False)], True)
with caplog.at_level(logging.INFO):
service._log_skipped_security_groups()
assert "scan-unused-services" not in caplog.text
def test_no_log_when_a_group_is_in_use(self, caplog):
import logging
service = self._service([self._sg(False), self._sg(True)], False)
with caplog.at_level(logging.INFO):
service._log_skipped_security_groups()
assert "scan-unused-services" not in caplog.text
def test_no_log_when_no_security_groups(self, caplog):
import logging
service = self._service([], False)
with caplog.at_level(logging.INFO):
service._log_skipped_security_groups()
assert "scan-unused-services" not in caplog.text
@@ -0,0 +1,29 @@
from prowler.providers.stackit.exceptions.exceptions import (
StackITBaseException,
StackITInvalidTokenError,
)
class Test_StackIT_Exception_Catalog_Immutability:
"""Regression: ``StackITBaseException.__init__`` previously assigned the
per-instance ``message`` override straight onto the class-level
``STACKIT_ERROR_CODES`` dict, leaking the override into every later
exception of the same code raised in the same process.
"""
def _default_message(self, code: int, class_name: str) -> str:
"""Read the default message directly from the unmodified catalog."""
return StackITBaseException.STACKIT_ERROR_CODES[(code, class_name)]["message"]
def test_message_override_does_not_mutate_class_catalog(self):
default = self._default_message(16002, "StackITInvalidTokenError")
StackITInvalidTokenError(message="instance-specific message")
assert self._default_message(16002, "StackITInvalidTokenError") == default
def test_sequential_overrides_do_not_leak(self):
"""An override on instance A must not affect instance B."""
default = self._default_message(16002, "StackITInvalidTokenError")
StackITInvalidTokenError(message="A")
StackITInvalidTokenError(message="B")
second_default = self._default_message(16002, "StackITInvalidTokenError")
assert second_default == default
@@ -0,0 +1,54 @@
from uuid import uuid4
from mock import MagicMock
from prowler.providers.stackit.models import StackITIdentityInfo
from prowler.providers.stackit.stackit_provider import StackitProvider
# StackIT Test Constants
STACKIT_PROJECT_ID = str(uuid4())
STACKIT_SERVICE_ACCOUNT_KEY_PATH = "/tmp/stackit-sa-key.json"
STACKIT_PROJECT_NAME = "Test Project"
def set_mocked_stackit_provider(
service_account_key_path: str = STACKIT_SERVICE_ACCOUNT_KEY_PATH,
project_id: str = STACKIT_PROJECT_ID,
identity: StackITIdentityInfo = None,
audit_config: dict = None,
fixer_config: dict = None,
scan_unused_services: bool = False,
) -> StackitProvider:
"""
Create a mocked StackIT provider for testing.
Args:
service_account_key_path: Path to the service account key file
(default: ``STACKIT_SERVICE_ACCOUNT_KEY_PATH`` constant)
project_id: The project ID to use (default: STACKIT_PROJECT_ID)
identity: Custom identity info (default: creates new StackITIdentityInfo)
audit_config: Audit configuration dict (default: None)
fixer_config: Fixer configuration dict (default: None)
Returns:
MagicMock: A mocked StackitProvider instance
"""
if identity is None:
identity = StackITIdentityInfo(
project_id=project_id,
project_name=STACKIT_PROJECT_NAME,
)
provider = MagicMock()
provider.type = "stackit"
provider.identity = identity
provider.session = {
"project_id": project_id,
"service_account_key_path": service_account_key_path,
}
provider.audit_config = audit_config if audit_config else {}
provider.fixer_config = fixer_config if fixer_config else {}
provider.scan_unused_services = scan_unused_services
provider.auth_method = "service_account_key"
return provider
@@ -0,0 +1,15 @@
from pathlib import Path
import pytest
from prowler.lib.check.models import CheckMetadata
@pytest.mark.parametrize(
"metadata_file",
sorted(Path("prowler/providers/stackit").glob("services/**/*.metadata.json")),
)
def test_stackit_check_metadata_is_valid(metadata_file):
metadata = CheckMetadata.parse_file(metadata_file)
assert metadata.Provider == "stackit"
assert metadata.CheckID == metadata_file.stem.replace(".metadata", "")
@@ -0,0 +1,413 @@
"""Tests for StackIT Provider input validation."""
import types
from argparse import Namespace
from unittest.mock import patch
import pytest
import prowler.providers.common.provider as common_provider
import prowler.providers.stackit.stackit_provider as stackit_provider_module
from prowler.providers.common.models import Connection
from prowler.providers.stackit.exceptions.exceptions import (
StackITAPIError,
StackITInvalidProjectIdError,
StackITInvalidTokenError,
StackITNonExistentTokenError,
)
from prowler.providers.stackit.stackit_provider import StackitProvider
class TestStackITProviderValidation:
"""Test suite for StackIT Provider input validation."""
KEY_PATH = "/tmp/sa-key.json"
VALID_PROJECT_ID = "12345678-1234-1234-1234-123456789abc"
def test_validate_arguments_valid(self):
"""Test validation passes with a key path and a valid UUID."""
StackitProvider.validate_arguments(self.VALID_PROJECT_ID, self.KEY_PATH)
def test_validate_arguments_empty_key_path(self):
with pytest.raises(StackITNonExistentTokenError) as exc_info:
StackitProvider.validate_arguments(self.VALID_PROJECT_ID, "")
assert "service account credentials are required" in str(exc_info.value)
def test_validate_arguments_none_key_path(self):
with pytest.raises(StackITNonExistentTokenError) as exc_info:
StackitProvider.validate_arguments(self.VALID_PROJECT_ID, None)
assert "service account credentials are required" in str(exc_info.value)
def test_validate_arguments_whitespace_only_key_path(self):
with pytest.raises(StackITNonExistentTokenError) as exc_info:
StackitProvider.validate_arguments(self.VALID_PROJECT_ID, " ")
assert "service account credentials are required" in str(exc_info.value)
def test_validate_arguments_empty_project_id(self):
with pytest.raises(StackITInvalidProjectIdError) as exc_info:
StackitProvider.validate_arguments("", self.KEY_PATH)
assert "project ID is required" in str(exc_info.value)
def test_validate_arguments_none_project_id(self):
with pytest.raises(StackITInvalidProjectIdError) as exc_info:
StackitProvider.validate_arguments(None, self.KEY_PATH)
assert "project ID is required" in str(exc_info.value)
def test_validate_arguments_whitespace_only_project_id(self):
with pytest.raises(StackITInvalidProjectIdError) as exc_info:
StackitProvider.validate_arguments(" ", self.KEY_PATH)
assert "project ID is required" in str(exc_info.value)
def test_validate_arguments_invalid_uuid_format(self):
with pytest.raises(StackITInvalidProjectIdError) as exc_info:
StackitProvider.validate_arguments("not-a-valid-uuid", self.KEY_PATH)
assert "must be a valid UUID format" in str(exc_info.value)
assert "not-a-valid-uuid" in str(exc_info.value)
def test_validate_arguments_invalid_uuid_too_short(self):
with pytest.raises(StackITInvalidProjectIdError) as exc_info:
StackitProvider.validate_arguments("1234-5678", self.KEY_PATH)
assert "must be a valid UUID format" in str(exc_info.value)
def test_validate_arguments_uuid_without_hyphens(self):
"""Python's UUID() accepts compact form."""
StackitProvider.validate_arguments(
"12345678123412341234123456789abc", self.KEY_PATH
)
def test_validate_arguments_numeric_only_project_id(self):
with pytest.raises(StackITInvalidProjectIdError) as exc_info:
StackitProvider.validate_arguments("123456789012", self.KEY_PATH)
assert "must be a valid UUID format" in str(exc_info.value)
def test_validate_arguments_uuid_with_uppercase(self):
StackitProvider.validate_arguments(
"12345678-1234-1234-1234-123456789ABC", self.KEY_PATH
)
def test_validate_arguments_uuid_with_braces(self):
StackitProvider.validate_arguments(
"{12345678-1234-1234-1234-123456789abc}", self.KEY_PATH
)
def test_validate_arguments_uuid_v4_format(self):
StackitProvider.validate_arguments(
"550e8400-e29b-41d4-a716-446655440000", self.KEY_PATH
)
def test_validate_arguments_both_invalid(self):
"""Key path is checked first."""
with pytest.raises(StackITNonExistentTokenError):
StackitProvider.validate_arguments("not-a-uuid", "")
def test_validate_arguments_both_none(self):
"""Key path is checked first."""
with pytest.raises(StackITNonExistentTokenError):
StackitProvider.validate_arguments(None, None)
def test_validate_arguments_accepts_inline_key_without_path(self):
"""Inline key content alone is enough; the path can be omitted."""
StackitProvider.validate_arguments(
self.VALID_PROJECT_ID, None, '{"keyId": "abc"}'
)
def test_validate_arguments_accepts_inline_key_when_path_is_empty(self):
StackitProvider.validate_arguments(
self.VALID_PROJECT_ID, "", '{"keyId": "abc"}'
)
def test_validate_arguments_rejects_when_inline_key_is_whitespace(self):
with pytest.raises(StackITNonExistentTokenError):
StackitProvider.validate_arguments(self.VALID_PROJECT_ID, None, " ")
class TestStackITProviderInitialization:
def test_init_global_provider_passes_key_path_from_cli(self, monkeypatch):
"""The service account key path and inline key content from CLI args
are forwarded to the provider constructor; tokens are not part of
the API anymore."""
captured_kwargs = {}
class FakeStackitProvider:
def __init__(self, **kwargs):
captured_kwargs.update(kwargs)
fake_module = types.SimpleNamespace(StackitProvider=FakeStackitProvider)
arguments = Namespace(
provider="stackit",
stackit_project_id="12345678-1234-1234-1234-123456789abc",
stackit_service_account_key_path="/tmp/sa-key.json",
stackit_service_account_key='{"keyId": "abc"}',
stackit_region=None,
scan_unused_services=False,
config_file="config.yaml",
mutelist_file=None,
fixer_config="fixer_config.yaml",
)
monkeypatch.setattr(common_provider.Provider, "_global", None)
with (
patch.object(common_provider, "import_module", return_value=fake_module),
patch.object(
common_provider, "load_and_validate_config_file", return_value={}
),
):
common_provider.Provider.init_global_provider(arguments)
assert "api_token" not in captured_kwargs
assert captured_kwargs["project_id"] == arguments.stackit_project_id
assert (
captured_kwargs["service_account_key_path"]
== arguments.stackit_service_account_key_path
)
assert (
captured_kwargs["service_account_key"]
== arguments.stackit_service_account_key
)
assert captured_kwargs["scan_unused_services"] is False
def test_init_global_provider_passes_scan_unused_services_true(self, monkeypatch):
"""scan_unused_services=True is forwarded to the provider constructor."""
captured_kwargs = {}
class FakeStackitProvider:
def __init__(self, **kwargs):
captured_kwargs.update(kwargs)
fake_module = types.SimpleNamespace(StackitProvider=FakeStackitProvider)
arguments = Namespace(
provider="stackit",
stackit_project_id="12345678-1234-1234-1234-123456789abc",
stackit_service_account_key_path="/tmp/sa-key.json",
stackit_region=None,
scan_unused_services=True,
config_file="config.yaml",
mutelist_file=None,
fixer_config="fixer_config.yaml",
)
monkeypatch.setattr(common_provider.Provider, "_global", None)
with (
patch.object(common_provider, "import_module", return_value=fake_module),
patch.object(
common_provider, "load_and_validate_config_file", return_value={}
),
):
common_provider.Provider.init_global_provider(arguments)
assert captured_kwargs["scan_unused_services"] is True
class TestStackITProviderTestConnection:
KEY_PATH = "/tmp/sa-key.json"
KEY_CONTENT = '{"keyId": "abc", "publicKey": "..."}'
PROJECT_ID = "12345678-1234-1234-1234-123456789abc"
@pytest.fixture
def fake_stackit_resourcemanager(self, monkeypatch):
class FakeConfiguration:
def __init__(self, service_account_key_path=None, service_account_key=None):
self.service_account_key_path = service_account_key_path
self.service_account_key = service_account_key
class FakeDefaultApi:
error = None
calls = []
def __init__(self, config):
self.config = config
def get_project(self, id):
self.__class__.calls.append(
(
self.config.service_account_key_path,
self.config.service_account_key,
id,
)
)
if self.__class__.error:
raise self.__class__.error
return {"name": "Test Project"}
# The SDK is imported at module level, so patch the names bound in the
# provider module rather than sys.modules.
monkeypatch.setattr(stackit_provider_module, "Configuration", FakeConfiguration)
monkeypatch.setattr(
stackit_provider_module, "ResourceManagerDefaultApi", FakeDefaultApi
)
return FakeDefaultApi
def test_connection_success_with_key_path(self, fake_stackit_resourcemanager):
connection = StackitProvider.test_connection(
project_id=self.PROJECT_ID,
service_account_key_path=self.KEY_PATH,
)
assert connection == Connection(is_connected=True)
assert fake_stackit_resourcemanager.calls == [
(self.KEY_PATH, None, self.PROJECT_ID)
]
def test_connection_success_with_inline_key(self, fake_stackit_resourcemanager):
"""The inline key path takes precedence over the file path."""
connection = StackitProvider.test_connection(
project_id=self.PROJECT_ID,
service_account_key=self.KEY_CONTENT,
)
assert connection == Connection(is_connected=True)
assert fake_stackit_resourcemanager.calls == [
(None, self.KEY_CONTENT, self.PROJECT_ID)
]
def test_connection_inline_key_overrides_path(self, fake_stackit_resourcemanager):
connection = StackitProvider.test_connection(
project_id=self.PROJECT_ID,
service_account_key_path=self.KEY_PATH,
service_account_key=self.KEY_CONTENT,
)
assert connection == Connection(is_connected=True)
# When the inline key is present the SDK is configured with it and
# the key path is not passed on at all.
assert fake_stackit_resourcemanager.calls == [
(None, self.KEY_CONTENT, self.PROJECT_ID)
]
def test_connection_returns_error_when_raise_on_exception_is_false(
self, fake_stackit_resourcemanager
):
fake_stackit_resourcemanager.error = RuntimeError("denied")
connection = StackitProvider.test_connection(
project_id=self.PROJECT_ID,
service_account_key_path=self.KEY_PATH,
raise_on_exception=False,
)
assert isinstance(connection.error, StackITAPIError)
assert "Failed to connect to StackIT using Resource Manager" in str(
connection.error
)
def test_connection_raises_when_raise_on_exception_is_true(
self, fake_stackit_resourcemanager
):
fake_stackit_resourcemanager.error = RuntimeError("denied")
with pytest.raises(StackITAPIError):
StackitProvider.test_connection(
project_id=self.PROJECT_ID,
service_account_key_path=self.KEY_PATH,
raise_on_exception=True,
)
class TestStackITProviderGetProjectName:
@pytest.fixture
def fake_resourcemanager(self, monkeypatch):
class FakeConfiguration:
def __init__(self, service_account_key_path=None, service_account_key=None):
self.service_account_key_path = service_account_key_path
self.service_account_key = service_account_key
class FakeDefaultApi:
error = None
def __init__(self, config):
pass
def get_project(self, id):
if self.__class__.error:
raise self.__class__.error
return {"name": "My Project"}
# The SDK is imported at module level, so patch the names bound in the
# provider module rather than sys.modules.
monkeypatch.setattr(stackit_provider_module, "Configuration", FakeConfiguration)
monkeypatch.setattr(
stackit_provider_module, "ResourceManagerDefaultApi", FakeDefaultApi
)
return FakeDefaultApi
def _make_provider(self):
provider = object.__new__(StackitProvider)
provider._service_account_key_path = "/tmp/sa-key.json"
provider._service_account_key = None
provider._project_id = "12345678-1234-1234-1234-123456789abc"
return provider
def test_get_project_name_403_returns_empty_string_and_does_not_abort(
self, fake_resourcemanager
):
"""The Resource Manager lookup is cosmetic; a 403 there must NOT
abort the scan because the service account can legitimately hold
IaaS roles on the project without holding Resource Manager roles.
"""
class Http403Error(Exception):
status = 403
fake_resourcemanager.error = Http403Error()
provider = self._make_provider()
# Must not raise; returns an empty project name so the scan can
# continue and the IaaS service can decide whether *its* endpoints
# are forbidden too.
assert provider._get_project_name() == ""
def test_get_project_name_401_raises_invalid_token_error(
self, fake_resourcemanager
):
class Http401Error(Exception):
status = 401
fake_resourcemanager.error = Http401Error()
provider = self._make_provider()
with pytest.raises(StackITInvalidTokenError):
provider._get_project_name()
def test_get_project_name_other_error_returns_empty_string(
self, fake_resourcemanager
):
fake_resourcemanager.error = RuntimeError("network error")
provider = self._make_provider()
result = provider._get_project_name()
assert result == ""
class Test_StackitProvider_Handle_API_Error:
"""Direct coverage for ``StackitProvider.handle_api_error`` so 401 and 403
are treated as credential failures by every service call site, not just
``_get_project_name``.
"""
def _http(self, status_code: int) -> Exception:
err = Exception(f"http {status_code}")
err.status = status_code
return err
def test_401_raises_invalid_token_error(self):
with pytest.raises(StackITInvalidTokenError):
StackitProvider.handle_api_error(self._http(401))
def test_403_raises_invalid_token_error(self):
with pytest.raises(StackITInvalidTokenError):
StackitProvider.handle_api_error(self._http(403))
def test_other_http_status_is_reraised_unchanged(self):
original = self._http(500)
with pytest.raises(Exception) as excinfo:
StackitProvider.handle_api_error(original)
assert excinfo.value is original
def test_exception_without_status_is_reraised_unchanged(self):
original = RuntimeError("network error")
with pytest.raises(RuntimeError) as excinfo:
StackitProvider.handle_api_error(original)
assert excinfo.value is original