mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-29 06:44:21 +00:00
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:
commit
fe821a41ea
306 files changed
+19238
-2270
No files matched your search
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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**"
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
+183
@@ -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
|
||||
+183
@@ -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
|
||||
+183
@@ -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
|
||||
+183
@@ -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
|
||||
+183
@@ -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
|
||||
+183
@@ -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
|
||||
+183
@@ -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
|
||||
+183
@@ -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
|
||||
+499
@@ -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"
|
||||
+156
@@ -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
|
||||
+126
@@ -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
|
||||
+163
@@ -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
|
||||
+124
@@ -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
|
||||
+124
@@ -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
|
||||
+127
@@ -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
|
||||
+127
@@ -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
|
||||
+127
@@ -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
|
||||
+210
@@ -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
|
||||
+152
@@ -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
|
||||
+127
@@ -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
|
||||
+124
@@ -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
|
||||
+9
-3
@@ -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)
|
||||
+504
@@ -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
|
||||
+503
@@ -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"
|
||||
+389
@@ -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"
|
||||
+589
@@ -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
|
||||
Reference in new issue
Block a user